Merge branch 'dev' into 'master'

v6.3

Closes #42

See merge request abakkk/DrawOnYourScreen!14
This commit is contained in:
abakkk 2020-09-17 22:39:36 +02:00
commit 6f76640146
39 changed files with 3010 additions and 1748 deletions

14
NEWS
View File

@ -1,3 +1,17 @@
v6.3 - September 2020
=====================
* Replace user stylesheet with proper drawing settings
* Multi-palettes
* Possibility to add insertable images from the clipboard
* Image directory is configurable
* Thumbnails in "Open drawing" sub-menu
* Drawings can be directly inserted as an image.
* Add a lot of icons in the menu and the OSD notifications
* Group menu items at the bottom
* Add tooltips to menu buttons
* New "Persistent over toggles' setting #42
v6.2 - August 2020 v6.2 - August 2020
================== ==================

View File

@ -7,7 +7,7 @@ Then save your beautiful work by taking a screenshot.
## Features ## Features
* Basic shapes (rectangle, circle, ellipse, line, curve, text, image, free) * Basic shapes (rectangle, circle, ellipse, line, curve, polygon, polyline, text, image, free)
* Basic transformations (move, rotate, resize, stretch, mirror, inverse) * Basic transformations (move, rotate, resize, stretch, mirror, inverse)
* Smooth stroke * Smooth stroke
* Draw over applications * Draw over applications
@ -26,7 +26,7 @@ Then save your beautiful work by taking a screenshot.
6. `Super + Alt + D` to test 6. `Super + Alt + D` to test
7. [https://framagit.org/abakkk/DrawOnYourScreen/issues](https://framagit.org/abakkk/DrawOnYourScreen/issues) to say it doesn't work 7. [https://framagit.org/abakkk/DrawOnYourScreen/issues](https://framagit.org/abakkk/DrawOnYourScreen/issues) to say it doesn't work
## Details ## Tips and tricks
* Draw arrows: * Draw arrows:
@ -42,7 +42,11 @@ Then save your beautiful work by taking a screenshot.
* Insertable images: * Insertable images:
Add your images (jpeg, png, svg) to `~/.local/share/drawOnYourScreen/images/`. You can insert images (jpeg, png, svg) in your drawings. By default images are sought in `~/.local/share/drawOnYourScreen/images/` but the location is configurable in the preferences. Another way is to copy-past the images from Nautilus or any clipboard source by using the usual `Ctrl + V` shortcut inside the drawing mode.
* Eraser and SVG:
There is no eraser in SVG so when you export elements made with the eraser to a SVG file, they are colored with the background color, transparent if it is disabled. See `“Add a drawing background”` or edit the SVG file afterwards.
* Screenshot Tool extension: * Screenshot Tool extension:

531
area.js
View File

@ -1,4 +1,5 @@
/* jslint esversion: 6 */ /* jslint esversion: 6 */
/* exported Tools, DrawingArea */
/* /*
* Copyright 2019 Abakkk * Copyright 2019 Abakkk
@ -37,24 +38,41 @@ const Screenshot = imports.ui.screenshot;
const Me = ExtensionUtils.getCurrentExtension(); const Me = ExtensionUtils.getCurrentExtension();
const Convenience = ExtensionUtils.getSettings ? ExtensionUtils : Me.imports.convenience; const Convenience = ExtensionUtils.getSettings ? ExtensionUtils : Me.imports.convenience;
const Extension = Me.imports.extension;
const Elements = Me.imports.elements; const Elements = Me.imports.elements;
const Files = Me.imports.files; const Files = Me.imports.files;
const Menu = Me.imports.menu; const Menu = Me.imports.menu;
const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext;
const pgettext = imports.gettext.domain(Me.metadata['gettext-domain']).pgettext;
const CAIRO_DEBUG_EXTENDS = false; const CAIRO_DEBUG_EXTENDS = false;
const SVG_DEBUG_EXTENDS = false; const SVG_DEBUG_EXTENDS = false;
const TEXT_CURSOR_TIME = 600; // ms const TEXT_CURSOR_TIME = 600; // ms
const ELEMENT_GRABBER_TIME = 80; // ms, default is about 16 ms
const GRID_TILES_HORIZONTAL_NUMBER = 30;
const { Shapes, ShapeNames, Transformations, LineCapNames, LineJoinNames, FillRuleNames, const { Shapes, Transformations } = Elements;
FontWeightNames, FontStyleNames, FontStretchNames, FontVariantNames } = Elements; const { DisplayStrings } = Menu;
const FontGenericFamilies = ['Sans-Serif', 'Serif', 'Monospace', 'Cursive', 'Fantasy'];
const Manipulations = { MOVE: 100, RESIZE: 101, MIRROR: 102 }; const Manipulations = { MOVE: 100, RESIZE: 101, MIRROR: 102 };
const ManipulationNames = { 100: "Move", 101: "Resize", 102: "Mirror" }; var Tools = Object.assign({
var Tools = Object.assign({}, Shapes, Manipulations); getNameOf: function(value) {
var ToolNames = Object.assign({}, ShapeNames, ManipulationNames); return Object.keys(this).find(key => this[key] == value);
}
}, Shapes, Manipulations);
Object.defineProperty(Tools, 'getNameOf', { enumerable: false });
var FontGenericFamilies = ['Sans-Serif', 'Serif', 'Monospace', 'Cursive', 'Fantasy']; const getClutterColorFromString = function(string, fallback) {
let [success, color] = Clutter.Color.from_string(string);
color.toString = () => string;
if (success)
return color;
log(`${Me.metadata.uuid}: "${string}" color cannot be parsed.`);
color = Clutter.Color.get_static(Clutter.StaticColor[fallback.toUpperCase()]);
color.toString = () => fallback.slice(0, 1).toUpperCase() + fallback.slice(1);
return color;
};
// DrawingArea is the widget in which we draw, thanks to Cairo. // DrawingArea is the widget in which we draw, thanks to Cairo.
// It creates and manages a DrawingElement for each "brushstroke". // It creates and manages a DrawingElement for each "brushstroke".
@ -62,18 +80,12 @@ var FontGenericFamilies = ['Sans-Serif', 'Serif', 'Monospace', 'Cursive', 'Fanta
var DrawingArea = new Lang.Class({ var DrawingArea = new Lang.Class({
Name: 'DrawOnYourScreenDrawingArea', Name: 'DrawOnYourScreenDrawingArea',
Extends: St.DrawingArea, Extends: St.DrawingArea,
Signals: { 'show-osd': { param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE, GObject.TYPE_BOOLEAN] }, Signals: { 'show-osd': { param_types: [Gio.Icon.$gtype, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE, GObject.TYPE_BOOLEAN] },
'show-osd-gicon': { param_types: [Gio.Icon.$gtype, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE, GObject.TYPE_BOOLEAN] },
'update-action-mode': {}, 'update-action-mode': {},
'leave-drawing-mode': {} }, 'leave-drawing-mode': {} },
_init: function(params, monitor, helper, loadPersistent) { _init: function(params, monitor, helper, loadPersistent) {
this.parent({ style_class: 'draw-on-your-screen', name: params.name}); this.parent({ style_class: 'draw-on-your-screen', name: params.name});
this.connect('destroy', this._onDestroy.bind(this));
this.reactiveHandler = this.connect('notify::reactive', this._onReactiveChanged.bind(this));
this.settings = Convenience.getSettings();
this.monitor = monitor; this.monitor = monitor;
this.helper = helper; this.helper = helper;
@ -81,16 +93,27 @@ var DrawingArea = new Lang.Class({
this.undoneElements = []; this.undoneElements = [];
this.currentElement = null; this.currentElement = null;
this.currentTool = Shapes.NONE; this.currentTool = Shapes.NONE;
this.currentImage = 0; this.currentImage = null;
this.currentTextRightAligned = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL;
let fontName = St.Settings && St.Settings.get().font_name || Convenience.getSettings('org.gnome.desktop.interface').get_string('font-name');
this.currentFont = Pango.FontDescription.from_string(fontName);
this.currentFont.unset_fields(Pango.FontMask.SIZE);
this.defaultFontFamily = this.currentFont.get_family();
this.currentLineWidth = 5;
this.currentLineJoin = Cairo.LineJoin.ROUND;
this.currentLineCap = Cairo.LineCap.ROUND;
this.currentFillRule = Cairo.FillRule.WINDING;
this.isSquareArea = false; this.isSquareArea = false;
this.hasGrid = false; this.hasGrid = false;
this.hasBackground = false; this.hasBackground = false;
this.textHasCursor = false; this.textHasCursor = false;
this.dashedLine = false; this.dashedLine = false;
this.fill = false; this.fill = false;
this.colors = [Clutter.Color.new(0, 0, 0, 255)];
this.newThemeAttributes = {}; this.connect('destroy', this._onDestroy.bind(this));
this.oldThemeAttributes = {}; this.connect('notify::reactive', this._onReactiveChanged.bind(this));
this.drawingSettingsChangedHandler = Me.drawingSettings.connect('changed', this._onDrawingSettingsChanged.bind(this));
this._onDrawingSettingsChanged();
if (loadPersistent) if (loadPersistent)
this._loadPersistent(); this._loadPersistent();
@ -98,7 +121,7 @@ var DrawingArea = new Lang.Class({
get menu() { get menu() {
if (!this._menu) if (!this._menu)
this._menu = new Menu.DrawingMenu(this, this.monitor); this._menu = new Menu.DrawingMenu(this, this.monitor, Tools);
return this._menu; return this._menu;
}, },
@ -123,6 +146,52 @@ var DrawingArea = new Lang.Class({
this._stopElementGrabber(); this._stopElementGrabber();
}, },
get currentPalette() {
return this._currentPalette;
},
set currentPalette(palette) {
this._currentPalette = palette;
this.colors = palette[1].map(colorString => getClutterColorFromString(colorString, 'white'));
if (!this.colors[0])
this.colors.push(Clutter.Color.get_static(Clutter.StaticColor.WHITE));
},
get currentImage() {
if (!this._currentImage)
this._currentImage = Files.Images.getNext(this._currentImage);
return this._currentImage;
},
set currentImage(image) {
this._currentImage = image;
},
get currentFontFamily() {
return this.currentFont.get_family();
},
set currentFontFamily(family) {
this.currentFont.set_family(family);
},
get currentFontStyle() {
return this.currentFont.get_style();
},
set currentFontStyle(style) {
this.currentFont.set_style(style);
},
get currentFontWeight() {
return this.currentFont.get_weight();
},
set currentFontWeight(weight) {
this.currentFont.set_weight(weight);
},
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(Manipulations).map(key => Manipulations[key]).indexOf(this.currentTool) != -1;
@ -137,27 +206,12 @@ var DrawingArea = new Lang.Class({
this.currentFillRule = evenodd ? Cairo.FillRule.EVEN_ODD : Cairo.FillRule.WINDING; this.currentFillRule = evenodd ? Cairo.FillRule.EVEN_ODD : Cairo.FillRule.WINDING;
}, },
getImages() {
let images = Files.getImages();
if (!images[this.currentImage])
this.currentImage = Math.max(images.length - 1, 0);
return images;
},
get currentFontFamily() {
return this._currentFontFamily || this.currentThemeFontFamily;
},
set currentFontFamily(fontFamily) {
this._currentFontFamily = fontFamily;
},
get fontFamilies() { get fontFamilies() {
if (!this._fontFamilies) { if (!this._fontFamilies) {
let pangoFontFamilies = Elements.getPangoFontFamilies().filter(family => { let otherFontFamilies = Elements.getAllFontFamilies().filter(family => {
return family != this.currentThemeFontFamily && FontGenericFamilies.indexOf(family) == -1; return family != this.defaultFontFamily && FontGenericFamilies.indexOf(family) == -1;
}); });
this._fontFamilies = [this.currentThemeFontFamily].concat(FontGenericFamilies, pangoFontFamilies); this._fontFamilies = [this.defaultFontFamily].concat(FontGenericFamilies, otherFontFamilies);
} }
return this._fontFamilies; return this._fontFamilies;
}, },
@ -181,57 +235,44 @@ var DrawingArea = new Lang.Class({
this.queue_repaint(); this.queue_repaint();
}, },
_updateStyle: function() { _onDrawingSettingsChanged: function() {
try { this.palettes = Me.drawingSettings.get_value('palettes').deep_unpack();
let themeNode = this.get_theme_node(); if (!this.colors) {
for (let i = 1; i < 10; i++) { if (this.palettes[0])
this.colors[i] = themeNode.get_color('-drawing-color' + i); this.currentPalette = this.palettes[0];
} else
let font = themeNode.get_font(); this.currentPalette = ['Palette', ['White']];
this.newThemeAttributes.ThemeFontFamily = font.get_family(); }
try { this.newThemeAttributes.FontWeight = font.get_weight(); } catch(e) { this.newThemeAttributes.FontWeight = Pango.Weight.NORMAL; } if (!this.currentColor)
this.newThemeAttributes.FontStyle = font.get_style(); this.currentColor = this.colors[0];
this.newThemeAttributes.FontStretch = font.get_stretch();
this.newThemeAttributes.FontVariant = font.get_variant(); if (Me.drawingSettings.get_boolean('square-area-auto')) {
this.newThemeAttributes.TextRightAligned = themeNode.get_text_align() == St.TextAlign.RIGHT; this.squareAreaSize = Math.pow(2, 6);
this.newThemeAttributes.LineWidth = themeNode.get_length('-drawing-line-width'); while (this.squareAreaSize * 2 < Math.min(this.monitor.width, this.monitor.height))
this.newThemeAttributes.LineJoin = themeNode.get_double('-drawing-line-join'); this.squareAreaSize *= 2;
this.newThemeAttributes.LineCap = themeNode.get_double('-drawing-line-cap'); } else {
this.newThemeAttributes.FillRule = themeNode.get_double('-drawing-fill-rule'); this.squareAreaSize = Me.drawingSettings.get_uint('square-area-size');
this.dashArray = [Math.abs(themeNode.get_length('-drawing-dash-array-on')), Math.abs(themeNode.get_length('-drawing-dash-array-off'))];
this.dashOffset = themeNode.get_length('-drawing-dash-offset');
this.gridGap = themeNode.get_length('-grid-overlay-gap');
this.gridLineWidth = themeNode.get_length('-grid-overlay-line-width');
this.gridInterlineWidth = themeNode.get_length('-grid-overlay-interline-width');
this.gridColor = themeNode.get_color('-grid-overlay-color');
this.squareAreaWidth = themeNode.get_length('-drawing-square-area-width');
this.squareAreaHeight = themeNode.get_length('-drawing-square-area-height');
this.activeBackgroundColor = themeNode.get_color('-drawing-background-color');
} catch(e) {
logError(e);
} }
for (let i = 1; i < 10; i++) { this.areaBackgroundColor = getClutterColorFromString(Me.drawingSettings.get_string('background-color'), 'black');
this.colors[i] = this.colors[i].alpha ? this.colors[i] : this.colors[0];
this.gridColor = getClutterColorFromString(Me.drawingSettings.get_string('grid-color'), 'gray');
if (Me.drawingSettings.get_boolean('grid-line-auto')) {
this.gridLineSpacing = Math.round(this.monitor.width / (5 * GRID_TILES_HORIZONTAL_NUMBER));
this.gridLineWidth = this.gridLineSpacing / 20;
} else {
this.gridLineSpacing = Me.drawingSettings.get_uint('grid-line-spacing');
this.gridLineWidth = Math.round(Me.drawingSettings.get_double('grid-line-width') * 100) / 100;
} }
this.currentColor = this.currentColor || this.colors[1];
this._fontFamilies = null; this.dashOffset = Math.round(Me.drawingSettings.get_double('dash-offset') * 100) / 100;
// SVG does not support 'Ultra-heavy' weight (1000) if (Me.drawingSettings.get_boolean('dash-array-auto')) {
this.newThemeAttributes.FontWeight = Math.min(this.newThemeAttributes.FontWeight, 900); this.dashArray = [0, 0];
this.newThemeAttributes.LineWidth = (this.newThemeAttributes.LineWidth > 0) ? this.newThemeAttributes.LineWidth : 3; } else {
this.newThemeAttributes.LineJoin = ([0, 1, 2].indexOf(this.newThemeAttributes.LineJoin) != -1) ? this.newThemeAttributes.LineJoin : Cairo.LineJoin.ROUND; let on = Math.round(Me.drawingSettings.get_double('dash-array-on') * 100) / 100;
this.newThemeAttributes.LineCap = ([0, 1, 2].indexOf(this.newThemeAttributes.LineCap) != -1) ? this.newThemeAttributes.LineCap : Cairo.LineCap.ROUND; let off = Math.round(Me.drawingSettings.get_double('dash-array-off') * 100) / 100;
this.newThemeAttributes.FillRule = ([0, 1].indexOf(this.newThemeAttributes.FillRule) != -1) ? this.newThemeAttributes.FillRule : Cairo.FillRule.WINDING; this.dashArray = [on, off];
for (let attributeName in this.newThemeAttributes) {
if (this.newThemeAttributes[attributeName] != this.oldThemeAttributes[attributeName]) {
this.oldThemeAttributes[attributeName] = this.newThemeAttributes[attributeName];
this[`current${attributeName}`] = this.newThemeAttributes[attributeName];
}
} }
this.gridGap = this.gridGap && this.gridGap >= 1 ? this.gridGap : 10;
this.gridLineWidth = this.gridLineWidth || 0.4;
this.gridInterlineWidth = this.gridInterlineWidth || 0.2;
this.gridColor = this.gridColor && this.gridColor.alpha ? this.gridColor : Clutter.Color.new(127, 127, 127, 255);
}, },
_repaint: function(cr) { _repaint: function(cr) {
@ -269,27 +310,27 @@ var DrawingArea = new Lang.Class({
cr.restore(); cr.restore();
} }
if (this.reactive && this.hasGrid && this.gridGap && this.gridGap >= 1) { if (this.reactive && this.hasGrid) {
cr.save(); cr.save();
Clutter.cairo_set_source_color(cr, this.gridColor); Clutter.cairo_set_source_color(cr, this.gridColor);
let [gridX, gridY] = [0, 0]; let [gridX, gridY] = [0, 0];
while (gridX < this.monitor.width / 2) { while (gridX < this.monitor.width / 2) {
cr.setLineWidth((gridX / this.gridGap) % 5 ? this.gridInterlineWidth : this.gridLineWidth); cr.setLineWidth((gridX / this.gridLineSpacing) % 5 ? this.gridLineWidth / 2 : this.gridLineWidth);
cr.moveTo(this.monitor.width / 2 + gridX, 0); cr.moveTo(this.monitor.width / 2 + gridX, 0);
cr.lineTo(this.monitor.width / 2 + gridX, this.monitor.height); cr.lineTo(this.monitor.width / 2 + gridX, this.monitor.height);
cr.moveTo(this.monitor.width / 2 - gridX, 0); cr.moveTo(this.monitor.width / 2 - gridX, 0);
cr.lineTo(this.monitor.width / 2 - gridX, this.monitor.height); cr.lineTo(this.monitor.width / 2 - gridX, this.monitor.height);
gridX += this.gridGap; gridX += this.gridLineSpacing;
cr.stroke(); cr.stroke();
} }
while (gridY < this.monitor.height / 2) { while (gridY < this.monitor.height / 2) {
cr.setLineWidth((gridY / this.gridGap) % 5 ? this.gridInterlineWidth : this.gridLineWidth); cr.setLineWidth((gridY / this.gridLineSpacing) % 5 ? this.gridLineWidth / 2 : this.gridLineWidth);
cr.moveTo(0, this.monitor.height / 2 + gridY); cr.moveTo(0, this.monitor.height / 2 + gridY);
cr.lineTo(this.monitor.width, this.monitor.height / 2 + gridY); cr.lineTo(this.monitor.width, this.monitor.height / 2 + gridY);
cr.moveTo(0, this.monitor.height / 2 - gridY); cr.moveTo(0, this.monitor.height / 2 - gridY);
cr.lineTo(this.monitor.width, this.monitor.height / 2 - gridY); cr.lineTo(this.monitor.width, this.monitor.height / 2 - gridY);
gridY += this.gridGap; gridY += this.gridLineSpacing;
cr.stroke(); cr.stroke();
} }
cr.restore(); cr.restore();
@ -362,8 +403,9 @@ var DrawingArea = new Lang.Class({
event.get_key_symbol() == Clutter.KEY_KP_Enter || event.get_key_symbol() == Clutter.KEY_KP_Enter ||
event.get_key_symbol() == Clutter.KEY_Control_L) { event.get_key_symbol() == Clutter.KEY_Control_L) {
if (this.currentElement.points.length == 2) if (this.currentElement.points.length == 2)
this.emit('show-osd', null, _("Press <i>%s</i> to get\na fourth control point") // Translators: %s is a key label
.format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true); this.emit('show-osd', Files.Icons.ARC, _("Press <i>%s</i> to get\na fourth control point")
.format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true);
this.currentElement.addPoint(); this.currentElement.addPoint();
this.updatePointerCursor(true); this.updatePointerCursor(true);
this._redisplay(); this._redisplay();
@ -425,8 +467,9 @@ var DrawingArea = new Lang.Class({
} }
// Reduce computing without notable effect. // Reduce computing without notable effect.
if (Math.random() <= 0.75) if (event.get_time() - (this.elementGrabberTimestamp || 0) < ELEMENT_GRABBER_TIME)
return; return;
this.elementGrabberTimestamp = event.get_time();
let coords = event.get_coords(); let coords = event.get_coords();
let [s, x, y] = this.transform_stage_point(coords[0], coords[1]); let [s, x, y] = this.transform_stage_point(coords[0], coords[1]);
@ -459,7 +502,7 @@ var DrawingArea = new Lang.Class({
if (this.grabbedElementLocked) { if (this.grabbedElementLocked) {
this.updatePointerCursor(); this.updatePointerCursor();
let label = controlPressed ? _("Mark a point of symmetry") : _("Draw a line of symmetry"); let label = controlPressed ? _("Mark a point of symmetry") : _("Draw a line of symmetry");
this.emit('show-osd', null, label, "", -1, true); this.emit('show-osd', Files.Icons.TOOL_MIRROR, label, "", -1, true);
return; return;
} }
} }
@ -473,6 +516,10 @@ var DrawingArea = new Lang.Class({
if (duplicate) { if (duplicate) {
// deep cloning // deep cloning
let copy = new this.grabbedElement.constructor(JSON.parse(JSON.stringify(this.grabbedElement))); let copy = new this.grabbedElement.constructor(JSON.parse(JSON.stringify(this.grabbedElement)));
if (this.grabbedElement.color)
copy.color = this.grabbedElement.color;
if (this.grabbedElement.font)
copy.font = this.grabbedElement.font;
if (this.grabbedElement.image) if (this.grabbedElement.image)
copy.image = this.grabbedElement.image; copy.image = this.grabbedElement.image;
this.elements.push(copy); this.elements.push(copy);
@ -560,34 +607,27 @@ var DrawingArea = new Lang.Class({
if (this.currentTool == Shapes.TEXT) { if (this.currentTool == Shapes.TEXT) {
this.currentElement = new Elements.DrawingElement({ this.currentElement = new Elements.DrawingElement({
shape: this.currentTool, shape: this.currentTool,
color: this.currentColor.to_string(), color: this.currentColor,
eraser: eraser, eraser: eraser,
font: { font: this.currentFont.copy(),
family: this.currentFontFamily, // Translators: initial content of the text area
weight: this.currentFontWeight, text: pgettext("text-area-content", "Text"),
style: this.currentFontStyle,
stretch: this.currentFontStretch,
variant: this.currentFontVariant },
text: _("Text"),
textRightAligned: this.currentTextRightAligned, textRightAligned: this.currentTextRightAligned,
points: [] points: []
}); });
} else if (this.currentTool == Shapes.IMAGE) { } else if (this.currentTool == Shapes.IMAGE) {
let images = this.getImages();
if (!images.length)
return;
this.currentElement = new Elements.DrawingElement({ this.currentElement = new Elements.DrawingElement({
shape: this.currentTool, shape: this.currentTool,
color: this.currentColor.to_string(), color: this.currentColor,
eraser: eraser, eraser: eraser,
image: images[this.currentImage], image: this.currentImage,
operator: this.currentOperator, operator: this.currentOperator,
points: [] points: []
}); });
} else { } else {
this.currentElement = new Elements.DrawingElement({ this.currentElement = new Elements.DrawingElement({
shape: this.currentTool, shape: this.currentTool,
color: this.currentColor.to_string(), color: this.currentColor,
eraser: eraser, eraser: eraser,
fill: this.fill, fill: this.fill,
fillRule: this.currentFillRule, fillRule: this.currentFillRule,
@ -599,9 +639,12 @@ 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 == Shapes.POLYGON || this.currentTool == Shapes.POLYLINE) {
this.emit('show-osd', null, _("Press <i>%s</i> to mark vertices") let icon = Files.Icons[this.currentTool == Shapes.POLYGON ? 'TOOL_POLYGON' : 'TOOL_POLYLINE'];
// Translators: %s is a key label
this.emit('show-osd', icon, _("Press <i>%s</i> to mark vertices")
.format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true); .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true);
}
this.motionHandler = this.connect('motion-event', (actor, event) => { this.motionHandler = this.connect('motion-event', (actor, event) => {
if (this.spaceKeyPressed) if (this.spaceKeyPressed)
@ -661,8 +704,9 @@ var DrawingArea = new Lang.Class({
let [x, y] = [this.currentElement.x, this.currentElement.y]; let [x, y] = [this.currentElement.x, this.currentElement.y];
this.currentElement.text = ''; this.currentElement.text = '';
this.currentElement.cursorPosition = 0; this.currentElement.cursorPosition = 0;
this.emit('show-osd', null, _("Type your text and press <i>%s</i>") // Translators: %s is a key label
.format(Gtk.accelerator_get_label(Clutter.KEY_Escape, 0)), "", -1, true); this.emit('show-osd', Files.Icons.TOOL_TEXT, _("Type your text and press <i>%s</i>")
.format(Gtk.accelerator_get_label(Clutter.KEY_Escape, 0)), "", -1, true);
this._updateTextCursorTimeout(); this._updateTextCursorTimeout();
this.textHasCursor = true; this.textHasCursor = true;
this._redisplay(); this._redisplay();
@ -749,7 +793,7 @@ var DrawingArea = new Lang.Class({
setPointerCursor: function(pointerCursorName) { setPointerCursor: function(pointerCursorName) {
if (!this.currentPointerCursorName || this.currentPointerCursorName != pointerCursorName) { if (!this.currentPointerCursorName || this.currentPointerCursorName != pointerCursorName) {
this.currentPointerCursorName = pointerCursorName; this.currentPointerCursorName = pointerCursorName;
Extension.setCursor(pointerCursorName); Me.stateObj.areaManager.setCursor(pointerCursorName);
} }
}, },
@ -835,7 +879,7 @@ var DrawingArea = new Lang.Class({
toggleBackground: function() { toggleBackground: function() {
this.hasBackground = !this.hasBackground; this.hasBackground = !this.hasBackground;
this.get_parent().set_background_color(this.hasBackground ? this.activeBackgroundColor : null); this.get_parent().set_background_color(this.hasBackground ? this.areaBackgroundColor : null);
}, },
toggleGrid: function() { toggleGrid: function() {
@ -846,10 +890,8 @@ var DrawingArea = new Lang.Class({
toggleSquareArea: function() { toggleSquareArea: function() {
this.isSquareArea = !this.isSquareArea; this.isSquareArea = !this.isSquareArea;
if (this.isSquareArea) { if (this.isSquareArea) {
let width = this.squareAreaWidth || this.squareAreaHeight || Math.min(this.monitor.width, this.monitor.height) * 3 / 4; this.set_position((this.monitor.width - this.squareAreaSize) / 2, (this.monitor.height - this.squareAreaSize) / 2);
let height = this.squareAreaHeight || this.squareAreaWidth || Math.min(this.monitor.width, this.monitor.height) * 3 / 4; this.set_size(this.squareAreaSize, this.squareAreaSize);
this.set_position(Math.floor(this.monitor.width / 2 - width / 2), Math.floor(this.monitor.height / 2 - height / 2));
this.set_size(width, height);
this.add_style_class_name('draw-on-your-screen-square-area'); this.add_style_class_name('draw-on-your-screen-square-area');
} else { } else {
this.set_position(0, 0); this.set_position(0, 0);
@ -858,76 +900,87 @@ var DrawingArea = new Lang.Class({
} }
}, },
switchColor: function() {
this.selectColor((this.currentColor == this.colors[1]) ? 2 : 1);
},
selectColor: function(index) { selectColor: function(index) {
if (!this.colors[index])
return;
this.currentColor = this.colors[index]; this.currentColor = this.colors[index];
if (this.currentElement) { if (this.currentElement) {
this.currentElement.color = this.currentColor.to_string(); this.currentElement.color = this.currentColor;
this._redisplay(); this._redisplay();
} }
// Foreground color markup is not displayed since 3.36, use style instead but the transparency is lost. // Foreground color markup is not displayed since 3.36, use style instead but the transparency is lost.
this.emit('show-osd', null, this.currentColor.to_string(), this.currentColor.to_string().slice(0, 7), -1, false); this.emit('show-osd', Files.Icons.COLOR, String(this.currentColor), this.currentColor.to_string().slice(0, 7), -1, false);
}, },
selectTool: function(tool) { selectTool: function(tool) {
this.currentTool = tool; this.currentTool = tool;
this.emit('show-osd', null, _(ToolNames[tool]), "", -1, false); this.emit('show-osd', Files.Icons[`TOOL_${Tools.getNameOf(tool)}`] || null, DisplayStrings.Tool[tool], "", -1, false);
this.updatePointerCursor(); this.updatePointerCursor();
}, },
switchFill: function() { switchFill: function() {
this.fill = !this.fill; this.fill = !this.fill;
this.emit('show-osd', null, this.fill ? _("Fill") : _("Outline"), "", -1, false); let icon = Files.Icons[this.fill ? 'FILL' : 'STROKE'];
}, this.emit('show-osd', icon, DisplayStrings.getFill(this.fill), "", -1, false);
switchDash: function() {
this.dashedLine = !this.dashedLine;
this.emit('show-osd', null, this.dashedLine ? _("Dashed line") : _("Full line"), "", -1, false);
},
incrementLineWidth: function(increment) {
this.currentLineWidth = Math.max(this.currentLineWidth + increment, 0);
this.emit('show-osd', null, _("%d px").format(this.currentLineWidth), "", 2 * this.currentLineWidth, false);
},
switchLineJoin: function() {
this.currentLineJoin = this.currentLineJoin == 2 ? 0 : this.currentLineJoin + 1;
this.emit('show-osd', null, _(LineJoinNames[this.currentLineJoin]), "", -1, false);
},
switchLineCap: function() {
this.currentLineCap = this.currentLineCap == 2 ? 0 : this.currentLineCap + 1;
this.emit('show-osd', null, _(LineCapNames[this.currentLineCap]), "", -1, false);
}, },
switchFillRule: function() { switchFillRule: function() {
this.currentFillRule = this.currentFillRule == 1 ? 0 : this.currentFillRule + 1; this.currentFillRule = this.currentFillRule == 1 ? 0 : this.currentFillRule + 1;
this.emit('show-osd', null, _(FillRuleNames[this.currentFillRule]), "", -1, false); let icon = Files.Icons[this.currentEvenodd ? 'FILLRULE_EVENODD' : 'FILLRULE_NONZERO'];
this.emit('show-osd', icon, DisplayStrings.FillRule[this.currentFillRule], "", -1, false);
},
switchColorPalette: function(reverse) {
let index = this.palettes.indexOf(this.currentPalette);
if (reverse)
this.currentPalette = index <= 0 ? this.palettes[this.palettes.length - 1] : this.palettes[index - 1];
else
this.currentPalette = index == this.palettes.length - 1 ? this.palettes[0] : this.palettes[index + 1];
this.emit('show-osd', Files.Icons.PALETTE, this.currentPalette[0], "", -1, false);
},
switchDash: function() {
this.dashedLine = !this.dashedLine;
let icon = Files.Icons[this.dashedLine ? 'DASHED_LINE' : 'FULL_LINE'];
this.emit('show-osd', icon, DisplayStrings.getDashedLine(this.dashedLine), "", -1, false);
},
incrementLineWidth: function(increment) {
this.currentLineWidth = Math.max(this.currentLineWidth + increment, 0);
this.emit('show-osd', null, DisplayStrings.getPixels(this.currentLineWidth), "", 2 * this.currentLineWidth, false);
},
switchLineJoin: function() {
this.currentLineJoin = this.currentLineJoin == 2 ? 0 : this.currentLineJoin + 1;
this.emit('show-osd', Files.Icons.LINEJOIN, DisplayStrings.LineJoin[this.currentLineJoin], "", -1, false);
},
switchLineCap: function() {
this.currentLineCap = this.currentLineCap == 2 ? 0 : this.currentLineCap + 1;
this.emit('show-osd', Files.Icons.LINECAP, DisplayStrings.LineCap[this.currentLineCap], "", -1, false);
}, },
switchFontWeight: function() { switchFontWeight: function() {
let fontWeights = Object.keys(FontWeightNames).map(key => Number(key)); let fontWeights = Object.keys(DisplayStrings.FontWeight).map(key => Number(key));
let index = fontWeights.indexOf(this.currentFontWeight); let index = fontWeights.indexOf(this.currentFontWeight);
this.currentFontWeight = index == fontWeights.length - 1 ? fontWeights[0] : fontWeights[index + 1]; this.currentFontWeight = index == fontWeights.length - 1 ? fontWeights[0] : fontWeights[index + 1];
if (this.currentElement && this.currentElement.font) { if (this.currentElement && this.currentElement.font) {
this.currentElement.font.weight = this.currentFontWeight; this.currentElement.font.set_weight(this.currentFontWeight);
this._redisplay(); this._redisplay();
} }
this.emit('show-osd', null, `<span font_weight="${this.currentFontWeight}">` + this.emit('show-osd', Files.Icons.FONT_WEIGHT, `<span font_weight="${this.currentFontWeight}">` +
`${_(FontWeightNames[this.currentFontWeight])}</span>`, "", -1, false); `${DisplayStrings.FontWeight[this.currentFontWeight]}</span>`, "", -1, false);
}, },
switchFontStyle: function() { switchFontStyle: function() {
this.currentFontStyle = this.currentFontStyle == 2 ? 0 : this.currentFontStyle + 1; this.currentFontStyle = this.currentFontStyle == 2 ? 0 : this.currentFontStyle + 1;
if (this.currentElement && this.currentElement.font) { if (this.currentElement && this.currentElement.font) {
this.currentElement.font.style = this.currentFontStyle; this.currentElement.font.set_style(this.currentFontStyle);
this._redisplay(); this._redisplay();
} }
this.emit('show-osd', null, `<span font_style="${FontStyleNames[this.currentFontStyle].toLowerCase()}">` + this.emit('show-osd', Files.Icons.FONT_STYLE, `<span font_style="${DisplayStrings.FontStyleMarkup[this.currentFontStyle]}">` +
`${_(FontStyleNames[this.currentFontStyle])}</span>`, "", -1, false); `${DisplayStrings.FontStyle[this.currentFontStyle]}</span>`, "", -1, false);
}, },
switchFontFamily: function(reverse) { switchFontFamily: function(reverse) {
@ -937,10 +990,10 @@ var DrawingArea = new Lang.Class({
else else
this.currentFontFamily = (index == this.fontFamilies.length - 1) ? this.fontFamilies[0] : this.fontFamilies[index + 1]; this.currentFontFamily = (index == this.fontFamilies.length - 1) ? this.fontFamilies[0] : this.fontFamilies[index + 1];
if (this.currentElement && this.currentElement.font) { if (this.currentElement && this.currentElement.font) {
this.currentElement.font.family = this.currentFontFamily; this.currentElement.font.set_family(this.currentFontFamily);
this._redisplay(); this._redisplay();
} }
this.emit('show-osd', null, `<span font_family="${this.currentFontFamily}">${_(this.currentFontFamily)}</span>`, "", -1, false); this.emit('show-osd', Files.Icons.FONT_FAMILY, `<span font_family="${this.currentFontFamily}">${DisplayStrings.getFontFamily(this.currentFontFamily)}</span>`, "", -1, false);
}, },
switchTextAlignment: function() { switchTextAlignment: function() {
@ -949,16 +1002,23 @@ var DrawingArea = new Lang.Class({
this.currentElement.textRightAligned = this.currentTextRightAligned; this.currentElement.textRightAligned = this.currentTextRightAligned;
this._redisplay(); this._redisplay();
} }
this.emit('show-osd', null, this.currentTextRightAligned ? _("Right aligned") : _("Left aligned"), "", -1, false); let icon = Files.Icons[this.currentTextRightAligned ? 'RIGHT_ALIGNED' : 'LEFT_ALIGNED'];
this.emit('show-osd', icon, DisplayStrings.getTextAlignment(this.currentTextRightAligned), "", -1, false);
}, },
switchImageFile: function() { switchImageFile: function(reverse) {
let images = this.getImages(); this.currentImage = Files.Images[reverse ? 'getPrevious' : 'getNext'](this.currentImage);
if (!images.length) if (this.currentImage)
return; this.emit('show-osd', this.currentImage.gicon, this.currentImage.toString(), "", -1, false);
if (images.length > 1) },
this.currentImage = this.currentImage == images.length - 1 ? 0 : this.currentImage + 1;
this.emit('show-osd-gicon', images[this.currentImage].gicon, images[this.currentImage].toString(), "", -1, false); pasteImageFiles: function() {
Files.Images.addImagesFromClipboard(lastImage => {
this.currentImage = lastImage;
this.currentTool = Shapes.IMAGE;
this.updatePointerCursor();
this.emit('show-osd', this.currentImage.gicon, this.currentImage.toString(), "", -1, false);
});
}, },
toggleHelp: function() { toggleHelp: function() {
@ -984,7 +1044,7 @@ var DrawingArea = new Lang.Class({
}, },
_onDestroy: function() { _onDestroy: function() {
this.disconnect(this.reactiveHandler); Me.drawingSettings.disconnect(this.drawingSettingsChangedHandler);
this.erase(); this.erase();
if (this._menu) if (this._menu)
this._menu.disable(); this._menu.disable();
@ -1001,11 +1061,10 @@ var DrawingArea = new Lang.Class({
this.buttonPressedHandler = this.connect('button-press-event', this._onButtonPressed.bind(this)); this.buttonPressedHandler = this.connect('button-press-event', this._onButtonPressed.bind(this));
this.keyboardPopupMenuHandler = this.connect('popup-menu', this._onKeyboardPopupMenu.bind(this)); this.keyboardPopupMenuHandler = this.connect('popup-menu', this._onKeyboardPopupMenu.bind(this));
this.scrollHandler = this.connect('scroll-event', this._onScroll.bind(this)); this.scrollHandler = this.connect('scroll-event', this._onScroll.bind(this));
this.get_parent().set_background_color(this.reactive && this.hasBackground ? this.activeBackgroundColor : null); this.get_parent().set_background_color(this.reactive && this.hasBackground ? this.areaBackgroundColor : null);
this._updateStyle();
}, },
leaveDrawingMode: function(save) { leaveDrawingMode: function(save, erase) {
if (this.stageKeyPressedHandler) { if (this.stageKeyPressedHandler) {
global.stage.disconnect(this.stageKeyPressedHandler); global.stage.disconnect(this.stageKeyPressedHandler);
this.stageKeyPressedHandler = null; this.stageKeyPressedHandler = null;
@ -1041,14 +1100,49 @@ var DrawingArea = new Lang.Class({
this.currentElement = null; this.currentElement = null;
this._stopTextCursorTimeout(); this._stopTextCursorTimeout();
this._redisplay(); if (erase)
this.erase();
else
this._redisplay();
this.closeMenu(); this.closeMenu();
this.get_parent().set_background_color(null); this.get_parent().set_background_color(null);
Files.Images.reset();
if (save) if (save)
this.savePersistent(); this.savePersistent();
}, },
saveAsSvg: function() { // Used by the menu.
getSvgContentsForJson(json) {
let elements = [];
let elementsContent = '';
elements.push(...JSON.parse(json.contents).map(object => {
if (object.color)
object.color = getClutterColorFromString(object.color, 'white');
if (object.font && typeof object.font == 'string')
object.font = Pango.FontDescription.from_string(object.font);
if (object.image)
object.image = new Files.Image(object.image);
return new Elements.DrawingElement(object);
}));
elements.forEach(element => elementsContent += element.buildSVG('transparent'));
let prefixes = 'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"';
let getGiconSvgContent = () => {
let size = Math.min(this.monitor.width, this.monitor.height);
let [x, y] = [(this.monitor.width - size) / 2, (this.monitor.height - size) / 2];
return `<svg viewBox="${x} ${y} ${size} ${size}" ${prefixes}>${elementsContent}\n</svg>`;
};
let getImageSvgContent = () => {
return `<svg viewBox="0 0 ${this.width} ${this.height}" ${prefixes}>${elementsContent}\n</svg>`;
};
return [getGiconSvgContent, getImageSvgContent];
},
exportToSvg: function() {
// stop drawing or writing // stop drawing or writing
if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.isWriting) { if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.isWriting) {
this._stopWriting(); this._stopWriting();
@ -1062,7 +1156,7 @@ var DrawingArea = new Lang.Class({
let content = `<svg viewBox="0 0 ${this.width} ${this.height}" ${prefixes}>`; let content = `<svg viewBox="0 0 ${this.width} ${this.height}" ${prefixes}>`;
if (SVG_DEBUG_EXTENDS) if (SVG_DEBUG_EXTENDS)
content = `<svg viewBox="${-this.width} ${-this.height} ${2 * this.width} ${2 * this.height}" xmlns="http://www.w3.org/2000/svg">`; content = `<svg viewBox="${-this.width} ${-this.height} ${2 * this.width} ${2 * this.height}" xmlns="http://www.w3.org/2000/svg">`;
let backgroundColorString = this.hasBackground ? this.activeBackgroundColor.to_string() : 'transparent'; let backgroundColorString = this.hasBackground ? String(this.areaBackgroundColor) : 'transparent';
if (backgroundColorString != 'transparent') { if (backgroundColorString != 'transparent') {
content += `\n <rect id="background" width="100%" height="100%" fill="${backgroundColorString}"/>`; content += `\n <rect id="background" width="100%" height="100%" fill="${backgroundColorString}"/>`;
} }
@ -1070,19 +1164,10 @@ var DrawingArea = new Lang.Class({
content += `\n <line stroke="black" x1="0" y1="${-this.height}" x2="0" y2="${this.height}"/>`; content += `\n <line stroke="black" x1="0" y1="${-this.height}" x2="0" y2="${this.height}"/>`;
content += `\n <line stroke="black" x1="${-this.width}" y1="0" x2="${this.width}" y2="0"/>`; content += `\n <line stroke="black" x1="${-this.width}" y1="0" x2="${this.width}" y2="0"/>`;
} }
for (let i = 0; i < this.elements.length; i++) { this.elements.forEach(element => content += element.buildSVG(backgroundColorString));
content += this.elements[i].buildSVG(backgroundColorString);
}
content += "\n</svg>"; content += "\n</svg>";
let filename = `${Me.metadata['svg-file-name']} ${Files.getDateString()}.svg`; if (Files.saveSvg(content)) {
let dir = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES);
let path = GLib.build_filenamev([dir, filename]);
if (GLib.file_test(path, GLib.FileTest.EXISTS))
return false;
let success = GLib.file_set_contents(path, content);
if (success) {
// pass the parent (bgContainer) to Flashspot because coords of this are relative // pass the parent (bgContainer) to Flashspot because coords of this are relative
let flashspot = new Screenshot.Flashspot(this.get_parent()); let flashspot = new Screenshot.Flashspot(this.get_parent());
flashspot.fire(); flashspot.fire();
@ -1095,7 +1180,7 @@ var DrawingArea = new Lang.Class({
} }
}, },
_saveAsJson: function(name, notify, callback) { _saveAsJson: function(json, notify, callback) {
// stop drawing or writing // stop drawing or writing
if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.isWriting) { if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.isWriting) {
this._stopWriting(); this._stopWriting();
@ -1103,46 +1188,31 @@ var DrawingArea = new Lang.Class({
this._stopDrawing(); this._stopDrawing();
} }
let json = new Files.Json({ name });
let oldContents;
if (name == Me.metadata['persistent-file-name']) {
let oldContents = json.contents;
// do not create a file to write just an empty array
if (!oldContents && this.elements.length == 0)
return;
}
// do not use "content = JSON.stringify(this.elements, null, 2);", neither "content = JSON.stringify(this.elements);" // do not use "content = JSON.stringify(this.elements, null, 2);", neither "content = JSON.stringify(this.elements);"
// do compromise between disk usage and human readability // do compromise between disk usage and human readability
let contents = `[\n ` + new Array(...this.elements.map(element => JSON.stringify(element))).join(`,\n\n `) + `\n]`; let contents = this.elements.length ? `[\n ` + new Array(...this.elements.map(element => JSON.stringify(element))).join(`,\n\n `) + `\n]` : '[]';
if (name == Me.metadata['persistent-file-name'] && contents == oldContents)
return;
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
json.contents = contents; json.contents = contents;
if (notify) if (notify)
this.emit('show-osd', 'document-save-symbolic', name, "", -1, false); this.emit('show-osd', Files.Icons.SAVE, json.name, "", -1, false);
if (name != Me.metadata['persistent-file-name']) { if (!json.isPersistent)
this.jsonName = name; this.currentJson = json;
this.lastJsonContents = contents;
}
if (callback) if (callback)
callback(); callback();
}); });
}, },
saveAsJsonWithName: function(name, callback) { saveAsJsonWithName: function(name, callback) {
this._saveAsJson(name, false, callback); this._saveAsJson(Files.Jsons.getNamed(name), false, callback);
}, },
saveAsJson: function() { saveAsJson: function(notify, callback) {
this._saveAsJson(Files.getDateString(), true); this._saveAsJson(Files.Jsons.getDated(), notify, callback);
}, },
savePersistent: function() { savePersistent: function() {
this._saveAsJson(Me.metadata['persistent-file-name']); this._saveAsJson(Files.Jsons.getPersistent());
}, },
syncPersistent: function() { syncPersistent: function() {
@ -1154,7 +1224,7 @@ var DrawingArea = new Lang.Class({
}, },
_loadJson: function(name, notify) { _loadJson: function(json, notify) {
// stop drawing or writing // stop drawing or writing
if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.isWriting) { if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.isWriting) {
this._stopWriting(); this._stopWriting();
@ -1164,56 +1234,49 @@ var DrawingArea = new Lang.Class({
this.elements = []; this.elements = [];
this.currentElement = null; this.currentElement = null;
let contents = (new Files.Json({ name })).contents; if (!json.contents)
if (!contents)
return; return;
this.elements.push(...JSON.parse(contents).map(object => { this.elements.push(...JSON.parse(json.contents).map(object => {
if (object.color)
object.color = getClutterColorFromString(object.color, 'white');
if (object.font && typeof object.font == 'string')
object.font = Pango.FontDescription.from_string(object.font);
if (object.image) if (object.image)
object.image = new Files.Image(object.image); object.image = new Files.Image(object.image);
return new Elements.DrawingElement(object); return new Elements.DrawingElement(object);
})); }));
if (notify) if (notify)
this.emit('show-osd', 'document-open-symbolic', name, "", -1, false); this.emit('show-osd', Files.Icons.OPEN, json.name, "", -1, false);
if (name != Me.metadata['persistent-file-name']) { if (!json.isPersistent)
this.jsonName = name; this.currentJson = json;
this.lastJsonContents = contents;
}
}, },
_loadPersistent: function() { _loadPersistent: function() {
this._loadJson(Me.metadata['persistent-file-name']); this._loadJson(Files.Jsons.getPersistent());
}, },
loadJson: function(name, notify) { loadJson: function(json, notify) {
this._loadJson(name, notify); this._loadJson(json, notify);
this._redisplay(); this._redisplay();
}, },
loadNextJson: function() { loadPreviousJson: function() {
let names = Files.getJsons().map(json => json.name); let json = Files.Jsons.getPrevious(this.currentJson || null);
if (json)
if (!names.length) this.loadJson(json, true);
return;
let nextName = names[this.jsonName && names.indexOf(this.jsonName) != names.length - 1 ? names.indexOf(this.jsonName) + 1 : 0];
this.loadJson(nextName, true);
}, },
loadPreviousJson: function() { loadNextJson: function() {
let names = Files.getJsons().map(json => json.name); let json = Files.Jsons.getNext(this.currentJson || null);
if (json)
if (!names.length) this.loadJson(json, true);
return;
let previousName = names[this.jsonName && names.indexOf(this.jsonName) > 0 ? names.indexOf(this.jsonName) - 1 : names.length - 1];
this.loadJson(previousName, true);
}, },
get drawingContentsHasChanged() { get drawingContentsHasChanged() {
let contents = `[\n ` + new Array(...this.elements.map(element => JSON.stringify(element))).join(`,\n\n `) + `\n]`; let contents = `[\n ` + new Array(...this.elements.map(element => JSON.stringify(element))).join(`,\n\n `) + `\n]`;
return contents != this.lastJsonContents; return contents != (this.currentJson && this.currentJson.contents);
} }
}); });

View File

@ -1,151 +0,0 @@
/*
* WARNING : user.css may be obsolete after an extension update.
*
* ~/.local/share/drawOnYourScreen/user.css file is automatically generated by activating "Edit style".
* Delete ~/.local/share/drawOnYourScreen/user.css file to retrieve the default drawing style.
*
* Except for the font, you don't need to restart the extension.
* Just save this file as ~/.local/share/drawOnYourScreen/user.css and the changes will be applied for your next brushstroke.
* Some attributes are modifiable in the user interface.
*
* line-join (no string):
* 0 : miter, 1 : round, 2 : bevel
* line-cap (no string):
* 0 : butt, 1 : round, 2 : square
* fill-rule (no string):
* 0 : nonzero (winding in Cairo), 1 : evenodd
*
* dash:
* By default, it is computed from the line width.
* dash-array-on is the length of dashes (put 0.1 to get dots or squares according to line-cap).
* dash-array-off is the length of gaps.
*
* square area:
* Drawing in a square area is convenient when using the extension as a vector graphics editor. By default,
* when toggling 'Square drawing area', the area is sized to 75% of monitor size. You can fix and customize this size
* by uncommenting square-area-width and square-area-height lines.
*
* font:
* Only one family : no comma separated list of families like "font1, font2, ..., Sans-Serif".
* Font family can be any font installed, or a generic family name (Serif, Sans-Serif, Monospace, Cursive, Fantasy).
* Font weight and font style : no upper case when string.
*
* text-align: left or right.
*
*/
.draw-on-your-screen {
-drawing-line-width: 5px;
-drawing-line-join: 1;
-drawing-line-cap: 1;
-drawing-fill-rule: 0;
/*-drawing-dash-array-on: 5px;*/
/*-drawing-dash-array-off: 15px;*/
/*-drawing-dash-offset: 0px;*/
-drawing-background-color: #2e2e2e;
-grid-overlay-gap: 10px;
-grid-overlay-line-width: 0.4px;
-grid-overlay-interline-width: 0.2px;
-grid-overlay-color: Gray;
/*-drawing-square-area-width: 512px;*/
/*-drawing-square-area-height: 512px;*/
font-family: Cantarell;
font-weight: normal;
font-style: normal;
text-align: left;
}
/* Palette */
.draw-on-your-screen {
-drawing-color1: HotPink;
-drawing-color2: Cyan;
-drawing-color3: yellow;
-drawing-color4: Orangered;
-drawing-color5: Chartreuse;
-drawing-color6: DarkViolet;
-drawing-color7: White;
-drawing-color8: Gray;
-drawing-color9: Black;
}
/*
Example of alternative palettes from GNOME HIG Colors.
https://developer.gnome.org/hig/stable/icon-design.html
The last uncommented palette wins.
*/
/* lighter */
/*
.draw-on-your-screen {
-drawing-color1: rgb(153, 193, 241);
-drawing-color2: rgb(143, 240, 164);
-drawing-color3: rgb(249, 240, 107);
-drawing-color4: rgb(255, 190, 111);
-drawing-color5: rgb(246, 97, 81);
-drawing-color6: rgb(220, 138, 221);
-drawing-color7: rgb(205, 171, 143);
-drawing-color8: rgb(255, 255, 255);
-drawing-color9: rgb(119, 118, 123);
}
*/
/* light */
/*
.draw-on-your-screen {
-drawing-color1: rgb( 98, 160, 241);
-drawing-color2: rgb( 87, 227, 137);
-drawing-color3: rgb(248, 228, 92);
-drawing-color4: rgb(255, 163, 72);
-drawing-color5: rgb(237, 51, 59);
-drawing-color6: rgb(192, 97, 203);
-drawing-color7: rgb(181, 131, 90);
-drawing-color8: rgb(246, 245, 244);
-drawing-color9: rgb( 94, 92, 100);
}
*/
/* normal */
/*
.draw-on-your-screen {
-drawing-color1: rgb( 53, 132, 228);
-drawing-color2: rgb( 51, 209, 122);
-drawing-color3: rgb(246, 211, 45);
-drawing-color4: rgb(255, 120, 0);
-drawing-color5: rgb(224, 27, 36);
-drawing-color6: rgb(145, 65, 172);
-drawing-color7: rgb(152, 106, 68);
-drawing-color8: rgb(222, 221, 218);
-drawing-color9: rgb( 61, 56, 70);
}
*/
/* dark */
/*
.draw-on-your-screen {
-drawing-color1: rgb( 28, 113, 216);
-drawing-color2: rgb( 46, 194, 126);
-drawing-color3: rgb(245, 194, 17);
-drawing-color4: rgb(230, 97, 0);
-drawing-color5: rgb(192, 28, 40);
-drawing-color6: rgb(129, 61, 156);
-drawing-color7: rgb(134, 94, 60);
-drawing-color8: rgb(192, 191, 188);
-drawing-color9: rgb( 36, 31, 49);
}
*/
/* darker */
/*
.draw-on-your-screen {
-drawing-color1: rgb( 26, 095, 180);
-drawing-color2: rgb( 38, 162, 105);
-drawing-color3: rgb(229, 165, 10);
-drawing-color4: rgb(198, 70, 0);
-drawing-color5: rgb(165, 29, 45);
-drawing-color6: rgb( 97, 53, 131);
-drawing-color7: rgb( 99, 69, 44);
-drawing-color8: rgb(154, 153, 150);
-drawing-color9: rgb( 0, 0, 0);
}
*/

View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" version="1.1">
<metadata>
https://github.com/PapirusDevelopmentTeam/papirus-icon-theme/blob/master/Papirus/symbolic/actions/tool-arc-symbolic.svg
https://www.gnu.org/licenses/gpl-3.0.html
</metadata>
<path fill="#474747" d="M 14,1 C 13,1 13,2 13,2 13,2 12.941,4.04 12.701,4.6152 12.204,5.8094 11.214,7 8,7 4.2143,7 2.2036,8.8094 1.4512,10.615 1,11.698 1,14 1,14 1,14 1,15 2,15 3,15 3,14 3,14 3,14 2.9373,12.253 3.2988,11.385 3.7964,10.191 4.7857,9 8,9 11.786,9 13.796,7.1906 14.549,5.3848 15.045,4.1955 15,2 15,2 15,2 15,1 14,1 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 604 B

View File

@ -7,7 +7,7 @@ Created by potrace 1.15, written by Peter Selinger 2001-2017
https://svgsilh.com/image/1745699.html https://svgsilh.com/image/1745699.html
https://creativecommons.org/publicdomain/zero/1.0 https://creativecommons.org/publicdomain/zero/1.0
</metadata> </metadata>
<g fill="#555" stroke="none"> <g fill="#474747">
<path d="M63.3 115.3 c-8.1 -9.8 -16.3 -21.4 -22.0 -31.2 -7.3 -12.5 -11.6 <path d="M63.3 115.3 c-8.1 -9.8 -16.3 -21.4 -22.0 -31.2 -7.3 -12.5 -11.6
-23.5 -12.7 -32.2 -0.7 -5.2 -0.3 -9.1 1.3 -14.3 1.2 -4.0 3.1 -7.8 5.5 -11.5 -23.5 -12.7 -32.2 -0.7 -5.2 -0.3 -9.1 1.3 -14.3 1.2 -4.0 3.1 -7.8 5.5 -11.5
1.6 -2.4 3.0 -4.1 5.1 -6.2 3.1 -3.1 5.7 -5.0 9.3 -6.8 3.8 -1.9 7.5 -3.0 1.6 -2.4 3.0 -4.1 5.1 -6.2 3.1 -3.1 5.7 -5.0 9.3 -6.8 3.8 -1.9 7.5 -3.0

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 851 B

View File

@ -1,5 +1,5 @@
<svg viewBox="0 0 576 576" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 576 576" xmlns="http://www.w3.org/2000/svg">
<rect fill="#555" stroke="transparent" x="228" y="228" width="120" height="120"/> <rect fill="#474747" x="228" y="228" width="120" height="120"/>
<rect fill="#555" stroke="transparent" x="50" y="228" width="120" height="120"/> <rect fill="#474747" x="50" y="228" width="120" height="120"/>
<rect fill="#555" stroke="transparent" x="406" y="228" width="120" height="120"/> <rect fill="#474747" x="406" y="228" width="120" height="120"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 321 B

After

Width:  |  Height:  |  Size: 267 B

View File

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" version="1.1">
<metadata>
https://github.com/PapirusDevelopmentTeam/papirus-icon-theme/blob/master/Papirus/symbolic/actions/document-export-symbolic.svg
https://www.gnu.org/licenses/gpl-3.0.html
</metadata>
<path fill="#474747" d="M 3,15 C 3,15 2,15 2,14 V 11 2 C 2,1 3,1 3,1 H 13 C 13,1 14,1 14,2 V 6.25 L 12,5 V 3.01 H 4 V 13 H 12 V 11 L 14,9.75 V 14 C 14,14 14,15 13,15 Z"/>
<path fill="#474747" d="M 10,11 V 9 H 6 V 7 H 10 V 5 L 14.5,8 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 517 B

View File

@ -1,3 +1,3 @@
<svg viewBox="-50 -130 740 740" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <svg viewBox="-50 -130 740 740" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<ellipse fill="#555" stroke="transparent" cx="320" cy="240" rx="320" ry="240"/> <ellipse fill="#474747" cx="320" cy="240" rx="320" ry="240"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 196 B

After

Width:  |  Height:  |  Size: 178 B

View File

@ -1,3 +1,3 @@
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<polygon fill="#555" stroke="transparent" fill-rule="evenodd" points="100,10 40,190 190,74 10,74 160,190"/> <polygon fill="#474747" fill-rule="evenodd" points="100,10 40,190 190,74 10,74 160,190"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 219 B

After

Width:  |  Height:  |  Size: 201 B

View File

@ -1,3 +1,3 @@
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<polygon fill="#555" stroke="transparent" fill-rule="nonzero" points="100,10 40,190 190,74 10,74 160,190"/> <polygon fill="#474747" fill-rule="nonzero" points="100,10 40,190 190,74 10,74 160,190"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 219 B

After

Width:  |  Height:  |  Size: 201 B

View File

@ -1,3 +1,3 @@
<svg viewBox="0 0 576 576" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 576 576" xmlns="http://www.w3.org/2000/svg">
<rect fill="#555" stroke="transparent" x="50" y="228" width="476" height="120"/> <rect fill="#474747" x="50" y="228" width="476" height="120"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 153 B

After

Width:  |  Height:  |  Size: 135 B

View File

@ -1,4 +1,4 @@
<svg viewBox="0 0 576 576" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 576 576" xmlns="http://www.w3.org/2000/svg">
<rect fill="#555" stroke="transparent" x="50" y="178" width="350" height="220"/> <rect fill="#474747" x="50" y="178" width="350" height="220"/>
<ellipse fill="#555" stroke="transparent" cx="400" cy="288" rx="130" ry="110"/> <ellipse fill="#474747" cx="400" cy="288" rx="130" ry="110"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 235 B

After

Width:  |  Height:  |  Size: 199 B

View File

@ -1,3 +1,3 @@
<svg viewBox="-50 -50 676 676" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="-50 -50 676 676" xmlns="http://www.w3.org/2000/svg">
<path fill="#555" stroke="transparent" d="M -13 518 L288 0 L589 518 L500 576 L288 217 L76 576z"/> <path fill="#474747" d="M -13 518 L288 0 L589 518 L500 576 L288 217 L76 576z"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 174 B

After

Width:  |  Height:  |  Size: 156 B

View File

@ -0,0 +1,32 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<metadata>
Created by potrace 1.15, written by Peter Selinger 2001-2017
https://svgsilh.com/image/2026954.html
https://creativecommons.org/publicdomain/zero/1.0/
</metadata>
<g transform="translate(0,128) scale(1,-1)" fill="#474747">
<path d="M61.4 111.9 c-11.9 -0.8 -22.5 -5.4 -30.9 -13.6 -7.6 -7.5 -12.5
-17 -14 -27.4 -0.4 -2.6 -0.5 -3.8 -0.5 -7 0 -4.4 0.4 -7.6 1.4 -11.7 0.9
-3.5 1.8 -6 3.5 -9.5 1.9 -3.8 3.6 -6.5 6.4 -9.8 1.2 -1.4 4.6 -4.8 6.2
-6.1 7.1 -5.8 15.4 -9.4 24.4 -10.6 3.1 -0.4 6.9 -0.5 8 -0.2 1.8 0.5 3.3 1.4
4.3 2.8 1.3 1.6 1.9 3.5 1.8 5.6 -0.1 2 -0.7 3.3 -2.2 5.1 -0.7 0.8 -1.4 2.2
-1.6 3.2 -0.3 1 -0.3 2.9 0 3.8 0.6 2.4 2.3 4.4 4.5 5.4 1.5 0.7 1.4 0.7 8.9 0.8
3.7 0 6.9 0.1 7 0.1 0.1 0 0.7 0.1 1.3 0.2 5.4 0.9 10.5 3.5 14.5 7.5 3.2 3.3 5.4
6.9 6.7 11.3 1.1 3.6 1.3 8.1 0.7 12.7 -1.9 13.4 -10.7 25.1 -23.8 31.8 -5.8
3 -12 4.7 -18.7 5.4 -1.5 0.2 -6.5 0.3 -7.9 0.2z m-8.4 -10.9 c2.7 -0.9 4.7
-3 5.5 -5.7 0.3 -1.1 0.3 -2.9 0 -4 -0.7 -2.8 -3 -5 -5.8 -5.8 -1.1 -0.3
-2.8 -0.3 -3.9 0 -2.8 0.7 -4.8 2.7 -5.7 5.5 -0.3 0.9 -0.4 2.7 -0.2 3.7 0.5 3.1 3
5.7 6.1 6.4 0.9 0.2 3.1 0.1 4 -0.2z m26 0.2 c3.2 -0.7 5.7 -3.3 6.2 -6.4 0.2
-1 0.1 -2.8 -0.2 -3.7 -0.9 -2.8 -2.9 -4.7 -5.7 -5.5 -1.1 -0.3 -2.8 -0.3 -3.9 0
-2.8 0.7 -5 2.3 -5.8 5.8 -0.3 1.1 -0.3 2.9 0 4 0.8 3 3.2 5.3 6.2 5.9 0.7
0.1 2.4 0.1 3.2 0z m-43.1 -21.3 c1.8 -0.3 3.1 -1 4.5 -2.3 1.3 -1.3 2 -2.7
2.3 -4.6 0.3 -2.5 -0.6 -5 -2.5 -6.7 -1 -0.9 -2 -1.5 -3.4 -1.9 -1 -0.3
-3 -0.3 -4 0 -3.6 1 -6 4.1 -6 7.8 0 1.7 0.4 3.1 1.3 4.4 1.8 2.6 4.9
4 7.9 3.5z m59.6 -0.2 c3.5 -1 5.8 -4.1 5.8 -7.7 0 -1.7 -0.4 -3 -1.3 -4.3
-2.5 -3.9 -7.7 -4.8 -11.5 -2.1 -1.2 0.9 -2.4 2.5 -2.9 4 -0.9 2.7 -0.2 5.8
1.8 7.9 1.2 1.3 2.7 2.1 4.4 2.5 1 0.2 2.7 0.1 3.6 -0.2z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -1,6 +1,6 @@
<svg viewBox="0 0 576 576" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 576 576" xmlns="http://www.w3.org/2000/svg">
<rect fill="#555" stroke="none" x="99.06" y="116.46" width="426.79" height="88.9" transform="translate(-1.13,-26.30) translate(297.56,177.87) rotate(-17.00) translate(-297.56,-177.87) translate(-14.89,16.96)"/> <rect fill="#474747" x="99.06" y="116.46" width="426.79" height="88.9" transform="translate(296.43,151.57) rotate(-17.00) translate(-312.45,-160.91)"/>
<rect fill="#555" stroke="none" x="382.92" y="219.17" width="137.39" height="55" transform="translate(-7.43,-27.23) translate(449.62,218.30) rotate(-16.48) translate(-449.62,-218.30) translate(-2,-19.20)"/> <rect fill="#474747" x="382.92" y="219.17" width="137.39" height="55" transform="translate(442.19,191.07) rotate(-16.48) translate(-451.62,-237.50)"/>
<rect fill="#555" stroke="none" x="99.06" y="116.46" width="426.79" height="88.9" transform="translate(0, 284.75) rotate(180) scale(1, -1) rotate(-180) translate(0, -284.75) translate(-1.13,-26.30) translate(297.56,177.87) rotate(-17.00) translate(-297.56,-177.87) translate(-14.89,16.96)"/> <rect fill="#474747" x="99.06" y="116.46" width="426.79" height="88.9" transform="translate(0, 284.75) rotate(180) scale(1, -1) rotate(-180) translate(296.43,-133.18) rotate(-17.00) translate(-312.45,-160.91)"/>
<rect fill="#555" stroke="none" x="382.92" y="219.17" width="137.39" height="55" transform="translate(0, 284.02) rotate(180) scale(1, -1) rotate(-180) translate(0, -284.02) translate(-7.43,-27.23) translate(449.62,218.30) rotate(-16.48) translate(-449.62,-218.30) translate(-2,-19.20)"/> <rect fill="#474747" x="382.92" y="219.17" width="137.39" height="55" transform="translate(0, 284.02) rotate(180) scale(1, -1) rotate(-180) translate(442.19,-92.95) rotate(-16.48) translate(-451.62,-237.50)"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 803 B

View File

@ -1,3 +1,3 @@
<svg viewBox="-50 -130 740 740" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> <svg viewBox="-50 -130 740 740" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<path stroke="transparent" fill="#555" d="m4,239.99861l0,0c0,-130.70635 141.47749,-236.66527 315.99786,-236.66527l0,0c83.80939,0 164.18481,24.93385 223.44586,69.31971c59.2608,44.38261 92.55627,104.57763 92.55627,167.34556l0,0c0,130.70897 -141.47833,236.66808 -316.00214,236.66808l0,0c-174.52037,0 -315.99786,-105.95911 -315.99786,-236.66808zm158.00044,0l0,0c0,65.35454 70.73923,118.33434 157.99742,118.33434c87.26294,0 158.00085,-52.9798 158.00085,-118.33434c0,-65.35178 -70.73792,-118.33152 -158.00085,-118.33152l0,0c-87.25819,0 -157.99742,52.97974 -157.99742,118.33152z"/> <path fill="#474747" d="m4,239.99861l0,0c0,-130.70635 141.47749,-236.66527 315.99786,-236.66527l0,0c83.80939,0 164.18481,24.93385 223.44586,69.31971c59.2608,44.38261 92.55627,104.57763 92.55627,167.34556l0,0c0,130.70897 -141.47833,236.66808 -316.00214,236.66808l0,0c-174.52037,0 -315.99786,-105.95911 -315.99786,-236.66808zm158.00044,0l0,0c0,65.35454 70.73923,118.33434 157.99742,118.33434c87.26294,0 158.00085,-52.9798 158.00085,-118.33434c0,-65.35178 -70.73792,-118.33152 -158.00085,-118.33152l0,0c-87.25819,0 -157.99742,52.97974 -157.99742,118.33152z"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 691 B

After

Width:  |  Height:  |  Size: 673 B

View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" version="1.1">
<metadata>
https://github.com/PapirusDevelopmentTeam/papirus-icon-theme/blob/master/Papirus/symbolic/actions/tool-circle-move-symbolic.svg
https://www.gnu.org/licenses/gpl-3.0.html
</metadata>
<path fill="#474747" d="M 8,1 A 7,7 0 0 0 1,8 7,7 0 0 0 8,15 7,7 0 0 0 15,8 7,7 0 0 0 8,1 Z M 8,3 A 5,5 0 0 1 13,8 5,5 0 0 1 8,13 5,5 0 0 1 3,8 5,5 0 0 1 8,3 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 442 B

View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" version="1.1">
<metadata>
https://github.com/PapirusDevelopmentTeam/papirus-icon-theme/blob/master/Papirus/symbolic/actions/tool-line-symbolic.svg
https://www.gnu.org/licenses/gpl-3.0.html
</metadata>
<path fill="#474747" d="M 12.778,13.778 C 12.778,13.778 13.278,14.278 13.778,13.778 14.278,13.278 13.778,12.778 13.778,12.778 L 3.2237,2.2237 C 3.2237,2.2237 2.7237,1.7237 2.2237,2.2237 1.7237,2.7237 2.2237,3.2237 2.2237,3.2237 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 505 B

View File

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" version="1.1">
<metadata>
https://github.com/PapirusDevelopmentTeam/papirus-icon-theme/blob/master/Papirus/symbolic/actions/view-mirror-symbolic.svg
https://www.gnu.org/licenses/gpl-3.0.html
</metadata>
<path fill="#474747" d="M 1.8613,0 C 1.0809,0 0,0.69182 0,1.5762 V 14.387 C 0,15.271 1.0809,16 1.8613,16 L 5.752,13.053 C 6.3603,12.597 7,12.324 7,11.439 V 4.5254 C 7,3.6409 6.188,3.303 5.752,2.9121 Z M 2,1.3535 5.8516,4.2598 C 5.8516,4.2598 6,4.328 6,4.5254 V 11.439 C 6,11.699 5.8984,11.699 5.8984,11.699 L 2,14.641 Z"/>
<path fill="#474747" opacity="0.3" d="M 13.139,0 C 13.919,0 15,0.69182 15,1.5762 V 14.387 C 15,15.271 13.919,16 13.139,16 L 9.248,13.053 C 8.6397,12.597 8,12.324 8,11.439 V 4.5254 C 8,3.6409 8.812,3.303 9.248,2.9121 Z M 13,1.3535 9.1484,4.2598 C 9.1484,4.2598 9,4.328 9,4.5254 V 11.439 C 9,11.699 9.1016,11.699 9.1016,11.699 L 13,14.641 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 940 B

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16">
<metadata>
Combination of https://github.com/PapirusDevelopmentTeam/papirus-icon-theme/blob/master/Papirus/symbolic/actions/object-move-symbolic.svg
https://www.gnu.org/licenses/gpl-3.0.html
and https://gitlab.gnome.org/World/design/icon-library/-/blob/master/data/resources/icon-dev-kit/rotate-symbolic.svg
https://www.gnu.org/licenses/gpl-3.0.html
optimized with SVGO
</metadata>
<path d="M8 15.5L5 12h2v-2h2v2h2zM.5 8L4 5v2h2v2H4v2z" fill="#474747"/>
<a transform="rotate(-4.342 5.603 3.916)">
<path fill="#474747" d="M8.672.954l-3.654 3.2c.634.717 1.341 1.557 1.92 2.193l1.28 1.46.166-2.484.022-.02c.769-.196 1.74.097 2.457.915 1.102 1.259.67 2.758-.634 3.9l.57.674c.346.407 1.058.4 1.396-.012 1.65-1.444 1.556-4.028.037-5.763-.986-1.124-2.398-1.707-3.725-1.579z"/>
</a>
</svg>

After

Width:  |  Height:  |  Size: 903 B

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 920.729 920.729">
<metadata>
Combination of https://www.svgrepo.com/svg/150374/pencil
https://creativecommons.org/publicdomain/zero/1.0/deed.en
and https://www.svgrepo.com/svg/29291/pencil
https://creativecommons.org/publicdomain/zero/1.0/deed.en
</metadata>
<g fill="#474747">
<path d="m 823.25922,841.48926 c -1.399,0 -2.699,0.1 -4.1,0.3 l -245.7,31.8 c -12.6,1.601 -21.399,-12.2 -14.6,-23 3.7,-5.7 7.3,-11.5 10.899,-17.3 9.5,-15.4 -3.199,-35.9 -20.8,-35.9 -1.2,0 -2.399,0.101 -3.7,0.301 l -322,48.399 -38.5,5.8 -104.999997,15.801 c -11.9,1.8 -19.9,11.899 -18.9,24 0.4,5.399 2.6,10.5 6.1,14.3 4.1,4.5 9.8,7 16,7 1.2,0 2.4,-0.101 3.7,-0.3 l 383.799997,-57.7 c 13.101,-2 22.101,12.8 14.4,23.6 -2.5,3.4 -5,6.9 -7.5,10.3 -6.9,9.4 -5.3,25.101 2.7,33.2 4.1,4.2 9.3,6.4 14.8,6.4 0.6,0 1.3,0 1.899,-0.101 h 0.101 0.1 l 331.3,-42.199 h 0.101 0.1 c 5.7,-0.9 10.7,-4.2 14,-9.301 3.601,-5.6 4.9,-12.5 3.3,-18.699 -2.6,-10.101 -11.4,-16.701 -22.5,-16.701 z"/>
<g transform="translate(58.371053,4.3729293)">
<path d="m 515.582,157.916 -439.199,439.2 4.6,10.1 27.1,58.8 41.6,22 c 9.8,5.2 17.7,13.101 22.9,22.9 l 22,41.6 58.8,27.101 10.101,4.6 439.2,-439.2 z"/>
<path d="m 853.282,159.216 -151.8,-151.8 c -4.9,-4.9 -11.3,-7.3 -17.7,-7.3 -6.4,0 -12.8,2.4 -17.7,7.3 l -129.3,129.3 187.2,187.2 129.3,-129.3 c 9.8,-9.8 9.8,-25.6 0,-35.4 z"/>
<path d="m 46.083,650.016 -4.3,16.9 -41,162.5 c -4.1,16.2 8.5,31.1 24.1,31.1 2,0 4.1,-0.3 6.2,-0.8 l 162.5,-41 16.9,-4.3 16.9,-4.3 13.3,-3.4 -30.9,-14.2 -29.5,-13.5 c -5,-2.3 -9.1,-6.199 -11.7,-11 l -18.6,-35.1 -4.3,-8 c -2.3,-4.4 -6,-8.1 -10.4,-10.4 l -8,-4.3 -35.1,-18.6 c -4.9,-2.601 -8.7,-6.7 -11,-11.7 l -13.5,-29.5 -14.2,-30.9 -3.4,13.301 z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" version="1.1">
<metadata>
https://github.com/PapirusDevelopmentTeam/papirus-icon-theme/blob/master/Papirus/symbolic/actions/tool-polygon-symbolic.svg
https://www.gnu.org/licenses/gpl-3.0.html
</metadata>
<path fill="#474747" d="M 12.98,2.5 1.9551,3.502 1.5,4 V 14 L 2.3203,14.385 8.0684,9.5938 13.777,12.447 14.496,11.945 13.496,2.9453 Z M 12.557,3.543 13.402,11.143 8.2227,8.5527 7.6797,8.6152 2.5,12.932 V 4.457 Z"/>
<path fill="#474747" d="M 10,9 A 2,2 0 0 1 8,11 2,2 0 0 1 6,9 2,2 0 0 1 8,7 2,2 0 0 1 10,9 Z M 4,4 A 2,2 0 0 1 2,6 2,2 0 0 1 0,4 2,2 0 0 1 2,2 2,2 0 0 1 4,4 Z M 4,14 A 2,2 0 0 1 2,16 2,2 0 0 1 0,14 2,2 0 0 1 2,12 2,2 0 0 1 4,14 Z M 16,12 A 2,2 0 0 1 14,14 2,2 0 0 1 12,12 2,2 0 0 1 14,10 2,2 0 0 1 16,12 Z M 15,3 A 2,2 0 0 1 13,5 2,2 0 0 1 11,3 2,2 0 0 1 13,1 2,2 0 0 1 15,3 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 871 B

View File

@ -0,0 +1,9 @@
<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<rect fill="#474747" x="98" y="204" width="226" height="40" transform="translate(116,253) rotate(-66) translate(-211,-226)"/>
<rect fill="#474747" x="145" y="339" width="270" height="40" transform="translate(236,262) rotate(58) translate(-280,-362)"/>
<rect fill="#474747" x="141" y="429" width="211" height="40" transform="translate(374,300) rotate(-47) translate(-246,-451)"/>
<circle fill="#474747" cx="166" cy="172" r="55"/>
<circle fill="#474747" cx="166" cy="172" r="55" transform="translate(143,190)"/>
<circle fill="#474747" cx="166" cy="172" r="55" transform="translate(-87,168)"/>
<circle fill="#474747" cx="166" cy="172" r="55" transform="translate(273,56)"/>
</svg>

After

Width:  |  Height:  |  Size: 755 B

View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" version="1.1">
<metadata>
https://github.com/PapirusDevelopmentTeam/papirus-icon-theme/blob/master/Papirus/symbolic/actions/tool-rectangle-symbolic.svg
https://www.gnu.org/licenses/gpl-3.0.html
</metadata>
<path fill="#474747" d="M 1,1 V 15 H 15 V 1 Z M 3,3 H 13 V 13 H 3 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 348 B

View File

@ -0,0 +1,15 @@
<svg width="16" height="16">
<metadata>
https://gitlab.gnome.org/GNOME/adwaita-icon-theme/-/blob/master/Adwaita/scalable/actions/view-fullscreen-symbolic.svg
https://creativecommons.org/licenses/by-sa/3.0/
</metadata>
<g fill="#474747">
<path d="M1.984 8.986A1 1 0 0 0 1 10v4a1 1 0 0 0 1 1h4a1 1 0 1 0 0-2H3v-3a1 1 0 0 0-1.016-1.014z" />
<path d="M6.48 8.49a1 1 0 0 0-.687.303l-4.5 4.5a1 1 0 1 0 1.414 1.414l4.5-4.5A1 1 0 0 0 6.48 8.49z" />
<path d="M1 14h1v1H1z" />
<path d="M10 1a1 1 0 1 0 0 2h3v3a1 1 0 1 0 2 0V2a1 1 0 0 0-1-1z" />
<path d="M14 1h1v1h-1z" />
<path d="M13.984.99a1 1 0 0 0-.69.301l-4.5 4.469a1 1 0 1 0 1.411 1.418l4.5-4.469a1 1 0 0 0-.72-1.719z" />
<path d="M1 9h1v1H1zM6 14h1v1H6zM14 6h1v1h-1zM9 1h1v1H9z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 773 B

View File

@ -1,4 +1,5 @@
/* jslint esversion: 6 */ /* jslint esversion: 6 */
/* exported Shapes, Transformations, getAllFontFamilies, DrawingElement */
/* /*
* Copyright 2019 Abakkk * Copyright 2019 Abakkk
@ -26,28 +27,27 @@ const Lang = imports.lang;
const Pango = imports.gi.Pango; const Pango = imports.gi.Pango;
const PangoCairo = imports.gi.PangoCairo; const PangoCairo = imports.gi.PangoCairo;
const reverseEnumeration = function(obj) { var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6, IMAGE: 7 };
let reversed = {}; var Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, STRETCH: 3, REFLECTION: 4, INVERSION: 5 };
Object.keys(obj).forEach(key => {
reversed[obj[key]] = key.slice(0,1) + key.slice(1).toLowerCase().replace('_', '-'); var getAllFontFamilies = function() {
}); return PangoCairo.font_map_get_default().list_families().map(fontFamily => fontFamily.get_name()).sort((a,b) => a.localeCompare(b));
return reversed;
}; };
var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6, IMAGE: 7 }; const getFillRuleSvgName = function(fillRule) {
var ShapeNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline", 7: "Image" }; return fillRule == Cairo.FillRule.EVEN_ODD ? 'evenodd' : 'nonzero';
var Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, STRETCH: 3, REFLECTION: 4, INVERSION: 5 }; };
var LineCapNames = Object.assign(reverseEnumeration(Cairo.LineCap), { 2: 'Square' });
var LineJoinNames = reverseEnumeration(Cairo.LineJoin);
var FillRuleNames = { 0: 'Nonzero', 1: 'Evenodd' };
var FontWeightNames = Object.assign(reverseEnumeration(Pango.Weight), { 200: "Ultra-light", 350: "Semi-light", 600: "Semi-bold", 800: "Ultra-bold" });
delete FontWeightNames[Pango.Weight.ULTRAHEAVY];
var FontStyleNames = reverseEnumeration(Pango.Style);
var FontStretchNames = reverseEnumeration(Pango.Stretch);
var FontVariantNames = reverseEnumeration(Pango.Variant);
var getPangoFontFamilies = function() { const getLineCapSvgName = function(lineCap) {
return PangoCairo.font_map_get_default().list_families().map(fontFamily => fontFamily.get_name()).sort((a,b) => a.localeCompare(b)); return lineCap == Cairo.LineCap.BUTT ? 'butt' :
lineCap == Cairo.LineCap.SQUASH ? 'square' :
'round';
};
const getLineJoinSvgName = function(lineJoin) {
return lineJoin == Cairo.LineJoin.MITER ? 'miter' :
lineJoin == Cairo.LineJoin.BEVEL ? 'bevel' :
'round';
}; };
const SVG_DEBUG_SUPERPOSES_CAIRO = false; const SVG_DEBUG_SUPERPOSES_CAIRO = false;
@ -80,10 +80,21 @@ const _DrawingElement = new Lang.Class({
if (params.transformations === undefined) if (params.transformations === undefined)
this.transformations = []; this.transformations = [];
if (params.font && params.font.weight === 0)
this.font.weight = 400; if (params.font && !(params.font instanceof Pango.FontDescription)) {
if (params.font && params.font.weight === 1) // compatibility with v6.2-
this.font.weight = 700; if (params.font.weight === 0)
this.font.weight = 400;
else if (params.font.weight === 1)
this.font.weight = 700;
this.font = new Pango.FontDescription();
['family', 'weight', 'style', 'stretch', 'variant'].forEach(attribute => {
if (params.font[attribute] !== undefined)
try {
this.font[`set_${attribute}`](params.font[attribute]);
} catch(e) {}
});
}
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);
@ -102,7 +113,7 @@ const _DrawingElement = new Lang.Class({
toJSON: function() { toJSON: function() {
return { return {
shape: this.shape, shape: this.shape,
color: this.color, color: this.color.toString(),
line: this.line, line: this.line,
dash: this.dash, dash: this.dash,
fill: this.fill, fill: this.fill,
@ -114,11 +125,8 @@ const _DrawingElement = new Lang.Class({
}, },
buildCairo: function(cr, params) { buildCairo: function(cr, params) {
if (this.color) { if (this.color)
let [success, color] = Clutter.Color.from_string(this.color); Clutter.cairo_set_source_color(cr, this.color);
if (success)
Clutter.cairo_set_source_color(cr, color);
}
if (this.showSymmetryElement) { if (this.showSymmetryElement) {
let transformation = this.lastTransformation; let transformation = this.lastTransformation;
@ -244,63 +252,89 @@ const _DrawingElement = new Lang.Class({
return inElement; return inElement;
}, },
buildSVG: function(bgColor) { buildSVG: function(bgcolorString) {
let transAttribute = ''; let transforms = [];
this.transformations.slice(0).reverse().forEach(transformation => { this.transformations.slice(0).reverse().forEach(transformation => {
transAttribute += transAttribute ? ' ' : ' transform="';
let center = this._getTransformedCenter(transformation); let center = this._getTransformedCenter(transformation);
if (transformation.type == Transformations.TRANSLATION) { if (transformation.type == Transformations.TRANSLATION) {
transAttribute += `translate(${transformation.slideX},${transformation.slideY})`; transforms.push(['translate', transformation.slideX, transformation.slideY]);
} else if (transformation.type == Transformations.ROTATION) { } else if (transformation.type == Transformations.ROTATION) {
transAttribute += `translate(${center[0]},${center[1]}) `; transforms.push(['translate', center[0], center[1]]);
transAttribute += `rotate(${transformation.angle * RADIAN}) `; transforms.push(['rotate', transformation.angle * RADIAN]);
transAttribute += `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 == Transformations.SCALE_PRESERVE || transformation.type == Transformations.STRETCH) {
transAttribute += `translate(${center[0]},${center[1]}) `; transforms.push(['translate', center[0], center[1]]);
transAttribute += `rotate(${transformation.angle * RADIAN}) `; transforms.push(['rotate', transformation.angle * RADIAN]);
transAttribute += `scale(${transformation.scaleX},${transformation.scaleY}) `; transforms.push(['scale', transformation.scaleX, transformation.scaleY]);
transAttribute += `rotate(${-transformation.angle * RADIAN}) `; transforms.push(['rotate', -transformation.angle * RADIAN]);
transAttribute += `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 == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) {
transAttribute += `translate(${transformation.slideX}, ${transformation.slideY}) `; transforms.push(['translate', transformation.slideX, transformation.slideY]);
transAttribute += `rotate(${transformation.angle * RADIAN}) `; transforms.push(['rotate', transformation.angle * RADIAN]);
transAttribute += `scale(${transformation.scaleX}, ${transformation.scaleY}) `; transforms.push(['scale', transformation.scaleX, transformation.scaleY]);
transAttribute += `rotate(${-transformation.angle * RADIAN}) `; transforms.push(['rotate', -transformation.angle * RADIAN]);
transAttribute += `translate(${-transformation.slideX}, ${-transformation.slideY})`; transforms.push(['translate', -transformation.slideX, -transformation.slideY]);
} }
}); });
transAttribute += transAttribute ? '"' : '';
let grouped = [];
transforms.forEach((transform, index) => {
let [type, ...values] = transform;
if (grouped.length && grouped[grouped.length - 1][0] == type)
values.forEach((value, valueIndex) => grouped[grouped.length - 1][valueIndex + 1] += value);
else
grouped.push(transform);
});
return this._drawSvg(transAttribute); let filtered = grouped.filter(transform => {
let [type, ...values] = transform;
if (type == 'scale')
return values.some(value => value != 1);
else
return values.some(value => value != 0);
});
let transAttribute = '';
if (filtered.length) {
transAttribute = ' transform="';
filtered.forEach((transform, index) => {
let [type, ...values] = transform;
transAttribute += `${index == 0 ? '' : ' '}${type}(${values.map(value => Number(value).toFixed(2))})`;
});
transAttribute += '"';
}
return this._drawSvg(transAttribute, bgcolorString);
}, },
_drawSvg: function(transAttribute) { _drawSvg: function(transAttribute, bgcolorString) {
let row = "\n "; let row = "\n ";
let points = this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100]); let points = this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100]);
let color = this.eraser ? bgColor : this.color; let color = this.eraser ? bgcolorString : this.color.toString();
let fill = this.fill && !this.isStraightLine; let fill = this.fill && !this.isStraightLine;
let attributes = ''; let attributes = this.eraser ? `class="eraser" ` : '';
if (fill) { if (fill) {
attributes = `fill="${color}"`; attributes += `fill="${color}"`;
if (this.fillRule) if (this.fillRule)
attributes += ` fill-rule="${FillRuleNames[this.fillRule].toLowerCase()}"`; attributes += ` fill-rule="${getFillRuleSvgName(this.fillRule)}"`;
} else { } else {
attributes = `fill="none"`; attributes += `fill="none"`;
} }
if (this.line && this.line.lineWidth) { if (this.line && this.line.lineWidth) {
attributes += ` stroke="${color}"` + attributes += ` stroke="${color}"` +
` stroke-width="${this.line.lineWidth}"`; ` stroke-width="${this.line.lineWidth}"`;
if (this.line.lineCap) if (this.line.lineCap)
attributes += ` stroke-linecap="${LineCapNames[this.line.lineCap].toLowerCase()}"`; attributes += ` stroke-linecap="${getLineCapSvgName(this.line.lineCap)}"`;
if (this.line.lineJoin && !this.isStraightLine) if (this.line.lineJoin && !this.isStraightLine)
attributes += ` stroke-linejoin="${LineJoinNames[this.line.lineJoin].toLowerCase()}"`; attributes += ` stroke-linejoin="${getLineJoinSvgName(this.line.lineJoin)}"`;
if (this.dash && this.dash.active && this.dash.array && this.dash.array[0] && this.dash.array[1]) if (this.dash && this.dash.active && this.dash.array && this.dash.array[0] && this.dash.array[1])
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}"`;
} else {
attributes += ` stroke="none"`;
} }
if (this.shape == Shapes.LINE && points.length == 4) { if (this.shape == Shapes.LINE && points.length == 4) {
@ -594,15 +628,18 @@ const TextElement = new Lang.Class({
Extends: _DrawingElement, Extends: _DrawingElement,
toJSON: function() { toJSON: function() {
// The font size is useless because it is always computed from the points during cairo/svg building.
this.font.unset_fields(Pango.FontMask.SIZE);
return { return {
shape: this.shape, shape: this.shape,
color: this.color, color: this.color.toString(),
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, lineIndex: this.lineIndex !== undefined ? this.lineIndex : undefined,
textRightAligned: this.textRightAligned, textRightAligned: this.textRightAligned,
font: this.font, 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])
}; };
}, },
@ -629,15 +666,8 @@ const TextElement = new Lang.Class({
if (this.points.length == 2) { if (this.points.length == 2) {
let layout = PangoCairo.create_layout(cr); let layout = PangoCairo.create_layout(cr);
let fontSize = this.height * Pango.SCALE; let fontSize = this.height * Pango.SCALE;
let fontDescription = new Pango.FontDescription(); this.font.set_absolute_size(fontSize);
fontDescription.set_absolute_size(fontSize); layout.set_font_description(this.font);
['family', 'weight', 'style', 'stretch', 'variant'].forEach(attribute => {
if (this.font[attribute] !== undefined)
try {
fontDescription[`set_${attribute}`](this.font[attribute]);
} catch(e) {}
});
layout.set_font_description(fontDescription);
layout.set_text(this.text, -1); layout.set_text(this.text, -1);
this.textWidth = layout.get_pixel_size()[0]; this.textWidth = layout.get_pixel_size()[0];
cr.moveTo(this.x, this.y - layout.get_baseline() / Pango.SCALE); cr.moveTo(this.x, this.y - layout.get_baseline() / Pango.SCALE);
@ -670,29 +700,29 @@ const TextElement = new Lang.Class({
return cr.inFill(x, y); return cr.inFill(x, y);
}, },
_drawSvg: function(transAttribute) { _drawSvg: function(transAttribute, bgcolorString) {
let row = "\n "; let row = "\n ";
let [x, y, height] = [Math.round(this.x*100)/100, Math.round(this.y*100)/100, Math.round(this.height*100)/100]; let [x, y, height] = [Math.round(this.x*100)/100, Math.round(this.y*100)/100, Math.round(this.height*100)/100];
let color = this.eraser ? bgColor : this.color; let color = this.eraser ? bgcolorString : this.color.toString();
let attributes = ''; let attributes = this.eraser ? `class="eraser" ` : '';
if (this.points.length == 2) { if (this.points.length == 2) {
attributes = `fill="${color}" ` + attributes += `fill="${color}" ` +
`stroke="transparent" ` + `font-size="${height}" ` +
`stroke-opacity="0" ` + `font-family="${this.font.get_family()}"`;
`font-size="${height}"`;
if (this.font.family)
attributes += ` font-family="${this.font.family}"`;
if (this.font.weight && this.font.weight != Pango.Weight.NORMAL)
attributes += ` font-weight="${this.font.weight}"`;
if (this.font.style && FontStyleNames[this.font.style])
attributes += ` font-style="${FontStyleNames[this.font.style].toLowerCase()}"`;
if (FontStretchNames[this.font.stretch] && this.font.stretch != Pango.Stretch.NORMAL)
attributes += ` font-stretch="${FontStretchNames[this.font.stretch].toLowerCase()}"`;
if (this.font.variant && FontVariantNames[this.font.variant])
attributes += ` font-variant="${FontVariantNames[this.font.variant].toLowerCase()}"`;
// this.font.to_string() is not valid to fill the svg 'font' shorthand property.
// Each property must be filled separately.
['Stretch', 'Style', 'Variant'].forEach(attribute => {
let lower = attribute.toLowerCase();
if (this.font[`get_${lower}`]() != Pango[attribute].NORMAL) {
let font = new Pango.FontDescription();
font[`set_${lower}`](this.font[`get_${lower}`]());
attributes += ` font-${lower}="${font.to_string()}"`;
}
});
if (this.font.get_weight() != Pango.Weight.NORMAL)
attributes += ` font-weight="${this.font.get_weight()}"`;
row += `<text ${attributes} x="${x}" `; row += `<text ${attributes} x="${x}" `;
row += `y="${y}"${transAttribute}>${this.text}</text>`; row += `y="${y}"${transAttribute}>${this.text}</text>`;
} }
@ -745,7 +775,7 @@ const ImageElement = new Lang.Class({
toJSON: function() { toJSON: function() {
return { return {
shape: this.shape, shape: this.shape,
color: this.color, color: this.color.toString(),
fill: this.fill, fill: this.fill,
eraser: this.eraser, eraser: this.eraser,
transformations: this.transformations, transformations: this.transformations,
@ -789,10 +819,10 @@ const ImageElement = new Lang.Class({
_drawSvg: function(transAttribute) { _drawSvg: function(transAttribute) {
let points = this.points; let points = this.points;
let row = "\n "; let row = "\n ";
let attributes = ''; let attributes = this.eraser ? `class="eraser" ` : '';
if (points.length == 2) { if (points.length == 2) {
attributes = `fill="none"`; attributes += `fill="none"`;
row += `<image ${attributes} x="${Math.min(points[0][0], points[1][0])}" y="${Math.min(points[0][1], points[1][1])}" ` + row += `<image ${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} ` +
`preserveAspectRatio="${this.preserveAspectRatio ? 'xMinYMin' : 'none'}" ` + `preserveAspectRatio="${this.preserveAspectRatio ? 'xMinYMin' : 'none'}" ` +

View File

@ -1,4 +1,5 @@
/* jslint esversion: 6 */ /* jslint esversion: 6 */
/* exported init */
/* /*
* Copyright 2019 Abakkk * Copyright 2019 Abakkk
@ -20,22 +21,21 @@
* 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 Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Lang = imports.lang; const Lang = imports.lang;
const Meta = imports.gi.Meta; const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const St = imports.gi.St; const St = imports.gi.St;
const Config = imports.misc.config; const Config = imports.misc.config;
const ExtensionUtils = imports.misc.extensionUtils;
const Main = imports.ui.main; const Main = imports.ui.main;
const OsdWindow = imports.ui.osdWindow; const OsdWindow = imports.ui.osdWindow;
const PanelMenu = imports.ui.panelMenu; const PanelMenu = imports.ui.panelMenu;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension(); const Me = ExtensionUtils.getCurrentExtension();
const Convenience = ExtensionUtils.getSettings && ExtensionUtils.initTranslations ? ExtensionUtils : Me.imports.convenience; const Convenience = ExtensionUtils.getSettings && ExtensionUtils.initTranslations ? ExtensionUtils : Me.imports.convenience;
const Area = Me.imports.area; const Area = Me.imports.area;
const Files = Me.imports.files;
const Helper = Me.imports.helper; const Helper = Me.imports.helper;
const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext;
@ -46,102 +46,114 @@ const HIDE_TIMEOUT_LONG = 2500; // ms, default is 1500 ms
const DRAWING_ACTION_MODE = Math.pow(2,14); const DRAWING_ACTION_MODE = Math.pow(2,14);
const WRITING_ACTION_MODE = Math.pow(2,15); const WRITING_ACTION_MODE = Math.pow(2,15);
// 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)
var WARNING_COLOR_STYLE_CLASS_NAME = 'login-dialog-message-warning'; const WARNING_COLOR_STYLE_CLASS_NAME = 'login-dialog-message-warning';
var manager;
function init() { function init() {
Convenience.initTranslations(); return new Extension();
} }
function enable() { const Extension = new Lang.Class({
manager = new AreaManager(); Name: 'DrawOnYourScreenExtension',
}
_init: function() {
Convenience.initTranslations();
},
function disable() { enable() {
manager.disable(); if (ExtensionUtils.isOutOfDate(Me))
manager = null; log(`${Me.metadata.uuid}: GNOME Shell ${Number.parseFloat(GS_VERSION)} is not supported.`);
}
Me.settings = Convenience.getSettings();
Me.internalShortcutSettings = Convenience.getSettings(Me.metadata['settings-schema'] + '.internal-shortcuts');
Me.drawingSettings = Convenience.getSettings(Me.metadata['settings-schema'] + '.drawing');
this.areaManager = new AreaManager();
},
disable() {
this.areaManager.disable();
delete this.areaManager;
delete Me.settings;
delete Me.internalShortcutSettings;
}
});
// AreaManager assigns one DrawingArea per monitor (updateAreas()), // AreaManager assigns one DrawingArea per monitor (updateAreas()),
// distributes keybinding callbacks to the active area // distributes keybinding callbacks to the active area
// and handles stylesheet and monitor changes. // and handles stylesheet and monitor changes.
var AreaManager = new Lang.Class({ const AreaManager = new Lang.Class({
Name: 'DrawOnYourScreenAreaManager', Name: 'DrawOnYourScreenAreaManager',
_init: function() { _init: function() {
this.settings = Convenience.getSettings();
this.areas = []; this.areas = [];
this.activeArea = null; this.activeArea = null;
this.enterGicon = new Gio.ThemedIcon({ name: 'applications-graphics-symbolic' });
this.leaveGicon = new Gio.ThemedIcon({ name: 'application-exit-symbolic' });
Main.wm.addKeybinding('toggle-drawing', Main.wm.addKeybinding('toggle-drawing',
this.settings, Me.settings,
Meta.KeyBindingFlags.NONE, Meta.KeyBindingFlags.NONE,
Shell.ActionMode.ALL, Shell.ActionMode.ALL,
this.toggleDrawing.bind(this)); this.toggleDrawing.bind(this));
Main.wm.addKeybinding('toggle-modal', Main.wm.addKeybinding('toggle-modal',
this.settings, Me.settings,
Meta.KeyBindingFlags.NONE, Meta.KeyBindingFlags.NONE,
Shell.ActionMode.ALL, Shell.ActionMode.ALL,
this.toggleModal.bind(this)); this.toggleModal.bind(this));
Main.wm.addKeybinding('erase-drawing', Main.wm.addKeybinding('erase-drawings',
this.settings, Me.settings,
Meta.KeyBindingFlags.NONE, Meta.KeyBindingFlags.NONE,
Shell.ActionMode.ALL, Shell.ActionMode.ALL,
this.eraseDrawing.bind(this)); this.eraseDrawings.bind(this));
this.updateAreas(); this.updateAreas();
this.monitorChangedHandler = Main.layoutManager.connect('monitors-changed', this.updateAreas.bind(this)); this.monitorChangedHandler = Main.layoutManager.connect('monitors-changed', this.updateAreas.bind(this));
this.updateIndicator(); this.updateIndicator();
this.indicatorSettingHandler = this.settings.connect('changed::indicator-disabled', this.updateIndicator.bind(this)); this.indicatorSettingHandler = Me.settings.connect('changed::indicator-disabled', this.updateIndicator.bind(this));
this.desktopSettingHandler = this.settings.connect('changed::drawing-on-desktop', this.onDesktopSettingChanged.bind(this)); this.desktopSettingHandler = Me.settings.connect('changed::drawing-on-desktop', this.onDesktopSettingChanged.bind(this));
this.persistentSettingHandler = this.settings.connect('changed::persistent-drawing', this.onPersistentSettingChanged.bind(this)); this.persistentOverRestartsSettingHandler = Me.settings.connect('changed::persistent-over-restarts', this.onPersistentOverRestartsSettingChanged.bind(this));
this.persistentOverTogglesSettingHandler = Me.settings.connect('changed::persistent-over-toggles', this.onPersistentOverTogglesSettingChanged.bind(this));
this.userStyleFile = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir'], 'user.css'])); },
if (this.userStyleFile.query_exists(null)) { get persistentOverToggles() {
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme(); return Me.settings.get_boolean('persistent-over-toggles');
theme.load_stylesheet(this.userStyleFile); },
}
get persistentOverRestarts() {
this.userStyleMonitor = this.userStyleFile.monitor_file(Gio.FileMonitorFlags.WATCH_MOVES, null); return Me.settings.get_boolean('persistent-over-toggles') && Me.settings.get_boolean('persistent-over-restarts');
this.userStyleHandler = this.userStyleMonitor.connect('changed', (monitor, file, otherFile, eventType) => { },
// 'CHANGED' events are followed by a 'CHANGES_DONE_HINT' event
if (eventType == Gio.FileMonitorEvent.CHANGED || eventType == Gio.FileMonitorEvent.ATTRIBUTE_CHANGED) get onDesktop() {
return; return Me.settings.get_boolean('persistent-over-toggles') && Me.settings.get_boolean('drawing-on-desktop');
let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
if (theme.get_custom_stylesheets().indexOf(this.userStyleFile) != -1)
theme.unload_stylesheet(this.userStyleFile);
if (this.userStyleFile.query_exists(null))
theme.load_stylesheet(this.userStyleFile);
});
}, },
onDesktopSettingChanged: function() { onDesktopSettingChanged: function() {
if (this.settings.get_boolean("drawing-on-desktop")) if (this.onDesktop)
this.areas.forEach(area => area.get_parent().show()); this.areas.forEach(area => area.get_parent().show());
else else
this.areas.forEach(area => area.get_parent().hide()); this.areas.forEach(area => area.get_parent().hide());
}, },
onPersistentSettingChanged: function() { onPersistentOverRestartsSettingChanged: function() {
if (this.settings.get_boolean('persistent-drawing')) if (this.persistentOverRestarts)
this.areas[Main.layoutManager.primaryIndex].syncPersistent(); this.areas[Main.layoutManager.primaryIndex].syncPersistent();
}, },
onPersistentOverTogglesSettingChanged: function() {
if (!this.persistentOverToggles && !this.activeArea)
this.eraseDrawings();
this.onPersistentOverRestartsSettingChanged();
this.onDesktopSettingChanged();
},
updateIndicator: function() { updateIndicator: function() {
if (this.indicator) { if (this.indicator) {
this.indicator.disable(); this.indicator.disable();
this.indicator = null; this.indicator = null;
} }
if (!this.settings.get_boolean('indicator-disabled')) if (!Me.settings.get_boolean('indicator-disabled'))
this.indicator = new DrawingIndicator(); this.indicator = new DrawingIndicator();
}, },
@ -156,13 +168,13 @@ var AreaManager = new Lang.Class({
let monitor = this.monitors[i]; let monitor = this.monitors[i];
let container = new St.Widget({ name: 'drawOnYourSreenContainer' + i }); let container = new St.Widget({ name: 'drawOnYourSreenContainer' + i });
let helper = new Helper.DrawingHelper({ name: 'drawOnYourSreenHelper' + i }, monitor); let helper = new Helper.DrawingHelper({ name: 'drawOnYourSreenHelper' + i }, monitor);
let loadPersistent = i == Main.layoutManager.primaryIndex && this.settings.get_boolean('persistent-drawing'); let loadPersistent = i == Main.layoutManager.primaryIndex && this.persistentOverRestarts;
let area = new Area.DrawingArea({ name: 'drawOnYourSreenArea' + i }, monitor, helper, loadPersistent); let area = new Area.DrawingArea({ name: 'drawOnYourSreenArea' + i }, monitor, helper, loadPersistent);
container.add_child(area); container.add_child(area);
container.add_child(helper); container.add_child(helper);
Main.layoutManager._backgroundGroup.insert_child_above(container, Main.layoutManager._bgManagers[i].backgroundActor); Main.layoutManager._backgroundGroup.insert_child_above(container, Main.layoutManager._bgManagers[i].backgroundActor);
if (!this.settings.get_boolean("drawing-on-desktop")) if (!this.onDesktop)
container.hide(); container.hide();
container.set_position(monitor.x, monitor.y); container.set_position(monitor.x, monitor.y);
@ -171,7 +183,6 @@ var AreaManager = new Lang.Class({
area.leaveDrawingHandler = area.connect('leave-drawing-mode', this.toggleDrawing.bind(this)); area.leaveDrawingHandler = area.connect('leave-drawing-mode', this.toggleDrawing.bind(this));
area.updateActionModeHandler = area.connect('update-action-mode', this.updateActionMode.bind(this)); area.updateActionModeHandler = area.connect('update-action-mode', this.updateActionMode.bind(this));
area.showOsdHandler = area.connect('show-osd', this.showOsd.bind(this)); area.showOsdHandler = area.connect('show-osd', this.showOsd.bind(this));
area.showOsdGiconHandler = area.connect('show-osd-gicon', this.showOsd.bind(this));
this.areas.push(area); this.areas.push(area);
} }
}, },
@ -187,12 +198,14 @@ var AreaManager = new Lang.Class({
'decrement-line-width': () => this.activeArea.incrementLineWidth(-1), 'decrement-line-width': () => this.activeArea.incrementLineWidth(-1),
'increment-line-width-more': () => this.activeArea.incrementLineWidth(5), 'increment-line-width-more': () => this.activeArea.incrementLineWidth(5),
'decrement-line-width-more': () => this.activeArea.incrementLineWidth(-5), 'decrement-line-width-more': () => this.activeArea.incrementLineWidth(-5),
'paste-image-files': this.activeArea.pasteImageFiles.bind(this.activeArea),
'switch-linejoin': this.activeArea.switchLineJoin.bind(this.activeArea), 'switch-linejoin': this.activeArea.switchLineJoin.bind(this.activeArea),
'switch-linecap': this.activeArea.switchLineCap.bind(this.activeArea), 'switch-linecap': this.activeArea.switchLineCap.bind(this.activeArea),
'switch-fill-rule': this.activeArea.switchFillRule.bind(this.activeArea), 'switch-fill-rule': this.activeArea.switchFillRule.bind(this.activeArea),
'switch-dash' : this.activeArea.switchDash.bind(this.activeArea), 'switch-dash' : this.activeArea.switchDash.bind(this.activeArea),
'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), 'switch-image-file' : this.activeArea.switchImageFile.bind(this.activeArea, false),
'switch-image-file-reverse' : this.activeArea.switchImageFile.bind(this.activeArea, true),
'select-none-shape': () => this.activeArea.selectTool(Area.Tools.NONE), 'select-none-shape': () => this.activeArea.selectTool(Area.Tools.NONE),
'select-line-shape': () => this.activeArea.selectTool(Area.Tools.LINE), 'select-line-shape': () => this.activeArea.selectTool(Area.Tools.LINE),
'select-ellipse-shape': () => this.activeArea.selectTool(Area.Tools.ELLIPSE), 'select-ellipse-shape': () => this.activeArea.selectTool(Area.Tools.ELLIPSE),
@ -208,27 +221,28 @@ var AreaManager = new Lang.Class({
// available when writing // available when writing
this.internalKeybindings2 = { this.internalKeybindings2 = {
'save-as-svg': this.activeArea.saveAsSvg.bind(this.activeArea), 'export-to-svg': this.activeArea.exportToSvg.bind(this.activeArea),
'save-as-json': this.activeArea.saveAsJson.bind(this.activeArea), 'save-as-json': this.activeArea.saveAsJson.bind(this.activeArea, true, null),
'open-previous-json': this.activeArea.loadPreviousJson.bind(this.activeArea), 'open-previous-json': this.activeArea.loadPreviousJson.bind(this.activeArea),
'open-next-json': this.activeArea.loadNextJson.bind(this.activeArea), 'open-next-json': this.activeArea.loadNextJson.bind(this.activeArea),
'toggle-background': this.activeArea.toggleBackground.bind(this.activeArea), 'toggle-background': this.activeArea.toggleBackground.bind(this.activeArea),
'toggle-grid': this.activeArea.toggleGrid.bind(this.activeArea), 'toggle-grid': this.activeArea.toggleGrid.bind(this.activeArea),
'toggle-square-area': this.activeArea.toggleSquareArea.bind(this.activeArea), 'toggle-square-area': this.activeArea.toggleSquareArea.bind(this.activeArea),
'reverse-switch-font-family': this.activeArea.switchFontFamily.bind(this.activeArea, true), 'switch-color-palette': this.activeArea.switchColorPalette.bind(this.activeArea, false),
'switch-color-palette-reverse': this.activeArea.switchColorPalette.bind(this.activeArea, true),
'switch-font-family': this.activeArea.switchFontFamily.bind(this.activeArea, false), 'switch-font-family': this.activeArea.switchFontFamily.bind(this.activeArea, false),
'switch-font-family-reverse': this.activeArea.switchFontFamily.bind(this.activeArea, true),
'switch-font-weight': this.activeArea.switchFontWeight.bind(this.activeArea), 'switch-font-weight': this.activeArea.switchFontWeight.bind(this.activeArea),
'switch-font-style': this.activeArea.switchFontStyle.bind(this.activeArea), 'switch-font-style': this.activeArea.switchFontStyle.bind(this.activeArea),
'switch-text-alignment': this.activeArea.switchTextAlignment.bind(this.activeArea), 'switch-text-alignment': this.activeArea.switchTextAlignment.bind(this.activeArea),
'toggle-panel-and-dock-visibility': this.togglePanelAndDockOpacity.bind(this), 'toggle-panel-and-dock-visibility': this.togglePanelAndDockOpacity.bind(this),
'toggle-help': this.activeArea.toggleHelp.bind(this.activeArea), 'toggle-help': this.activeArea.toggleHelp.bind(this.activeArea),
'open-user-stylesheet': this.openUserStyleFile.bind(this),
'open-preferences': this.openPreferences.bind(this) 'open-preferences': this.openPreferences.bind(this)
}; };
for (let key in this.internalKeybindings1) { for (let key in this.internalKeybindings1) {
Main.wm.addKeybinding(key, Main.wm.addKeybinding(key,
this.settings, Me.internalShortcutSettings,
Meta.KeyBindingFlags.NONE, Meta.KeyBindingFlags.NONE,
DRAWING_ACTION_MODE, DRAWING_ACTION_MODE,
this.internalKeybindings1[key]); this.internalKeybindings1[key]);
@ -236,7 +250,7 @@ var AreaManager = new Lang.Class({
for (let key in this.internalKeybindings2) { for (let key in this.internalKeybindings2) {
Main.wm.addKeybinding(key, Main.wm.addKeybinding(key,
this.settings, Me.internalShortcutSettings,
Meta.KeyBindingFlags.NONE, Meta.KeyBindingFlags.NONE,
DRAWING_ACTION_MODE | WRITING_ACTION_MODE, DRAWING_ACTION_MODE | WRITING_ACTION_MODE,
this.internalKeybindings2[key]); this.internalKeybindings2[key]);
@ -245,10 +259,10 @@ var AreaManager = new Lang.Class({
for (let i = 1; i < 10; i++) { for (let i = 1; i < 10; i++) {
let iCaptured = i; let iCaptured = i;
Main.wm.addKeybinding('select-color' + i, Main.wm.addKeybinding('select-color' + i,
this.settings, Me.internalShortcutSettings,
Meta.KeyBindingFlags.NONE, Meta.KeyBindingFlags.NONE,
DRAWING_ACTION_MODE | WRITING_ACTION_MODE, DRAWING_ACTION_MODE | WRITING_ACTION_MODE,
() => this.activeArea.selectColor(iCaptured)); this.activeArea.selectColor.bind(this.activeArea, iCaptured - 1));
} }
}, },
@ -272,27 +286,10 @@ var AreaManager = new Lang.Class({
} }
}, },
openUserStyleFile: function() { eraseDrawings: function() {
if (!this.userStyleFile.query_exists(null)) {
if (!this.userStyleFile.get_parent().query_exists(null))
this.userStyleFile.get_parent().make_directory_with_parents(null);
let defaultStyleFile = Me.dir.get_child('data').get_child('default.css');
if (!defaultStyleFile.query_exists(null))
return;
let success = defaultStyleFile.copy(this.userStyleFile, Gio.FileCopyFlags.NONE, null, null);
if (!success)
return;
}
Gio.AppInfo.launch_default_for_uri(this.userStyleFile.get_uri(), global.create_app_launch_context(0, -1));
if (this.activeArea)
this.toggleDrawing();
},
eraseDrawing: function() {
for (let i = 0; i < this.areas.length; i++) for (let i = 0; i < this.areas.length; i++)
this.areas[i].erase(); this.areas[i].erase();
if (this.settings.get_boolean('persistent-drawing')) if (this.persistentOverRestarts)
this.areas[Main.layoutManager.primaryIndex].savePersistent(); this.areas[Main.layoutManager.primaryIndex].savePersistent();
}, },
@ -345,7 +342,7 @@ var AreaManager = new Lang.Class({
Main.uiGroup.set_child_at_index(Main.layoutManager.keyboardBox, this.oldKeyboardIndex); Main.uiGroup.set_child_at_index(Main.layoutManager.keyboardBox, this.oldKeyboardIndex);
Main.uiGroup.remove_actor(activeContainer); Main.uiGroup.remove_actor(activeContainer);
Main.layoutManager._backgroundGroup.insert_child_above(activeContainer, Main.layoutManager._bgManagers[activeIndex].backgroundActor); Main.layoutManager._backgroundGroup.insert_child_above(activeContainer, Main.layoutManager._bgManagers[activeIndex].backgroundActor);
if (!this.settings.get_boolean("drawing-on-desktop")) if (!this.onDesktop)
activeContainer.hide(); activeContainer.hide();
} else { } else {
Main.layoutManager._backgroundGroup.remove_actor(activeContainer); Main.layoutManager._backgroundGroup.remove_actor(activeContainer);
@ -365,8 +362,9 @@ var AreaManager = new Lang.Class({
if (Main._findModal(this.activeArea) != -1) { if (Main._findModal(this.activeArea) != -1) {
Main.popModal(this.activeArea); Main.popModal(this.activeArea);
if (source && source == global.display) if (source && source == global.display)
this.showOsd(null, 'touchpad-disabled-symbolic', _("Keyboard and pointer released"), null, null, false); // Translators: "released" as the opposite of "grabbed"
setCursor('DEFAULT'); this.showOsd(null, Files.Icons.UNGRAB, _("Keyboard and pointer released"), null, null, false);
this.setCursor('DEFAULT');
this.activeArea.reactive = false; this.activeArea.reactive = false;
this.removeInternalKeybindings(); this.removeInternalKeybindings();
} else { } else {
@ -378,7 +376,7 @@ var AreaManager = new Lang.Class({
this.activeArea.reactive = true; this.activeArea.reactive = true;
this.activeArea.initPointerCursor(); this.activeArea.initPointerCursor();
if (source && source == global.display) if (source && source == global.display)
this.showOsd(null, 'input-touchpad-symbolic', _("Keyboard and pointer grabbed"), null, null, false); this.showOsd(null, Files.Icons.GRAB, _("Keyboard and pointer grabbed"), null, null, false);
} }
return true; return true;
@ -387,10 +385,11 @@ var AreaManager = new Lang.Class({
toggleDrawing: function() { toggleDrawing: function() {
if (this.activeArea) { if (this.activeArea) {
let activeIndex = this.areas.indexOf(this.activeArea); let activeIndex = this.areas.indexOf(this.activeArea);
let save = activeIndex == Main.layoutManager.primaryIndex && this.settings.get_boolean('persistent-drawing'); let save = activeIndex == Main.layoutManager.primaryIndex && this.persistentOverRestarts;
let erase = !this.persistentOverToggles;
this.showOsd(null, this.leaveGicon, _("Leaving drawing mode")); this.showOsd(null, Files.Icons.LEAVE, _("Leaving drawing mode"));
this.activeArea.leaveDrawingMode(save); this.activeArea.leaveDrawingMode(save, erase);
if (this.hiddenList) if (this.hiddenList)
this.togglePanelAndDockOpacity(); this.togglePanelAndDockOpacity();
@ -410,9 +409,10 @@ var AreaManager = new Lang.Class({
} }
this.activeArea.enterDrawingMode(); this.activeArea.enterDrawingMode();
this.osdDisabled = this.settings.get_boolean('osd-disabled'); this.osdDisabled = Me.settings.get_boolean('osd-disabled');
let label = _("<small>Press <i>%s</i> for help</small>").format(this.activeArea.helper.helpKeyLabel) + "\n\n" + _("Entering drawing mode"); // Translators: %s is a key label
this.showOsd(null, this.enterGicon, label, null, null, true); let label = "<small>" + _("Press <i>%s</i> for help").format(this.activeArea.helper.helpKeyLabel) + "</small>\n\n" + _("Entering drawing mode");
this.showOsd(null, Files.Icons.ENTER, label, null, null, true);
} }
if (this.indicator) if (this.indicator)
@ -446,10 +446,8 @@ var AreaManager = new Lang.Class({
if (level && GS_VERSION > '3.33.0') if (level && GS_VERSION > '3.33.0')
level = level / 100; level = level / 100;
if (icon && typeof icon == 'string') if (!icon)
icon = new Gio.ThemedIcon({ name: icon }); icon = Files.Icons.ENTER;
else if (!icon)
icon = this.enterGicon;
let osdWindow = Main.osdWindowManager._osdWindows[activeIndex]; let osdWindow = Main.osdWindowManager._osdWindows[activeIndex];
@ -503,13 +501,20 @@ var AreaManager = new Lang.Class({
OsdWindow.HIDE_TIMEOUT = hideTimeoutSave; OsdWindow.HIDE_TIMEOUT = hideTimeoutSave;
}, },
setCursor: function(cursorName) {
// check display or screen (API changes)
if (global.display.set_cursor)
global.display.set_cursor(Meta.Cursor[cursorName]);
else if (global.screen && global.screen.set_cursor)
global.screen.set_cursor(Meta.Cursor[cursorName]);
},
removeAreas: function() { removeAreas: function() {
for (let i = 0; i < this.areas.length; i++) { for (let i = 0; i < this.areas.length; i++) {
let area = this.areas[i]; let area = this.areas[i];
area.disconnect(area.leaveDrawingHandler); area.disconnect(area.leaveDrawingHandler);
area.disconnect(area.updateActionModeHandler); area.disconnect(area.updateActionModeHandler);
area.disconnect(area.showOsdHandler); area.disconnect(area.showOsdHandler);
area.disconnect(area.showOsdGiconHandler);
let container = area.get_parent(); let container = area.get_parent();
container.get_parent().remove_actor(container); container.get_parent().remove_actor(container);
container.destroy(); container.destroy();
@ -518,37 +523,35 @@ var AreaManager = new Lang.Class({
}, },
disable: function() { disable: function() {
if (this.userStyleHandler && this.userStyleMonitor) {
this.userStyleMonitor.disconnect(this.userStyleHandler);
this.userStyleHandler = null;
}
if (this.userStyleMonitor) {
this.userStyleMonitor.cancel();
this.userStyleMonitor = null;
}
if (this.monitorChangedHandler) { if (this.monitorChangedHandler) {
Main.layoutManager.disconnect(this.monitorChangedHandler); Main.layoutManager.disconnect(this.monitorChangedHandler);
this.monitorChangedHandler = null; this.monitorChangedHandler = null;
} }
if (this.indicatorSettingHandler) { if (this.indicatorSettingHandler) {
this.settings.disconnect(this.indicatorSettingHandler); Me.settings.disconnect(this.indicatorSettingHandler);
this.indicatorSettingHandler = null; this.indicatorSettingHandler = null;
} }
if (this.desktopSettingHandler) { if (this.desktopSettingHandler) {
this.settings.disconnect(this.desktopSettingHandler); Me.settings.disconnect(this.desktopSettingHandler);
this.desktopSettingHandler = null; this.desktopSettingHandler = null;
} }
if (this.persistentSettingHandler) { if (this.persistentOverTogglesSettingHandler) {
this.settings.disconnect(this.persistentSettingHandler); Me.settings.disconnect(this.persistentOverTogglesSettingHandler);
this.persistentSettingHandler = null; this.persistentOverTogglesSettingHandler = null;
}
if (this.persistentOverRestartsSettingHandler) {
Me.settings.disconnect(this.persistentOverRestartsSettingHandler);
this.persistentOverRestartsSettingHandler = null;
} }
if (this.activeArea) if (this.activeArea)
this.toggleDrawing(); this.toggleDrawing();
Main.wm.removeKeybinding('toggle-drawing'); Main.wm.removeKeybinding('toggle-drawing');
Main.wm.removeKeybinding('toggle-modal'); Main.wm.removeKeybinding('toggle-modal');
Main.wm.removeKeybinding('erase-drawing'); Main.wm.removeKeybinding('erase-drawings');
this.removeAreas(); this.removeAreas();
Files.Images.disable();
Files.Jsons.disable();
if (this.indicator) if (this.indicator)
this.indicator.disable(); this.indicator.disable();
} }
@ -601,12 +604,3 @@ const DrawingIndicator = new Lang.Class({
} }
}); });
function setCursor(cursorName) {
// check display or screen (API changes)
if (global.display.set_cursor)
global.display.set_cursor(Meta.Cursor[cursorName]);
else if (global.screen && global.screen.set_cursor)
global.screen.set_cursor(Meta.Cursor[cursorName]);
}

486
files.js
View File

@ -1,4 +1,5 @@
/* jslint esversion: 6 */ /* jslint esversion: 6 */
/* exported Icons, Image, Images, Json, Jsons, getDateString, saveSvg */
/* /*
* Copyright 2019 Abakkk * Copyright 2019 Abakkk
@ -26,13 +27,54 @@ const GdkPixbuf = imports.gi.GdkPixbuf;
const Gio = imports.gi.Gio; const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const Lang = imports.lang; const Lang = imports.lang;
const St = imports.gi.St;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension(); const Me = ExtensionUtils.getCurrentExtension();
const EXAMPLE_IMAGES = Me.dir.get_child('data').get_child('images'); const EXAMPLE_IMAGE_DIRECTORY = Me.dir.get_child('data').get_child('images');
const USER_IMAGES = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir'], 'images'])); const DEFAULT_USER_IMAGE_LOCATION = GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir'], 'images']);
const Clipboard = St.Clipboard.get_default();
const CLIPBOARD_TYPE = St.ClipboardType.CLIPBOARD;
const ICON_DIR = Me.dir.get_child('data').get_child('icons');
const ICON_NAMES = [
'arc', 'color', 'dashed-line', 'document-export', 'fillrule-evenodd', 'fillrule-nonzero', 'fill', 'full-line', 'linecap', 'linejoin', 'palette', 'smooth', 'stroke',
'tool-ellipse', 'tool-line', 'tool-mirror', 'tool-move', 'tool-none', 'tool-polygon', 'tool-polyline', 'tool-rectangle', 'tool-resize',
];
const ThemedIconNames = {
ENTER: 'applications-graphics', LEAVE: 'application-exit',
GRAB: 'input-touchpad', UNGRAB: 'touchpad-disabled',
OPEN: 'document-open', SAVE: 'document-save',
FONT_FAMILY: 'font-x-generic', FONT_STYLE: 'format-text-italic', FONT_WEIGHT:'format-text-bold',
LEFT_ALIGNED: 'format-justify-left', RIGHT_ALIGNED: 'format-justify-right',
TOOL_IMAGE: 'insert-image', TOOL_TEXT: 'insert-text',
};
// wrapper around an image file var Icons = {};
ICON_NAMES.forEach(name => {
Object.defineProperty(Icons, name.toUpperCase().replace(/-/gi, '_'), {
get: function() {
if (!this[`_${name}`]) {
let file = Gio.File.new_for_path(ICON_DIR.get_child(`${name}-symbolic.svg`).get_path());
this[`_${name}`] = file.query_exists(null) ? new Gio.FileIcon({ file }) : new Gio.ThemedIcon({ name: 'action-unavailable-symbolic' });
}
return this[`_${name}`];
}
});
});
Object.keys(ThemedIconNames).forEach(key => {
Object.defineProperty(Icons, key, {
get: function() {
if (!this[`_${key}`])
this[`_${key}`] = new Gio.ThemedIcon({ name: `${ThemedIconNames[key]}-symbolic` });
return this[`_${key}`];
}
});
});
// Wrapper around image data. If not subclassed, it is used when loading in the area an image element for a drawing file (.json)
// and it takes { displayName, contentType, base64, hash } as params.
var Image = new Lang.Class({ var Image = new Lang.Class({
Name: 'DrawOnYourScreenImage', Name: 'DrawOnYourScreenImage',
@ -54,29 +96,9 @@ var Image = new Lang.Class({
}; };
}, },
// only called from menu so file exists
get gicon() {
if (!this._gicon)
this._gicon = new Gio.FileIcon({ file: this.file });
return this._gicon;
},
get bytes() { get bytes() {
if (!this._bytes) { if (!this._bytes)
if (this.file) this._bytes = new GLib.Bytes(GLib.base64_decode(this.base64));
try {
// load_bytes available in GLib 2.56+
this._bytes = this.file.load_bytes(null)[0];
} catch(e) {
let [success_, contents] = this.file.load_contents(null);
if (contents instanceof Uint8Array)
this._bytes = ByteArray.toGBytes(contents);
else
this._bytes = contents.toGBytes();
}
else
this._bytes = new GLib.Bytes(GLib.base64_decode(this.base64));
}
return this._bytes; return this._bytes;
}, },
@ -124,34 +146,207 @@ var Image = new Lang.Class({
} }
}); });
var getImages = function() { // Add a gicon generator to Image. It is used with image files and it takes { file, info } as params.
let images = []; const ImageWithGicon = new Lang.Class({
Name: 'DrawOnYourScreenImageWithGicon',
Extends: Image,
[EXAMPLE_IMAGES, USER_IMAGES].forEach(directory => { get displayName() {
let enumerator; return this.info.get_display_name();
try { },
enumerator = directory.enumerate_children('standard::display-name,standard::content-type', Gio.FileQueryInfoFlags.NONE, null);
} catch(e) { get contentType() {
return; return this.info.get_content_type();
},
get thumbnailFile() {
if (!this._thumbnailFile) {
if (this.info.has_attribute('thumbnail::path') && this.info.get_attribute_boolean('thumbnail::is-valid')) {
let thumbnailPath = this.info.get_attribute_as_string('thumbnail::path');
this._thumbnailFile = Gio.File.new_for_path(thumbnailPath);
}
} }
return this._thumbnailFile || null;
},
get gicon() {
if (!this._gicon)
this._gicon = new Gio.FileIcon({ file: this.thumbnailFile || this.file });
return this._gicon;
},
// use only thumbnails in menu (memory)
get thumbnailGicon() {
if (this.contentType != 'image/svg+xml' && !this.thumbnailFile)
return null;
let fileInfo = enumerator.next_file(null); return this.gicon;
while (fileInfo) { },
if (fileInfo.get_content_type().indexOf('image') == 0)
images.push(new Image({ file: enumerator.get_child(fileInfo), contentType: fileInfo.get_content_type(), displayName: fileInfo.get_display_name() })); get bytes() {
fileInfo = enumerator.next_file(null); if (!this._bytes) {
try {
// load_bytes available in GLib 2.56+
this._bytes = this.file.load_bytes(null)[0];
} catch(e) {
let [, contents] = this.file.load_contents(null);
if (contents instanceof Uint8Array)
this._bytes = ByteArray.toGBytes(contents);
else
this._bytes = contents.toGBytes();
}
} }
enumerator.close(null); return this._bytes;
}); }
});
// It is directly generated from a Json object, without an image file. It takes { bytes, displayName, gicon } as params.
const ImageFromJson = new Lang.Class({
Name: 'DrawOnYourScreenImageFromJson',
Extends: Image,
contentType: 'image/svg+xml',
images.sort((a, b) => { get bytes() {
return a.displayName.localeCompare(b.displayName); return this._bytes;
}); },
return images; set bytes(bytes) {
this._bytes = bytes;
}
});
// Access images with getPrevious, getNext, getSorted or by iterating over it.
var Images = {
_images: [],
_clipboardImages: [],
_upToDate: false,
disable: function() {
this._images = [];
this._clipboardImages = [];
this._upToDate = false;
},
_clipboardImagesContains: function(file) {
return this._clipboardImages.some(image => image.file.equal(file));
},
// Firstly iterate over the extension directory that contains Example.svg,
// secondly iterate over the directory that was configured by the user in prefs,
// finally iterate over the images pasted from the clipboard.
[Symbol.iterator]: function() {
if (this._upToDate)
return this._images.concat(this._clipboardImages)[Symbol.iterator]();
this._upToDate = true;
let oldImages = this._images;
let newImages = this._images = [];
let clipboardImagesContains = this._clipboardImagesContains.bind(this);
let clipboardIterator = this._clipboardImages[Symbol.iterator]();
return {
getExampleEnumerator: function() {
try {
return EXAMPLE_IMAGE_DIRECTORY.enumerate_children('standard::,thumbnail::', Gio.FileQueryInfoFlags.NONE, null);
} catch(e) {
return this.getUserEnumerator();
}
},
getUserEnumerator: function() {
try {
let userLocation = Me.drawingSettings.get_string('image-location') || DEFAULT_USER_IMAGE_LOCATION;
let userDirectory = Gio.File.new_for_commandline_arg(userLocation);
return userDirectory.enumerate_children('standard::,thumbnail::', Gio.FileQueryInfoFlags.NONE, null);
} catch(e) {
return null;
}
},
get enumerator() {
if (this._enumerator === undefined)
this._enumerator = this.getExampleEnumerator();
else if (this._enumerator && this._enumerator.get_container().equal(EXAMPLE_IMAGE_DIRECTORY) && this._enumerator.is_closed())
this._enumerator = this.getUserEnumerator();
else if (this._enumerator && this._enumerator.is_closed())
this._enumerator = null;
return this._enumerator;
},
next: function() {
if (!this.enumerator)
return clipboardIterator.next();
let info = this.enumerator.next_file(null);
if (!info) {
this.enumerator.close(null);
return this.next();
}
let file = this.enumerator.get_child(info);
if (info.get_content_type().indexOf('image') == 0 && !clipboardImagesContains(file)) {
let image = oldImages.find(oldImage => oldImage.file.equal(file)) || new ImageWithGicon({ file, info });
newImages.push(image);
return { value: image, done: false };
} else {
return this.next();
}
}
};
},
getSorted: function() {
return [...this].sort((a, b) => a.toString().localeCompare(b.toString()));
},
getNext: function(currentImage) {
let images = this.getSorted();
let index = currentImage && currentImage.file ? images.findIndex(image => image.file.equal(currentImage.file)) : -1;
return images[index == images.length - 1 ? 0 : index + 1] || null;
},
getPrevious: function(currentImage) {
let images = this.getSorted();
let index = currentImage && currentImage.file ? images.findIndex(image => image.file.equal(currentImage.file)) : -1;
return images[index <= 0 ? images.length - 1 : index - 1] || null;
},
reset: function() {
this._upToDate = false;
},
addImagesFromClipboard: function(callback) {
Clipboard.get_text(CLIPBOARD_TYPE, (clipBoard, text) => {
if (!text)
return;
let lines = text.split('\n');
if (lines[0] == 'x-special/nautilus-clipboard')
lines = lines.slice(2);
let images = lines.filter(line => !!line)
.map(line => Gio.File.new_for_commandline_arg(line))
.filter(file => file.query_exists(null))
.map(file => [file, file.query_info('standard::,thumbnail::', Gio.FileQueryInfoFlags.NONE, null)])
.filter(pair => pair[1].get_content_type().indexOf('image') == 0)
.map(pair => new ImageWithGicon({ file: pair[0], info: pair[1] }));
// Prevent duplicated
images.filter(image => !this._clipboardImagesContains(image.file))
.forEach(image => this._clipboardImages.push(image));
if (images.length) {
this.reset();
let lastFile = images[images.length - 1].file;
callback(this._clipboardImages.find(image => image.file.equal(lastFile)));
}
});
}
}; };
// wrapper around a json file // Wrapper around a json file (drawing saves).
var Json = new Lang.Class({ var Json = new Lang.Class({
Name: 'DrawOnYourScreenJson', Name: 'DrawOnYourScreenJson',
@ -160,6 +355,10 @@ var Json = new Lang.Class({
this[key] = params[key]; this[key] = params[key];
}, },
get isPersistent() {
return this.name == Me.metadata['persistent-file-name'];
},
toString: function() { toString: function() {
return this.displayName || this.name; return this.displayName || this.name;
}, },
@ -169,10 +368,10 @@ var Json = new Lang.Class({
}, },
get file() { get file() {
if (!this._file && this.name) if (!this._file)
this._file = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir'], `${this.name}.json`])); this._file = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir'], `${this.name}.json`]));
return this._file || null; return this._file;
}, },
set file(file) { set file(file) {
@ -180,62 +379,181 @@ var Json = new Lang.Class({
}, },
get contents() { get contents() {
let success_, contents; if (this._contents === undefined) {
try { try {
[success_, contents] = this.file.load_contents(null); [, this._contents] = this.file.load_contents(null);
if (contents instanceof Uint8Array) if (this._contents instanceof Uint8Array)
contents = ByteArray.toString(contents); this._contents = ByteArray.toString(this._contents);
} catch(e) { } catch(e) {
return null; this._contents = null;
}
} }
return contents;
return this._contents;
}, },
set contents(contents) { set contents(contents) {
if (this.isPersistent && (this.contents == contents || !this.contents && contents == '[]'))
return;
try { try {
this.file.replace_contents(contents, null, false, Gio.FileCreateFlags.NONE, null); this.file.replace_contents(contents, null, false, Gio.FileCreateFlags.NONE, null);
} catch(e) { } catch(e) {
this.file.get_parent().make_directory_with_parents(null); this.file.get_parent().make_directory_with_parents(null);
this.file.replace_contents(contents, null, false, Gio.FileCreateFlags.NONE, null); this.file.replace_contents(contents, null, false, Gio.FileCreateFlags.NONE, null);
} }
this._contents = contents;
},
addSvgContents: function(getGiconSvgContent, getImageSvgContent) {
let giconSvgBytes = new GLib.Bytes(getGiconSvgContent());
this.gicon = Gio.BytesIcon.new(giconSvgBytes);
this.getImageSvgBytes = () => new GLib.Bytes(getImageSvgContent());
},
get image() {
if (!this._image)
this._image = new ImageFromJson({ bytes: this.getImageSvgBytes(), gicon: this.gicon, displayName: this.displayName });
return this._image;
} }
}); });
var getJsons = function() { // Access jsons with getPersistent, getDated, getNamed, getPrevious, getNext, getSorted or by iterating over it.
let directory = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir']])); var Jsons = {
_jsons: [],
_upToDate: false,
let enumerator; disable: function() {
try { if (this._monitor) {
enumerator = directory.enumerate_children('standard::name,standard::display-name,standard::content-type,time::modified', Gio.FileQueryInfoFlags.NONE, null); this._monitor.disconnect(this._monitorHandler);
} catch(e) { this._monitor.cancel();
return [];
}
let jsons = [];
let fileInfo = enumerator.next_file(null);
while (fileInfo) {
if (fileInfo.get_content_type().indexOf('json') != -1 && fileInfo.get_name() != `${Me.metadata['persistent-file-name']}.json`) {
let file = enumerator.get_child(fileInfo);
jsons.push(new Json({
file,
name: fileInfo.get_name().slice(0, -5),
displayName: fileInfo.get_display_name().slice(0, -5),
// fileInfo.get_modification_date_time: Gio 2.62+
modificationUnixTime: fileInfo.get_attribute_uint64('time::modified')
}));
} }
fileInfo = enumerator.next_file(null);
delete this._monitor;
delete this._persistent;
this._jsons = [];
this._upToDate = false;
},
_updateMonitor: function() {
if (this._monitor)
return;
let directory = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir']]));
this._monitor = directory.monitor(Gio.FileMonitorFlags.NONE, null);
this._monitorHandler = this._monitor.connect('changed', (monitor, file) => {
if (file.get_basename() != `${Me.metadata['persistent-file-name']}.json` && file.get_basename().indexOf('.goutputstream'))
this.reset();
});
},
[Symbol.iterator]: function() {
if (this._upToDate)
return this._jsons[Symbol.iterator]();
this._updateMonitor();
this._upToDate = true;
let newJsons = this._jsons = [];
return {
get enumerator() {
if (this._enumerator === undefined) {
try {
let directory = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir']]));
this._enumerator = directory.enumerate_children('standard::name,standard::display-name,standard::content-type,time::modified', Gio.FileQueryInfoFlags.NONE, null);
} catch(e) {
this._enumerator = null;
}
}
return this._enumerator;
},
next: function() {
if (!this.enumerator || this.enumerator.is_closed())
return { done: true };
let info = this.enumerator.next_file(null);
if (!info) {
this.enumerator.close(null);
return this.next();
}
let file = this.enumerator.get_child(info);
if (info.get_content_type().indexOf('json') != -1 && info.get_name() != `${Me.metadata['persistent-file-name']}.json`) {
let json = new Json({
file, name: info.get_name().slice(0, -5),
displayName: info.get_display_name().slice(0, -5),
// info.get_modification_date_time: Gio 2.62+
modificationUnixTime: info.get_attribute_uint64('time::modified')
});
newJsons.push(json);
return { value: json, done: false };
} else {
return this.next();
}
}
};
},
getSorted: function() {
return [...this].sort((a, b) => b.modificationUnixTime - a.modificationUnixTime);
},
getNext: function(currentJson) {
let jsons = this.getSorted();
let index = currentJson ? jsons.findIndex(json => json.name == currentJson.name) : -1;
return jsons[index == jsons.length - 1 ? 0 : index + 1] || null;
},
getPrevious: function(currentJson) {
let jsons = this.getSorted();
let index = currentJson ? jsons.findIndex(json => json.name == currentJson.name) : -1;
return jsons[index <= 0 ? jsons.length - 1 : index - 1] || null;
},
getPersistent: function() {
if (!this._persistent)
this._persistent = new Json({ name: Me.metadata['persistent-file-name'] });
return this._persistent;
},
getDated: function() {
return new Json({ name: getDateString() });
},
getNamed: function(name) {
return [...this].find(json => json.name == name) || new Json({ name });
},
reset: function() {
this._upToDate = false;
} }
enumerator.close(null);
jsons.sort((a, b) => {
return b.modificationUnixTime - a.modificationUnixTime;
});
return jsons;
}; };
var getDateString = function() { var getDateString = function() {
let date = GLib.DateTime.new_now_local(); let date = GLib.DateTime.new_now_local();
return `${date.format("%F")} ${date.format("%X")}`; return `${date.format("%F")} ${date.format("%X")}`;
}; };
var saveSvg = function(content) {
let filename = `${Me.metadata['svg-file-name']} ${getDateString()}.svg`;
let dir = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES);
let path = GLib.build_filenamev([dir, filename]);
let file = Gio.File.new_for_path(path);
if (file.query_exists(null))
return false;
try {
return file.replace_contents(content, null, false, Gio.FileCreateFlags.NONE, null)[0];
} catch(e) {
return false;
}
};

110
helper.js
View File

@ -1,4 +1,5 @@
/* jslint esversion: 6 */ /* jslint esversion: 6 */
/* exported DrawingHelper */
/* /*
* Copyright 2019 Abakkk * Copyright 2019 Abakkk
@ -25,24 +26,19 @@ const Lang = imports.lang;
const St = imports.gi.St; const St = imports.gi.St;
const Config = imports.misc.config; const Config = imports.misc.config;
const ExtensionUtils = imports.misc.extensionUtils;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension(); const Me = ExtensionUtils.getCurrentExtension();
const Convenience = ExtensionUtils.getSettings ? ExtensionUtils : Me.imports.convenience; const Convenience = ExtensionUtils.getSettings ? ExtensionUtils : Me.imports.convenience;
const Prefs = Me.imports.prefs; const Shortcuts = Me.imports.shortcuts;
const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext;
const GS_VERSION = Config.PACKAGE_VERSION; const GS_VERSION = Config.PACKAGE_VERSION;
const HELPER_ANIMATION_TIME = 0.25; const HELPER_ANIMATION_TIME = 0.25;
const MEDIA_KEYS_SCHEMA = 'org.gnome.settings-daemon.plugins.media-keys'; const MEDIA_KEYS_SCHEMA = 'org.gnome.settings-daemon.plugins.media-keys';
const MEDIA_KEYS_KEYS = { const MEDIA_KEYS_KEYS = ['screenshot', 'screenshot-clip', 'area-screenshot', 'area-screenshot-clip'];
'screenshot': "Screenshot",
'screenshot-clip': "Screenshot to clipboard",
'area-screenshot': "Area screenshot",
'area-screenshot-clip': "Area screenshot to clipboard"
};
// DrawingHelper provides the "help osd" (Ctrl + F1) // DrawingHelper provides the "help osd" (Ctrl + F1)
// It uses the same texts as in prefs // It uses the same texts as in prefs
@ -55,24 +51,27 @@ var DrawingHelper = new Lang.Class({
this.parent(params); this.parent(params);
this.monitor = monitor; this.monitor = monitor;
this.hide(); this.hide();
this.settings = Convenience.getSettings();
this.settingHandler = this.settings.connect('changed', this._onSettingChanged.bind(this)); this.settingsHandler = Me.settings.connect('changed', this._onSettingsChanged.bind(this));
this.connect('destroy', () => this.settings.disconnect(this.settingHandler)); this.internalShortcutsettingsHandler = Me.internalShortcutSettings.connect('changed', this._onSettingsChanged.bind(this));
this.connect('destroy', () => {
Me.settings.disconnect(this.settingsHandler);
Me.internalShortcutSettings.disconnect(this.internalShortcutsettingsHandler);
});
}, },
_onSettingChanged: function(settings, key) { _onSettingsChanged: function(settings, key) {
if (key == 'toggle-help') if (key == 'toggle-help')
this._updateHelpKeyLabel(); this._updateHelpKeyLabel();
if (this.vbox) { if (this.vbox) {
this.vbox.destroy(); this.vbox.destroy();
this.vbox = null; delete this.vbox;
} }
}, },
_updateHelpKeyLabel: function() { _updateHelpKeyLabel: function() {
let [keyval, mods] = Gtk.accelerator_parse(this.settings.get_strv('toggle-help')[0]); let [keyval, mods] = Gtk.accelerator_parse(Me.internalShortcutSettings.get_strv('toggle-help')[0] || '');
this._helpKeyLabel = Gtk.accelerator_get_label(keyval, mods); this._helpKeyLabel = Gtk.accelerator_get_label(keyval, mods);
}, },
@ -88,63 +87,72 @@ var DrawingHelper = new Lang.Class({
this.add_actor(this.vbox); this.add_actor(this.vbox);
this.vbox.add_child(new St.Label({ text: _("Global") })); this.vbox.add_child(new St.Label({ text: _("Global") }));
for (let settingKey in Prefs.GLOBAL_KEYBINDINGS) { Shortcuts.GLOBAL_KEYBINDINGS.forEach((settingKeys, index) => {
let hbox = new St.BoxLayout({ vertical: false }); if (index)
if (settingKey.indexOf('-separator-') != -1) { this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' }));
settingKeys.forEach(settingKey => {
if (!Me.settings.get_strv(settingKey)[0])
return;
let hbox = new St.BoxLayout({ vertical: false });
let [keyval, mods] = Gtk.accelerator_parse(Me.settings.get_strv(settingKey)[0] || '');
hbox.add_child(new St.Label({ text: Me.settings.settings_schema.get_key(settingKey).get_summary() }));
hbox.add_child(new St.Label({ text: Gtk.accelerator_get_label(keyval, mods), x_expand: true }));
this.vbox.add_child(hbox); this.vbox.add_child(hbox);
continue; });
} });
if (!this.settings.get_strv(settingKey)[0])
continue;
let [keyval, mods] = Gtk.accelerator_parse(this.settings.get_strv(settingKey)[0]);
hbox.add_child(new St.Label({ text: _(Prefs.GLOBAL_KEYBINDINGS[settingKey]) }));
hbox.add_child(new St.Label({ text: Gtk.accelerator_get_label(keyval, mods), x_expand: true }));
this.vbox.add_child(hbox);
}
this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' }));
this.vbox.add_child(new St.Label({ text: _("Internal") })); this.vbox.add_child(new St.Label({ text: _("Internal") }));
for (let i = 0; i < Prefs.OTHER_SHORTCUTS.length; i++) { Shortcuts.OTHERS.forEach((pairs, index) => {
if (Prefs.OTHER_SHORTCUTS[i].desc.indexOf('-separator-') != -1) { if (index)
this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' })); this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' }));
continue;
} pairs.forEach(pair => {
let hbox = new St.BoxLayout({ vertical: false }); let [action, shortcut] = pair;
hbox.add_child(new St.Label({ text: _(Prefs.OTHER_SHORTCUTS[i].desc) })); let hbox = new St.BoxLayout({ vertical: false });
hbox.add_child(new St.Label({ text: Prefs.OTHER_SHORTCUTS[i].shortcut, x_expand: true })); hbox.add_child(new St.Label({ text: action }));
hbox.get_children()[0].get_clutter_text().set_use_markup(true); hbox.add_child(new St.Label({ text: shortcut, x_expand: true }));
this.vbox.add_child(hbox); hbox.get_children()[0].get_clutter_text().set_use_markup(true);
} this.vbox.add_child(hbox);
});
});
this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' })); this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' }));
for (let settingKey in Prefs.INTERNAL_KEYBINDINGS) { Shortcuts.INTERNAL_KEYBINDINGS.forEach((settingKeys, index) => {
if (settingKey.indexOf('-separator-') != -1) { if (index)
this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' })); this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' }));
continue;
} settingKeys.forEach(settingKey => {
let hbox = new St.BoxLayout({ vertical: false }); if (!Me.internalShortcutSettings.get_strv(settingKey)[0])
if (!this.settings.get_strv(settingKey)[0]) return;
continue;
let [keyval, mods] = Gtk.accelerator_parse(this.settings.get_strv(settingKey)[0]); let hbox = new St.BoxLayout({ vertical: false });
hbox.add_child(new St.Label({ text: _(Prefs.INTERNAL_KEYBINDINGS[settingKey]) })); let [keyval, mods] = Gtk.accelerator_parse(Me.internalShortcutSettings.get_strv(settingKey)[0] || '');
hbox.add_child(new St.Label({ text: Gtk.accelerator_get_label(keyval, mods), x_expand: true })); hbox.add_child(new St.Label({ text: Me.internalShortcutSettings.settings_schema.get_key(settingKey).get_summary() }));
this.vbox.add_child(hbox); hbox.add_child(new St.Label({ text: Gtk.accelerator_get_label(keyval, mods), x_expand: true }));
} this.vbox.add_child(hbox);
});
});
let mediaKeysSettings; let mediaKeysSettings;
try { mediaKeysSettings = Convenience.getSettings(MEDIA_KEYS_SCHEMA); } catch(e) { return; } try { mediaKeysSettings = Convenience.getSettings(MEDIA_KEYS_SCHEMA); } catch(e) { return; }
this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' }));
this.vbox.add_child(new St.Label({ text: _("System") })); this.vbox.add_child(new St.Label({ text: _("System") }));
for (let settingKey in MEDIA_KEYS_KEYS) { for (let settingKey of MEDIA_KEYS_KEYS) {
if (!mediaKeysSettings.settings_schema.has_key(settingKey)) if (!mediaKeysSettings.settings_schema.has_key(settingKey))
continue; continue;
let shortcut = GS_VERSION < '3.33.0' ? mediaKeysSettings.get_string(settingKey) : mediaKeysSettings.get_strv(settingKey)[0]; let shortcut = GS_VERSION < '3.33.0' ? mediaKeysSettings.get_string(settingKey) : mediaKeysSettings.get_strv(settingKey)[0];
if (!shortcut) if (!shortcut)
continue; continue;
let [keyval, mods] = Gtk.accelerator_parse(shortcut); let [keyval, mods] = Gtk.accelerator_parse(shortcut || '');
let hbox = new St.BoxLayout({ vertical: false }); let hbox = new St.BoxLayout({ vertical: false });
hbox.add_child(new St.Label({ text: _(MEDIA_KEYS_KEYS[settingKey]) })); hbox.add_child(new St.Label({ text: mediaKeysSettings.settings_schema.get_key(settingKey).get_summary() }));
hbox.add_child(new St.Label({ text: Gtk.accelerator_get_label(keyval, mods), x_expand: true })); hbox.add_child(new St.Label({ text: Gtk.accelerator_get_label(keyval, mods), x_expand: true }));
this.vbox.add_child(hbox); this.vbox.add_child(hbox);
} }

8
locale/POTFILES.in Normal file
View File

@ -0,0 +1,8 @@
# xgettext --from-code=UTF-8 --add-comments="Translators: " --no-location --package-name="Draw On Your Screen" --msgid-bugs-address="https://framagit.org/abakkk/DrawOnYourScreen/issues" -f locale/POTFILES.in
area.js
extension.js
helper.js
menu.js
prefs.js
shortcuts.js
schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml

View File

@ -8,9 +8,9 @@
# You are free to translate them or not. # You are free to translate them or not.
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Draw On Your Screen VERSION\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: 2019-03-04 16:40+0100\n" "POT-Creation-Date: 2020-09-17 22:27+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -19,66 +19,344 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
msgid "About" #. Translators: %s is a key label
#, javascript-format
msgid ""
"Press <i>%s</i> to get\n"
"a fourth control point"
msgstr "" msgstr ""
# You are free to translate the extension name, that is displayed in About page, or not. msgid "Mark a point of symmetry"
msgid "Draw On You Screen"
msgstr "" msgstr ""
msgid "Version %d" msgid "Draw a line of symmetry"
msgstr "" msgstr ""
msgid "Start drawing with Super+Alt+D and save your beautiful work by taking a screenshot" #. Translators: initial content of the text area
msgctxt "text-area-content"
msgid "Text"
msgstr "" msgstr ""
# Add your name here, for example: #. Translators: %s is a key label
# (add "\n" as separator if there is many translators) #, javascript-format
# msgid "translator-credits" msgid "Press <i>%s</i> to mark vertices"
# msgstr "Me"
# or, with mail:
# msgid "translator-credits"
# msgstr "<a href=\"mailto:me@mail.org\">Me</a>"
# or, with page:
# msgid "translator-credits"
# msgstr "<a href=\"https://...\">Me</a>"
# else keep it empty.
# It will be displayed in about page
msgid "translator-credits"
msgstr "" msgstr ""
msgid "Preferences" #. Translators: %s is a key label
#, javascript-format
msgid "Type your text and press <i>%s</i>"
msgstr ""
#. Translators: "released" as the opposite of "grabbed"
msgid "Keyboard and pointer released"
msgstr ""
msgid "Keyboard and pointer grabbed"
msgstr ""
msgid "Leaving drawing mode"
msgstr ""
#. Translators: %s is a key label
#, javascript-format
msgid "Press <i>%s</i> for help"
msgstr ""
msgid "Entering drawing mode"
msgstr "" msgstr ""
msgid "Global" msgid "Global"
msgstr "" msgstr ""
msgid "Enter/leave drawing mode" msgid "Internal"
msgstr "" msgstr ""
# There is a similar text in GNOME Boxes (https://gitlab.gnome.org/GNOME/gnome-boxes/tree/master/po) msgid "System"
msgid "Grab/ungrab keyboard and pointer"
msgstr "" msgstr ""
msgid "Erase all drawings" msgid "Dashed line"
msgstr "" msgstr ""
msgid "Persistent" #. Translators: as the alternative to "Dashed line"
msgid "Full line"
msgstr "" msgstr ""
msgid "Persistent drawing through session restart" msgid "Fill"
msgstr "" msgstr ""
msgid "Drawing on the desktop" #. Translators: as the alternative to "Fill"
msgid "Outline"
msgstr "" msgstr ""
msgid "<i>Draw On Your Screen</i> becomes <i>Draw On Your Desktop</i>" #. Translators: fill-rule SVG attribute
msgid "Nonzero"
msgstr "" msgstr ""
msgid "Disable on-screen notifications" msgid "Evenodd"
msgstr "" msgstr ""
msgid "Disable panel indicator" #. Translators: generic font-family SVG attribute
msgctxt "font-family"
msgid "Sans-Serif"
msgstr ""
msgctxt "font-family"
msgid "Serif"
msgstr ""
msgctxt "font-family"
msgid "Monospace"
msgstr ""
msgctxt "font-family"
msgid "Cursive"
msgstr ""
msgctxt "font-family"
msgid "Fantasy"
msgstr ""
#. Translators: font-style SVG attribute
msgctxt "font-style"
msgid "Normal"
msgstr ""
msgctxt "font-style"
msgid "Oblique"
msgstr ""
msgctxt "font-style"
msgid "Italic"
msgstr ""
#. Translators: font-weight SVG attribute
msgctxt "font-weight"
msgid "Thin"
msgstr ""
msgctxt "font-weight"
msgid "Ultra Light"
msgstr ""
msgctxt "font-weight"
msgid "Light"
msgstr ""
msgctxt "font-weight"
msgid "Semi Light"
msgstr ""
msgctxt "font-weight"
msgid "Book"
msgstr ""
msgctxt "font-weight"
msgid "Normal"
msgstr ""
msgctxt "font-weight"
msgid "Medium"
msgstr ""
msgctxt "font-weight"
msgid "Semi Bold"
msgstr ""
msgctxt "font-weight"
msgid "Bold"
msgstr ""
msgctxt "font-weight"
msgid "Ultra Bold"
msgstr ""
msgctxt "font-weight"
msgid "Heavy"
msgstr ""
msgctxt "font-weight"
msgid "Ultra Heavy"
msgstr ""
#. Translators: stroke-linecap SVG attribute
msgctxt "stroke-linecap"
msgid "Butt"
msgstr ""
msgctxt "stroke-linecap"
msgid "Round"
msgstr ""
msgctxt "stroke-linecap"
msgid "Square"
msgstr ""
#. Translators: stroke-linejoin SVG attribute
msgctxt "stroke-linejoin"
msgid "Miter"
msgstr ""
msgctxt "stroke-linejoin"
msgid "Round"
msgstr ""
msgctxt "stroke-linejoin"
msgid "Bevel"
msgstr ""
#. Translators: value in pixel unit (e.g. "5 px")
#, javascript-format
msgid "%f px"
msgstr ""
#. Translators: text alignment
msgid "Right aligned"
msgstr ""
msgid "Left aligned"
msgstr ""
msgctxt "drawing-tool"
msgid "Free drawing"
msgstr ""
msgctxt "drawing-tool"
msgid "Line"
msgstr ""
msgctxt "drawing-tool"
msgid "Ellipse"
msgstr ""
msgctxt "drawing-tool"
msgid "Rectangle"
msgstr ""
msgctxt "drawing-tool"
msgid "Text"
msgstr ""
msgctxt "drawing-tool"
msgid "Polygon"
msgstr ""
msgctxt "drawing-tool"
msgid "Polyline"
msgstr ""
msgctxt "drawing-tool"
msgid "Image"
msgstr ""
msgctxt "drawing-tool"
msgid "Move"
msgstr ""
msgctxt "drawing-tool"
msgid "Resize"
msgstr ""
msgctxt "drawing-tool"
msgid "Mirror"
msgstr ""
msgid "Undo"
msgstr ""
msgid "Redo"
msgstr ""
msgid "Erase"
msgstr ""
msgid "Smooth"
msgstr ""
msgid "Open drawing"
msgstr ""
msgid "Save drawing as…"
msgstr ""
msgid "Palette"
msgstr ""
msgid "Color"
msgstr ""
msgid "Type a name"
msgstr ""
#. Translators: "Preferences" page in preferences
msgid "Preferences"
msgstr ""
#. Translators: "Drawing" page in preferences
msgid "Drawing"
msgstr ""
#. Translators: "About" page in preferences
msgid "About"
msgstr ""
#. Translators: you are free to translate the extension name, that is displayed in About page, or not
msgid "Draw On You Screen"
msgstr ""
#. Translators: version number in "About" page
#, javascript-format
msgid "Version %f"
msgstr ""
#. Translators: you are free to translate the extension description, that is displayed in About page, or not
msgid ""
"Start drawing with Super+Alt+D and save your beautiful work by taking a "
"screenshot"
msgstr ""
#. Translators: add your name here or keep it empty, it will be displayed in about page, e.g.
#. msgstr ""
#. "translator1\n"
#. "<a href=\"mailto:translator2@mail.org\">translator2</a>\n"
#. "<a href=\"https://...\">translator3</a>"
msgid "translator-credits"
msgstr ""
msgid "Palettes"
msgstr ""
msgid "Add a new palette"
msgstr ""
msgid "Area"
msgstr ""
msgid "Auto"
msgstr ""
msgid "Grid overlay line"
msgstr ""
msgid "Tools"
msgstr ""
msgid "Dash array"
msgstr ""
msgid "Reset settings"
msgstr ""
msgid "Rename the palette"
msgstr ""
msgid "Remove the palette"
msgstr ""
#. Translators: default name of a new palette
msgid "New palette"
msgstr ""
msgid "In drawing mode"
msgstr "" msgstr ""
msgid "Draw" msgid "Draw"
@ -102,17 +380,19 @@ msgstr ""
msgid "Scroll" msgid "Scroll"
msgstr "" msgstr ""
#. Translators: %s are key labels (Ctrl+F1 and Ctrl+F9)
msgid "Select color" msgid "Select color"
msgstr "" msgstr ""
# %s are key labels (Ctrl+F1 and Ctrl+F9) #, javascript-format
msgid "%s … %s" msgid "%s … %s"
msgstr "" msgstr ""
#. Translators: %s is a key label
msgid "Ignore pointer movement" msgid "Ignore pointer movement"
msgstr "" msgstr ""
# %s is a key label #, javascript-format
msgid "%s held" msgid "%s held"
msgstr "" msgstr ""
@ -137,6 +417,9 @@ msgstr ""
msgid "Smooth free drawing outline" msgid "Smooth free drawing outline"
msgstr "" msgstr ""
msgid "Unlock image ratio"
msgstr ""
msgid "Rotate <span alpha=\"50%\">(while moving)</span>" msgid "Rotate <span alpha=\"50%\">(while moving)</span>"
msgstr "" msgstr ""
@ -146,73 +429,224 @@ msgstr ""
msgid "Inverse <span alpha=\"50%\">(while mirroring)</span>" msgid "Inverse <span alpha=\"50%\">(while mirroring)</span>"
msgstr "" msgstr ""
msgid "Internal" msgid "Drawing on the desktop"
msgstr "" msgstr ""
msgid "(in drawing mode)" msgid "<i>Draw On Your Screen</i> becomes <i>Draw On Your Desktop</i>"
msgstr "" msgstr ""
msgid "Undo last brushstroke" msgid "Erase all drawings"
msgstr "" msgstr ""
msgid "Redo last brushstroke" msgid "Disable panel indicator"
msgstr "" msgstr ""
msgid "Erase last brushstroke" msgid "Disable on-screen notifications"
msgstr "" msgstr ""
msgid "Smooth last brushstroke" msgid "Persistent over toggles"
msgstr "" msgstr ""
msgid "Select line" msgid "Drawing remains when toggling drawing mode"
msgstr "" msgstr ""
msgid "Select ellipse" msgid "Persistent over restarts"
msgstr "" msgstr ""
msgid "Select rectangle" msgid "Drawing is automatically saved to a file"
msgstr "" msgstr ""
msgid "Select polygon" msgid "Enter/leave drawing mode"
msgstr "" msgstr ""
msgid "Select polyline" #. Translators: there is a similar text in GNOME Boxes (https://gitlab.gnome.org/GNOME/gnome-boxes/tree/master/po)
msgid "Grab/ungrab keyboard and pointer"
msgstr "" msgstr ""
msgid "Select image" msgid "Background color"
msgstr "" msgstr ""
msgid "Select text" msgid "The color of the drawing area background"
msgstr "" msgstr ""
msgid "Select move" msgid "Automatic dash array"
msgstr "" msgstr ""
msgid "Select resize" msgid "Compute the lengths from the line width"
msgstr "" msgstr ""
msgid "Select mirror" msgid "Dash array on"
msgstr "" msgstr ""
msgid "Toggle fill/outline" msgid "The dash length in pixels"
msgstr "" msgstr ""
msgid "Increment line width" msgid "Dash array off"
msgstr ""
msgid "The gap between the dashes in pixels"
msgstr ""
msgid "Dash offset"
msgstr ""
msgid "The dash offset in pixels"
msgstr ""
msgid "Grid overlay color"
msgstr ""
msgid "The color of the lines"
msgstr ""
msgid "Automatic grid overlay line"
msgstr ""
msgid "Compute the lengths from the screen size"
msgstr ""
msgid "Grid overlay line spacing"
msgstr ""
msgid "The gap between lines in pixels"
msgstr ""
msgid "Grid overlay line width"
msgstr ""
msgid "The line width in pixels"
msgstr ""
msgid "Image location"
msgstr ""
msgid "The location of the directory in which the image tool picks"
msgstr ""
msgid "Color palettes"
msgstr ""
msgid "The palettes of drawing colors"
msgstr ""
msgid "Automatic square area size"
msgstr ""
msgid "Compute the area size from the screen size"
msgstr ""
msgid "Square area size"
msgstr ""
msgid "The size of the area in pixels"
msgstr "" msgstr ""
msgid "Decrement line width" msgid "Decrement line width"
msgstr "" msgstr ""
msgid "Increment line width even more"
msgstr ""
msgid "Decrement line width even more" msgid "Decrement line width even more"
msgstr "" msgstr ""
msgid "Change linejoin" msgid "Erase last brushstroke"
msgstr "" msgstr ""
msgid "Change linecap" msgid "Export drawing to a SVG file"
msgstr ""
msgid "Increment line width"
msgstr ""
msgid "Increment line width even more"
msgstr ""
msgid "Open next drawing"
msgstr ""
msgid "Open preferences"
msgstr ""
msgid "Open previous drawing"
msgstr ""
msgid "Add images from the clipboard"
msgstr ""
msgid "Redo last brushstroke"
msgstr ""
msgid "Save drawing"
msgstr ""
msgid "Select color 1"
msgstr ""
msgid "Select color 2"
msgstr ""
msgid "Select color 3"
msgstr ""
msgid "Select color 4"
msgstr ""
msgid "Select color 5"
msgstr ""
msgid "Select color 6"
msgstr ""
msgid "Select color 7"
msgstr ""
msgid "Select color 8"
msgstr ""
msgid "Select color 9"
msgstr ""
msgid "Select ellipse tool"
msgstr ""
msgid "Select image tool"
msgstr ""
msgid "Select line tool"
msgstr ""
msgid "Select mirror tool"
msgstr ""
msgid "Select move tool"
msgstr ""
msgid "Select free drawing"
msgstr ""
msgid "Select polygon tool"
msgstr ""
msgid "Select polyline tool"
msgstr ""
msgid "Select rectangle tool"
msgstr ""
msgid "Select resize tool"
msgstr ""
msgid "Select text tool"
msgstr ""
msgid "Smooth last brushstroke"
msgstr ""
msgid "Change color palette"
msgstr ""
msgid "Change color palette (reverse)"
msgstr ""
msgid "Toggle fill/outline"
msgstr "" msgstr ""
msgid "Toggle fill rule" msgid "Toggle fill rule"
@ -224,289 +658,42 @@ msgstr ""
msgid "Change font family (reverse)" msgid "Change font family (reverse)"
msgstr "" msgstr ""
msgid "Change font weight"
msgstr ""
msgid "Change font style" msgid "Change font style"
msgstr "" msgstr ""
msgid "Change font weight"
msgstr ""
msgid "Change image"
msgstr ""
msgid "Change image (reverse)"
msgstr ""
msgid "Change linecap"
msgstr ""
msgid "Change linejoin"
msgstr ""
msgid "Toggle text alignment" msgid "Toggle text alignment"
msgstr "" msgstr ""
msgid "Change image file"
msgstr ""
msgid "Hide panel and dock"
msgstr ""
msgid "Add a drawing background" msgid "Add a drawing background"
msgstr "" msgstr ""
msgid "Add a grid overlay" msgid "Add a grid overlay"
msgstr "" msgstr ""
msgid "Square drawing area"
msgstr ""
msgid "Open previous drawing"
msgstr ""
msgid "Open next drawing"
msgstr ""
msgid "Save drawing as a SVG file"
msgstr ""
msgid "Edit style"
msgstr ""
msgid "Open preferences"
msgstr ""
msgid "Show help" msgid "Show help"
msgstr "" msgstr ""
msgid "" msgid "Hide panel and dock"
"<b>Default</b> drawing style attributes (color palette, font, line, dash) are defined in an editable <b>css</b> file.\n"
"See <i>“%s”</i>."
msgstr "" msgstr ""
msgid "" #. Translators: It is an action: "Make the drawing area a square"
"When you save elements made with <b>eraser</b> in a <b>SVG</b> file, " msgid "Square drawing area"
"they are colored with background color, transparent if it is disabled.\n"
"See <i>“%s”</i> or edit the SVG file afterwards."
msgstr "" msgstr ""
msgid "Screenshot" msgid "Undo last brushstroke"
msgstr "" msgstr ""
msgid "Screenshot to clipboard"
msgstr ""
msgid "Area screenshot"
msgstr ""
msgid "Area screenshot to clipboard"
msgstr ""
msgid "System"
msgstr ""
msgid "Undo"
msgstr ""
msgid "Redo"
msgstr ""
msgid "Erase"
msgstr ""
msgid "Smooth"
msgstr ""
msgid "Free drawing"
msgstr ""
msgid "Line"
msgstr ""
msgid "Ellipse"
msgstr ""
msgid "Rectangle"
msgstr ""
msgid "Text"
msgstr ""
msgid "Polygon"
msgstr ""
msgid "Polyline"
msgstr ""
msgid "Image"
msgstr ""
msgid "Move"
msgstr ""
msgid "Resize"
msgstr ""
msgid "Mirror"
msgstr ""
msgid "Color"
msgstr ""
msgid "Fill"
msgstr ""
# fill-rule SVG attribute
msgid "Evenodd"
msgstr ""
msgid "%d px"
msgstr ""
# stroke-linejoin SVG attribute
msgid "Miter"
msgstr ""
# stroke-linejoin and stroke-linecap SVG attribute
msgid "Round"
msgstr ""
# stroke-linejoin SVG attribute
msgid "Bevel"
msgstr ""
# stroke-linecap SVG attribute
msgid "Butt"
msgstr ""
# stroke-linecap SVG attribute
msgid "Square"
msgstr ""
msgid "Dashed"
msgstr ""
# generic font-family SVG attribute
msgid "Sans-Serif"
msgstr ""
# generic font-family SVG attribute
msgid "Serif"
msgstr ""
# generic font-family SVG attribute
msgid "Monospace"
msgstr ""
# generic font-family SVG attribute
msgid "Cursive"
msgstr ""
# generic font-family SVG attribute
msgid "Fantasy"
msgstr ""
# font-weight SVG attribute
msgid "Thin"
msgstr ""
# font-weight SVG attribute
msgid "Ultra-light"
msgstr ""
# font-weight SVG attribute
msgid "Light"
msgstr ""
# font-weight SVG attribute
msgid "Semi-light"
msgstr ""
# font-weight SVG attribute
msgid "Book"
msgstr ""
# font-weight and font-style SVG attribute
msgid "Normal"
msgstr ""
# font-weight SVG attribute
msgid "Medium"
msgstr ""
# font-weight SVG attribute
msgid "Semi-bold"
msgstr ""
# font-weight SVG attribute
msgid "Bold"
msgstr ""
# font-weight SVG attribute
msgid "Ultra-bold"
msgstr ""
# font-weight SVG attribute
msgid "Heavy"
msgstr ""
# font-style SVG attribute
msgid "Italic"
msgstr ""
# font-style SVG attribute
msgid "Oblique"
msgstr ""
msgid "Right aligned"
msgstr ""
msgid "Open drawing"
msgstr ""
msgid "Save drawing"
msgstr ""
msgid "Leaving drawing mode"
msgstr ""
# %s is a key label
msgid "<small>Press <i>%s</i> for help</small>"
msgstr ""
msgid "Entering drawing mode"
msgstr ""
# "released" as the opposite of "grabbed"
msgid "Keyboard and pointer released"
msgstr ""
msgid "Keyboard and pointer grabbed"
msgstr ""
# %s is a key label
msgid ""
"Press <i>%s</i> to get\n"
"a fourth control point"
msgstr ""
msgid "Mark a point of symmetry"
msgstr ""
msgid "Draw a line of symmetry"
msgstr ""
# %s is a key label
msgid ""
"Press <i>%s</i> to mark vertices"
msgstr ""
# %s is a key label
msgid ""
"Type your text and press <i>%s</i>"
msgstr ""
# as the alternative to "Fill"
msgid "Outline"
msgstr ""
msgid "Dashed line"
msgstr ""
msgid "Full line"
msgstr ""
msgid "Left aligned"
msgstr ""
msgid "Nonzero"
msgstr ""

577
menu.js
View File

@ -1,4 +1,5 @@
/* jslint esversion: 6 */ /* jslint esversion: 6 */
/* exported DisplayStrings, DrawingMenu */
/* /*
* Copyright 2019 Abakkk * Copyright 2019 Abakkk
@ -21,7 +22,6 @@
*/ */
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
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;
@ -30,44 +30,121 @@ const St = imports.gi.St;
const BoxPointer = imports.ui.boxpointer; const BoxPointer = imports.ui.boxpointer;
const Config = imports.misc.config; const Config = imports.misc.config;
const Dash = imports.ui.dash;
const Main = imports.ui.main; const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu; const PopupMenu = imports.ui.popupMenu;
const Slider = imports.ui.slider; const Slider = imports.ui.slider;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension(); const Me = ExtensionUtils.getCurrentExtension();
const Area = Me.imports.area;
const Elements = Me.imports.elements;
const Extension = Me.imports.extension;
const Files = Me.imports.files; const Files = Me.imports.files;
const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext;
const pgettext = imports.gettext.domain(Me.metadata['gettext-domain']).pgettext;
const GS_VERSION = Config.PACKAGE_VERSION; const GS_VERSION = Config.PACKAGE_VERSION;
const ICON_DIR = Me.dir.get_child('data').get_child('icons');
const SMOOTH_ICON_PATH = ICON_DIR.get_child('smooth-symbolic.svg').get_path();
const COLOR_ICON_PATH = ICON_DIR.get_child('color-symbolic.svg').get_path();
const FILL_ICON_PATH = ICON_DIR.get_child('fill-symbolic.svg').get_path();
const STROKE_ICON_PATH = ICON_DIR.get_child('stroke-symbolic.svg').get_path();
const LINEJOIN_ICON_PATH = ICON_DIR.get_child('linejoin-symbolic.svg').get_path();
const LINECAP_ICON_PATH = ICON_DIR.get_child('linecap-symbolic.svg').get_path();
const FILLRULE_NONZERO_ICON_PATH = ICON_DIR.get_child('fillrule-nonzero-symbolic.svg').get_path();
const FILLRULE_EVENODD_ICON_PATH = ICON_DIR.get_child('fillrule-evenodd-symbolic.svg').get_path();
const DASHED_LINE_ICON_PATH = ICON_DIR.get_child('dashed-line-symbolic.svg').get_path();
const FULL_LINE_ICON_PATH = ICON_DIR.get_child('full-line-symbolic.svg').get_path();
// 150 labels with font-family style take ~15Mo // 150 labels with font-family style take ~15Mo
const FONT_FAMILY_STYLE = true; const FONT_FAMILY_STYLE = true;
// use 'login-dialog-message-warning' class in order to get GS theme warning color (default: #f57900)
const WARNING_COLOR_STYLE_CLASS_NAME = 'login-dialog-message-warning';
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;
}; };
const getSummary = function(settingKey) {
return Me.internalShortcutSettings.settings_schema.get_key(settingKey).get_summary();
};
// Used by both menu and osd notifications.
var DisplayStrings = {
getDashedLine: function(dashed) {
return dashed ? _("Dashed line") :
// Translators: as the alternative to "Dashed line"
_("Full line");
},
getFill: function(fill) {
return fill ? _("Fill") :
// Translators: as the alternative to "Fill"
_("Outline");
},
get FillRule() {
if (!this._fillRules)
// Translators: fill-rule SVG attribute
this._fillRules = { 0: _("Nonzero"), 1: _("Evenodd") };
return this._fillRules;
},
getFontFamily: function(family) {
if (!this._fontGenericFamilies)
// Translators: generic font-family SVG attribute
this._fontGenericFamilies = { 'Sans-Serif': pgettext("font-family", "Sans-Serif"), 'Serif': pgettext("font-family", "Serif"),
'Monospace': pgettext("font-family", "Monospace"), 'Cursive': pgettext("font-family", "Cursive"),
'Fantasy': pgettext("font-family", "Fantasy") };
return this._fontGenericFamilies[family] || family;
},
get FontStyle() {
if (!this._fontStyles)
// Translators: font-style SVG attribute
this._fontStyles = { 0: pgettext("font-style", "Normal"), 1: pgettext("font-style", "Oblique"), 2: pgettext("font-style", "Italic") };
return this._fontStyles;
},
FontStyleMarkup: { 0: 'normal', 1: 'oblique', 2: 'italic' },
get FontWeight() {
if (!this._fontWeights)
// Translators: font-weight SVG attribute
this._fontWeights = { 100: pgettext("font-weight", "Thin"), 200: pgettext("font-weight", "Ultra Light"), 300: pgettext("font-weight", "Light"),
350: pgettext("font-weight", "Semi Light"), 380: pgettext("font-weight", "Book"), 400: pgettext("font-weight", "Normal"),
500: pgettext("font-weight", "Medium"), 600: pgettext("font-weight", "Semi Bold"), 700: pgettext("font-weight", "Bold"),
800: pgettext("font-weight", "Ultra Bold"), 900: pgettext("font-weight", "Heavy"), 1000: pgettext("font-weight", "Ultra Heavy") };
return this._fontWeights;
},
get LineCap() {
if (!this._lineCaps)
// Translators: stroke-linecap SVG attribute
this._lineCaps = { 0: pgettext("stroke-linecap", "Butt"), 1: pgettext("stroke-linecap", "Round"), 2: pgettext("stroke-linecap", "Square") };
return this._lineCaps;
},
get LineJoin() {
if (!this._lineJoins)
// Translators: stroke-linejoin SVG attribute
this._lineJoins = { 0: pgettext("stroke-linejoin", "Miter"), 1: pgettext("stroke-linejoin", "Round"), 2: pgettext("stroke-linejoin", "Bevel") };
return this._lineJoins;
},
getPixels(value) {
// Translators: value in pixel unit (e.g. "5 px")
return _("%f px").format(value);
},
getTextAlignment: function(rightAligned) {
// Translators: text alignment
return rightAligned ? _("Right aligned") : _("Left aligned");
},
get Tool() {
if (!this._tools)
this._tools = { 0: pgettext("drawing-tool", "Free drawing"), 1: pgettext("drawing-tool", "Line"), 2: pgettext("drawing-tool", "Ellipse"),
3: pgettext("drawing-tool", "Rectangle"), 4: pgettext("drawing-tool", "Text"), 5: pgettext("drawing-tool", "Polygon"),
6: pgettext("drawing-tool", "Polyline"), 7: pgettext("drawing-tool", "Image"),
100: pgettext("drawing-tool", "Move"), 101: pgettext("drawing-tool", "Resize"), 102: pgettext("drawing-tool", "Mirror") };
return this._tools;
}
};
var DrawingMenu = new Lang.Class({ var DrawingMenu = new Lang.Class({
Name: 'DrawOnYourScreenDrawingMenu', Name: 'DrawOnYourScreenDrawingMenu',
_init: function(area, monitor) { _init: function(area, monitor, drawingTools) {
this.area = area; this.area = area;
this.drawingTools = drawingTools;
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;
this.menu = new PopupMenu.PopupMenu(Main.layoutManager.dummyCursor, 0.25, side); this.menu = new PopupMenu.PopupMenu(Main.layoutManager.dummyCursor, 0.25, side);
this.menuManager = new PopupMenu.PopupMenuManager(GS_VERSION < '3.33.0' ? { actor: this.area } : this.area); this.menuManager = new PopupMenu.PopupMenuManager(GS_VERSION < '3.33.0' ? { actor: this.area } : this.area);
@ -94,20 +171,11 @@ var DrawingMenu = new Lang.Class({
this.saveDrawingSubMenu.close(); this.saveDrawingSubMenu.close();
menuCloseFunc.bind(this.menu)(animate); menuCloseFunc.bind(this.menu)(animate);
}; };
this.colorIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(COLOR_ICON_PATH) });
this.smoothIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(SMOOTH_ICON_PATH) });
this.strokeIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(STROKE_ICON_PATH) });
this.fillIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(FILL_ICON_PATH) });
this.fillRuleNonzeroIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(FILLRULE_NONZERO_ICON_PATH) });
this.fillRuleEvenoddIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(FILLRULE_EVENODD_ICON_PATH) });
this.linejoinIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(LINEJOIN_ICON_PATH) });
this.linecapIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(LINECAP_ICON_PATH) });
this.fullLineIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(FULL_LINE_ICON_PATH) });
this.dashedLineIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(DASHED_LINE_ICON_PATH) });
}, },
disable: function() { disable: function() {
delete this.area;
delete this.drawingTools;
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);
this.menu.destroy(); this.menu.destroy();
@ -154,106 +222,101 @@ var DrawingMenu = new Lang.Class({
_redisplay: function() { _redisplay: function() {
this.menu.removeAll(); this.menu.removeAll();
this.actionButtons = []; let groupItem = new PopupMenu.PopupBaseMenuItem({ reactive: false, can_focus: false, style_class: 'draw-on-your-screen-menu-group-item' });
let groupItem = new PopupMenu.PopupBaseMenuItem({ reactive: false, can_focus: false, style_class: "draw-on-your-screen-menu-group-item" }); this.undoButton = new ActionButton(_("Undo"), 'edit-undo-symbolic', this.area.undo.bind(this.area), this._updateActionSensitivity.bind(this));
getActor(groupItem).add_child(this._createActionButton(_("Undo"), this.area.undo.bind(this.area), 'edit-undo-symbolic')); this.redoButton = new ActionButton(_("Redo"), 'edit-redo-symbolic', this.area.redo.bind(this.area), this._updateActionSensitivity.bind(this));
getActor(groupItem).add_child(this._createActionButton(_("Redo"), this.area.redo.bind(this.area), 'edit-redo-symbolic')); this.eraseButton = new ActionButton(_("Erase"), 'edit-clear-all-symbolic', this.area.deleteLastElement.bind(this.area), this._updateActionSensitivity.bind(this));
getActor(groupItem).add_child(this._createActionButton(_("Erase"), this.area.deleteLastElement.bind(this.area), 'edit-clear-all-symbolic')); this.smoothButton = new ActionButton(_("Smooth"), Files.Icons.SMOOTH, this.area.smoothLastElement.bind(this.area), this._updateActionSensitivity.bind(this));
getActor(groupItem).add_child(this._createActionButton(_("Smooth"), this.area.smoothLastElement.bind(this.area), this.smoothIcon)); this.eraseButton.child.add_style_class_name('draw-on-your-screen-menu-destructive-button');
this.smoothButton.child.add_style_class_name('draw-on-your-screen-menu-destructive-button');
getActor(groupItem).add_child(this.undoButton);
getActor(groupItem).add_child(this.redoButton);
getActor(groupItem).add_child(this.eraseButton);
getActor(groupItem).add_child(this.smoothButton);
this.menu.addMenuItem(groupItem); this.menu.addMenuItem(groupItem);
this._addSeparator(this.menu, true); this._addSeparator(this.menu, true);
this._addSubMenuItem(this.menu, 'document-edit-symbolic', Area.ToolNames, this.area, 'currentTool', this._updateSectionVisibility.bind(this)); this.toolItem = this._addToolSubMenuItem(this.menu, this._updateSectionVisibility.bind(this));
this.colorItem = this._addColorSubMenuItem(this.menu); this.paletteItem = this._addPaletteSubMenuItem(this.menu, Files.Icons.PALETTE);
this.fillItem = this._addSwitchItem(this.menu, _("Fill"), this.strokeIcon, this.fillIcon, this.area, 'fill', this._updateSectionVisibility.bind(this)); this.colorItem = this._addColorSubMenuItem(this.menu, Files.Icons.COLOR);
this.fillItem = this._addSwitchItem(this.menu, DisplayStrings.getFill(true), Files.Icons.STROKE, Files.Icons.FILL, this.area, 'fill', this._updateSectionVisibility.bind(this));
this.fillSection = new PopupMenu.PopupMenuSection(); this.fillSection = new PopupMenu.PopupMenuSection();
this.fillSection.itemActivated = () => {}; this.fillSection.itemActivated = () => {};
this.fillRuleItem = this._addSwitchItem(this.fillSection, _("Evenodd"), this.fillRuleNonzeroIcon, this.fillRuleEvenoddIcon, this.area, 'currentEvenodd'); this.fillRuleItem = this._addSwitchItem(this.fillSection, DisplayStrings.FillRule[1], Files.Icons.FILLRULE_NONZERO, Files.Icons.FILLRULE_EVENODD, this.area, 'currentEvenodd');
this.menu.addMenuItem(this.fillSection); this.menu.addMenuItem(this.fillSection);
this._addSeparator(this.menu); this._addSeparator(this.menu);
let lineSection = new PopupMenu.PopupMenuSection(); let lineSection = new PopupMenu.PopupMenuSection();
this._addSliderItem(lineSection, this.area, 'currentLineWidth'); this._addSliderItem(lineSection, this.area, 'currentLineWidth');
this._addSubMenuItem(lineSection, this.linejoinIcon, Elements.LineJoinNames, this.area, 'currentLineJoin'); this._addSubMenuItem(lineSection, Files.Icons.LINEJOIN, DisplayStrings.LineJoin, this.area, 'currentLineJoin');
this._addSubMenuItem(lineSection, this.linecapIcon, Elements.LineCapNames, this.area, 'currentLineCap'); this._addSubMenuItem(lineSection, Files.Icons.LINECAP, DisplayStrings.LineCap, this.area, 'currentLineCap');
this._addSwitchItem(lineSection, _("Dashed"), this.fullLineIcon, this.dashedLineIcon, this.area, 'dashedLine'); this._addSwitchItem(lineSection, DisplayStrings.getDashedLine(true), Files.Icons.FULL_LINE, Files.Icons.DASHED_LINE, this.area, 'dashedLine');
this._addSeparator(lineSection); this._addSeparator(lineSection);
this.menu.addMenuItem(lineSection); this.menu.addMenuItem(lineSection);
lineSection.itemActivated = () => {}; lineSection.itemActivated = () => {};
this.lineSection = lineSection; this.lineSection = lineSection;
let fontSection = new PopupMenu.PopupMenuSection(); let fontSection = new PopupMenu.PopupMenuSection();
this._addFontFamilySubMenuItem(fontSection, 'font-x-generic-symbolic'); this._addFontFamilySubMenuItem(fontSection, Files.Icons.FONT_FAMILY);
this._addSubMenuItem(fontSection, 'format-text-bold-symbolic', Elements.FontWeightNames, this.area, 'currentFontWeight'); this._addSubMenuItem(fontSection, Files.Icons.FONT_WEIGHT, DisplayStrings.FontWeight, this.area, 'currentFontWeight');
this._addSubMenuItem(fontSection, 'format-text-italic-symbolic', Elements.FontStyleNames, this.area, 'currentFontStyle'); this._addSubMenuItem(fontSection, Files.Icons.FONT_STYLE, DisplayStrings.FontStyle, this.area, 'currentFontStyle');
this._addSwitchItem(fontSection, _("Right aligned"), 'format-justify-left-symbolic', 'format-justify-right-symbolic', this.area, 'currentTextRightAligned'); this._addSwitchItem(fontSection, DisplayStrings.getTextAlignment(true), Files.Icons.LEFT_ALIGNED, Files.Icons.RIGHT_ALIGNED, this.area, 'currentTextRightAligned');
this._addSeparator(fontSection); this._addSeparator(fontSection);
this.menu.addMenuItem(fontSection); this.menu.addMenuItem(fontSection);
fontSection.itemActivated = () => {}; fontSection.itemActivated = () => {};
this.fontSection = fontSection; this.fontSection = fontSection;
let imageSection = new PopupMenu.PopupMenuSection(); let imageSection = new PopupMenu.PopupMenuSection();
let images = this.area.getImages(); this.imageItem = this._addImageSubMenuItem(imageSection);
if (images.length) {
if (this.area.currentImage > images.length - 1)
this.area.currentImage = images.length - 1;
this._addSubMenuItem(imageSection, null, images, this.area, 'currentImage');
}
this._addSeparator(imageSection); this._addSeparator(imageSection);
this.menu.addMenuItem(imageSection); this.menu.addMenuItem(imageSection);
imageSection.itemActivated = () => {}; imageSection.itemActivated = () => {};
this.imageSection = imageSection; this.imageSection = imageSection;
let manager = Extension.manager; let areaManager = Me.stateObj.areaManager;
this._addSimpleSwitchItem(this.menu, _("Hide panel and dock"), manager.hiddenList ? true : false, manager.togglePanelAndDockOpacity.bind(manager)); this._addSimpleSwitchItem(this.menu, getSummary('toggle-panel-and-dock-visibility'), !!areaManager.hiddenList, areaManager.togglePanelAndDockOpacity.bind(areaManager));
this._addSimpleSwitchItem(this.menu, _("Add a drawing background"), this.area.hasBackground, this.area.toggleBackground.bind(this.area)); this._addSimpleSwitchItem(this.menu, getSummary('toggle-background'), this.area.hasBackground, this.area.toggleBackground.bind(this.area));
this._addSimpleSwitchItem(this.menu, _("Add a grid overlay"), this.area.hasGrid, this.area.toggleGrid.bind(this.area)); this._addSimpleSwitchItem(this.menu, getSummary('toggle-grid'), this.area.hasGrid, this.area.toggleGrid.bind(this.area));
this._addSimpleSwitchItem(this.menu, _("Square drawing area"), this.area.isSquareArea, this.area.toggleSquareArea.bind(this.area)); this._addSimpleSwitchItem(this.menu, getSummary('toggle-square-area'), this.area.isSquareArea, this.area.toggleSquareArea.bind(this.area));
this._addSeparator(this.menu); this._addSeparator(this.menu);
this._addDrawingNameItem(this.menu); this._addDrawingNameItem(this.menu);
this._addOpenDrawingSubMenuItem(this.menu); this._addOpenDrawingSubMenuItem(this.menu, _("Open drawing"), 'document-open-symbolic');
this._addSaveDrawingSubMenuItem(this.menu); this._addSaveDrawingSubMenuItem(this.menu, _("Save drawing as…"), 'document-save-as-symbolic');
this._addSeparator(this.menu);
this.menu.addAction(_("Save drawing as a SVG file"), this.area.saveAsSvg.bind(this.area), 'image-x-generic-symbolic'); groupItem = new PopupMenu.PopupBaseMenuItem({ reactive: false, can_focus: false, style_class: 'draw-on-your-screen-menu-group-item' });
this.menu.addAction(_("Edit style"), manager.openUserStyleFile.bind(manager), 'document-page-setup-symbolic'); this.saveButton = new ActionButton(getSummary('save-as-json'), 'document-save-symbolic', this.area.saveAsJson.bind(this.area, false, this._onDrawingSaved.bind(this)), null);
this.menu.addAction(_("Show help"), () => { this.close(); this.area.toggleHelp(); }, 'preferences-desktop-keyboard-shortcuts-symbolic'); this.svgButton = new ActionButton(getSummary('export-to-svg'), Files.Icons.DOCUMENT_EXPORT, this.area.exportToSvg.bind(this.area), null);
this.prefsButton = new ActionButton(getSummary('open-preferences'), 'document-page-setup-symbolic', areaManager.openPreferences.bind(areaManager), null);
this.helpButton = new ActionButton(getSummary('toggle-help'), 'preferences-desktop-keyboard-shortcuts-symbolic', () => { this.close(); this.area.toggleHelp(); }, null);
getActor(groupItem).add_child(this.saveButton);
getActor(groupItem).add_child(this.svgButton);
getActor(groupItem).add_child(this.prefsButton);
getActor(groupItem).add_child(this.helpButton);
this.menu.addMenuItem(groupItem);
this._updateActionSensitivity(); this._updateActionSensitivity();
this._updateSectionVisibility(); this._updateSectionVisibility();
}, },
// from system.js (GS 3.34-)
_createActionButton: function(accessibleName, callback, icon) {
let button = new St.Button({ track_hover: true,
x_align: Clutter.ActorAlign.CENTER,
accessible_name: accessibleName,
// use 'popup-menu' and 'popup-menu-item' style classes to provide theme colors
style_class: 'system-menu-action popup-menu-item popup-menu' });
button.child = new St.Icon(typeof icon == 'string' ? { icon_name: icon } : { gicon: icon });
button.connect('clicked', () => {
callback();
this._updateActionSensitivity();
});
button.bind_property('reactive', button, 'can_focus', GObject.BindingFlags.DEFAULT);
this.actionButtons.push(button);
return new St.Bin({ child: button, x_expand: true });
},
_updateActionSensitivity: function() { _updateActionSensitivity: function() {
let [undoButton, redoButton, eraseButton, smoothButton] = this.actionButtons; this.undoButton.child.reactive = this.area.elements.length > 0;
undoButton.reactive = this.area.elements.length > 0; this.redoButton.child.reactive = this.area.undoneElements.length > 0;
redoButton.reactive = this.area.undoneElements.length > 0; this.eraseButton.child.reactive = this.area.elements.length > 0;
eraseButton.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;
smoothButton.reactive = this.area.elements.length > 0 && this.area.elements[this.area.elements.length - 1].shape == Area.Tools.NONE; this.saveButton.child.reactive = this.area.elements.length > 0;
this.svgButton.child.reactive = this.area.elements.length > 0;
this.saveDrawingSubMenuItem.setSensitive(this.area.elements.length > 0);
}, },
_updateSectionVisibility: function() { _updateSectionVisibility: function() {
let [isText, isImage] = [this.area.currentTool == Area.Tools.TEXT, this.area.currentTool == Area.Tools.IMAGE]; let [isText, isImage] = [this.area.currentTool == this.drawingTools.TEXT, this.area.currentTool == this.drawingTools.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;
this.colorItem.setSensitive(!isImage); this.colorItem.setSensitive(!isImage);
this.paletteItem.setSensitive(!isImage);
this.fillItem.setSensitive(!isText && !isImage); this.fillItem.setSensitive(!isText && !isImage);
this.fillSection.setSensitive(!isText && !isImage); this.fillSection.setSensitive(!isText && !isImage);
@ -269,18 +332,14 @@ var DrawingMenu = new Lang.Class({
item.icon = new St.Icon({ style_class: 'popup-menu-icon' }); item.icon = new St.Icon({ style_class: 'popup-menu-icon' });
getActor(item).insert_child_at_index(item.icon, 1); getActor(item).insert_child_at_index(item.icon, 1);
let icon = target[targetProperty] ? iconTrue : iconFalse; let icon = target[targetProperty] ? iconTrue : iconFalse;
if (icon && icon instanceof GObject.Object && GObject.type_is_a(icon, Gio.Icon)) if (icon)
item.icon.set_gicon(icon); item.icon.set_gicon(icon);
else if (icon)
item.icon.set_icon_name(icon);
item.connect('toggled', (item, state) => { item.connect('toggled', (item, state) => {
target[targetProperty] = state; target[targetProperty] = state;
let icon = target[targetProperty] ? iconTrue : iconFalse; let icon = target[targetProperty] ? iconTrue : iconFalse;
if (icon && icon instanceof GObject.Object && GObject.type_is_a(icon, Gio.Icon)) if (icon)
item.icon.set_gicon(icon); item.icon.set_gicon(icon);
else if (icon)
item.icon.set_icon_name(icon);
if (onToggled) if (onToggled)
onToggled(); onToggled();
}); });
@ -296,26 +355,26 @@ var DrawingMenu = new Lang.Class({
_addSliderItem: function(menu, target, targetProperty) { _addSliderItem: function(menu, target, targetProperty) {
let item = new PopupMenu.PopupBaseMenuItem({ activate: false }); let item = new PopupMenu.PopupBaseMenuItem({ activate: false });
let label = new St.Label({ text: _("%d px").format(target[targetProperty]), style_class: 'draw-on-your-screen-menu-slider-label' }); let label = new St.Label({ text: DisplayStrings.getPixels(target[targetProperty]), style_class: 'draw-on-your-screen-menu-slider-label' });
let slider = new Slider.Slider(target[targetProperty] / 50); let slider = new Slider.Slider(target[targetProperty] / 50);
if (GS_VERSION < '3.33.0') { if (GS_VERSION < '3.33.0') {
slider.connect('value-changed', (slider, value, property) => { slider.connect('value-changed', (slider, value, property) => {
target[targetProperty] = Math.max(Math.round(value * 50), 0); target[targetProperty] = Math.max(Math.round(value * 50), 0);
label.set_text(target[targetProperty] + " px"); label.set_text(DisplayStrings.getPixels(target[targetProperty]));
if (target[targetProperty] === 0) if (target[targetProperty] === 0)
label.add_style_class_name(Extension.WARNING_COLOR_STYLE_CLASS_NAME); label.add_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME);
else else
label.remove_style_class_name(Extension.WARNING_COLOR_STYLE_CLASS_NAME); label.remove_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME);
}); });
} else { } else {
slider.connect('notify::value', () => { slider.connect('notify::value', () => {
target[targetProperty] = Math.max(Math.round(slider.value * 50), 0); target[targetProperty] = Math.max(Math.round(slider.value * 50), 0);
label.set_text(target[targetProperty] + " px"); label.set_text(DisplayStrings.getPixels(target[targetProperty]));
if (target[targetProperty] === 0) if (target[targetProperty] === 0)
label.add_style_class_name(Extension.WARNING_COLOR_STYLE_CLASS_NAME); label.add_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME);
else else
label.remove_style_class_name(Extension.WARNING_COLOR_STYLE_CLASS_NAME); label.remove_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME);
}); });
} }
@ -327,94 +386,139 @@ var DrawingMenu = new Lang.Class({
menu.addMenuItem(item); menu.addMenuItem(item);
}, },
_addSubMenuItem: function(menu, icon, obj, target, targetProperty, callback) { _addSubMenuItem: function(menu, icon, obj, target, targetProperty) {
if (targetProperty == 'currentImage') let item = new PopupMenu.PopupSubMenuMenuItem(String(obj[target[targetProperty]]), icon ? true : false);
icon = obj[target[targetProperty]].gicon;
let item = new PopupMenu.PopupSubMenuMenuItem(_(String(obj[target[targetProperty]])), icon ? true : false);
if (icon && icon instanceof GObject.Object && GObject.type_is_a(icon, Gio.Icon))
item.icon.set_gicon(icon);
else if (icon)
item.icon.set_icon_name(icon);
item.menu.itemActivated = () => { item.icon.set_gicon(icon);
item.menu.close(); item.menu.itemActivated = item.menu.close;
};
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
for (let i in obj) { Object.keys(obj).forEach(key => {
let text; let text = targetProperty == 'currentFontWeight' ? `<span font_weight="${key}">${obj[key]}</span>` :
if (targetProperty == 'currentFontWeight') targetProperty == 'currentFontStyle' ? `<span font_style="${DisplayStrings.FontStyleMarkup[key]}">${obj[key]}</span>` :
text = `<span font_weight="${i}">${_(obj[i])}</span>`; String(obj[key]);
else if (targetProperty == 'currentFontStyle')
text = `<span font_style="${obj[i].toLowerCase()}">${_(obj[i])}</span>`;
else
text = _(String(obj[i]));
let iCaptured = Number(i);
let subItem = item.menu.addAction(text, () => { let subItem = item.menu.addAction(text, () => {
item.label.set_text(_(String(obj[iCaptured]))); item.label.set_text(String(obj[key]));
target[targetProperty] = iCaptured; target[targetProperty] = Number(key);
if (targetProperty == 'currentImage')
item.icon.set_gicon(obj[iCaptured].gicon);
if (callback)
callback();
}); });
subItem.label.get_clutter_text().set_use_markup(true); subItem.label.get_clutter_text().set_use_markup(true);
getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment);
});
return GLib.SOURCE_REMOVE;
});
menu.addMenuItem(item);
},
_addToolSubMenuItem: function(menu, callback) {
let item = new PopupMenu.PopupSubMenuMenuItem('', true);
item.update = () => {
item.label.set_text(DisplayStrings.Tool[this.area.currentTool]);
let toolName = this.drawingTools.getNameOf(this.area.currentTool);
item.icon.set_gicon(Files.Icons[`TOOL_${toolName}`]);
};
item.update();
item.menu.itemActivated = item.menu.close;
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
Object.keys(DisplayStrings.Tool).forEach(key => {
let text = DisplayStrings.Tool[key];
let toolName = this.drawingTools.getNameOf(key);
let subItemIcon = Files.Icons[`TOOL_${toolName}`];
let subItem = item.menu.addAction(text, () => {
this.area.currentTool = Number(key);
item.update();
callback();
}, subItemIcon);
subItem.label.get_clutter_text().set_use_markup(true);
getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment);
// change the display order of tools // change the display order of tools
if (obj == Area.ToolNames && i == Area.Tools.POLYGON) if (key == this.drawingTools.POLYGON)
item.menu.moveMenuItem(subItem, 4); item.menu.moveMenuItem(subItem, Number(this.drawingTools.TEXT));
else if (obj == Area.ToolNames && i == Area.Tools.POLYLINE) else if (key == this.drawingTools.POLYLINE)
item.menu.moveMenuItem(subItem, 5); item.menu.moveMenuItem(subItem, Number(this.drawingTools.TEXT) + 1);
} });
return GLib.SOURCE_REMOVE; return GLib.SOURCE_REMOVE;
}); });
menu.addMenuItem(item);
},
_addColorSubMenuItem: function(menu) {
let item = new PopupMenu.PopupSubMenuMenuItem(_("Color"), true);
item.icon.set_gicon(this.colorIcon);
item.icon.set_style(`color:${this.area.currentColor.to_string().slice(0, 7)};`);
item.menu.itemActivated = () => {
item.menu.close();
};
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
for (let i = 1; i < this.area.colors.length; i++) {
let text = this.area.colors[i].to_string();
let iCaptured = i;
let colorItem = item.menu.addAction(text, () => {
this.area.currentColor = this.area.colors[iCaptured];
item.icon.set_style(`color:${this.area.currentColor.to_string().slice(0, 7)};`);
});
// Foreground color markup is not displayed since 3.36, use style instead but the transparency is lost.
colorItem.label.set_style(`color:${this.area.colors[i].to_string().slice(0, 7)};`);
getActor(colorItem).connect('key-focus-in', updateSubMenuAdjustment);
}
return GLib.SOURCE_REMOVE;
});
menu.addMenuItem(item); menu.addMenuItem(item);
return item; return item;
}, },
_addFontFamilySubMenuItem: function(menu, icon) { _addPaletteSubMenuItem: function(menu, icon) {
let item = new PopupMenu.PopupSubMenuMenuItem(this.area.currentFontFamily, true); let text = _(this.area.currentPalette[0] || "Palette");
item.icon.set_icon_name(icon); let item = new PopupMenu.PopupSubMenuMenuItem(text, true);
item.icon.set_gicon(icon);
item.menu.itemActivated = () => { item.menu.itemActivated = item.menu.close;
item.menu.close();
}; GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
this.area.palettes.forEach(palette => {
let [name, colors] = palette;
if (!colors[0])
return;
let subItem = item.menu.addAction(_(name || "Palette"), () => {
item.label.set_text(_(name || "Palette"));
this.area.currentPalette = palette;
this._populateColorSubMenu();
});
getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment);
});
return GLib.SOURCE_REMOVE;
});
menu.addMenuItem(item);
return item;
},
_addColorSubMenuItem: function(menu, icon) {
let item = new PopupMenu.PopupSubMenuMenuItem(_("Color"), true);
this.colorSubMenu = item.menu;
item.icon.set_gicon(icon);
item.icon.set_style(`color:${this.area.currentColor.to_string().slice(0, 7)};`);
item.menu.itemActivated = item.menu.close;
this._populateColorSubMenu();
menu.addMenuItem(item);
return item;
},
_populateColorSubMenu: function() {
this.colorSubMenu.removeAll();
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
this.area.colors.forEach(color => {
let text = String(color);
let subItem = this.colorSubMenu.addAction(text, () => {
this.area.currentColor = color;
this.colorItem.icon.set_style(`color:${color.to_string().slice(0, 7)};`);
});
// Foreground color markup is not displayed since 3.36, use style instead but the transparency is lost.
subItem.label.set_style(`color:${color.to_string().slice(0, 7)};`);
getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment);
});
return GLib.SOURCE_REMOVE;
});
},
_addFontFamilySubMenuItem: function(menu, icon) {
let item = new PopupMenu.PopupSubMenuMenuItem(DisplayStrings.getFontFamily(this.area.currentFontFamily), true);
item.icon.set_gicon(icon);
item.menu.itemActivated = item.menu.close;
item.menu.openOld = item.menu.open; item.menu.openOld = item.menu.open;
item.menu.open = (animate) => { item.menu.open = (animate) => {
if (!item.menu.isOpen && item.menu.isEmpty()) { if (!item.menu.isOpen && item.menu.isEmpty()) {
this.area.fontFamilies.forEach(family => { this.area.fontFamilies.forEach(family => {
let subItem = item.menu.addAction(_(family), () => { let subItem = item.menu.addAction(DisplayStrings.getFontFamily(family), () => {
item.label.set_text(_(family)); item.label.set_text(DisplayStrings.getFontFamily(family));
this.area.currentFontFamily = family; this.area.currentFontFamily = family;
}); });
if (FONT_FAMILY_STYLE) if (FONT_FAMILY_STYLE)
@ -428,6 +532,34 @@ var DrawingMenu = new Lang.Class({
menu.addMenuItem(item); menu.addMenuItem(item);
}, },
_addImageSubMenuItem: function(menu, images) {
let item = new PopupMenu.PopupSubMenuMenuItem('', true);
item.update = () => {
item.label.set_text(this.area.currentImage.toString());
item.icon.set_gicon(this.area.currentImage.gicon);
};
item.update();
item.menu.itemActivated = item.menu.close;
item.menu.openOld = item.menu.open;
item.menu.open = (animate) => {
if (!item.menu.isOpen && item.menu.isEmpty()) {
Files.Images.getSorted().forEach(image => {
let subItem = item.menu.addAction(image.toString(), () => {
this.area.currentImage = image;
item.update();
}, image.thumbnailGicon || undefined);
getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment);
});
}
item.menu.openOld();
};
menu.addMenuItem(item);
return item;
},
_addDrawingNameItem: function(menu) { _addDrawingNameItem: function(menu) {
this.drawingNameMenuItem = new PopupMenu.PopupMenuItem('', { reactive: false, activate: false }); this.drawingNameMenuItem = new PopupMenu.PopupMenuItem('', { reactive: false, activate: false });
this.drawingNameMenuItem.setSensitive(false); this.drawingNameMenuItem.setSensitive(false);
@ -436,24 +568,22 @@ var DrawingMenu = new Lang.Class({
}, },
_updateDrawingNameMenuItem: function() { _updateDrawingNameMenuItem: function() {
getActor(this.drawingNameMenuItem).visible = this.area.jsonName ? true : false; getActor(this.drawingNameMenuItem).visible = this.area.currentJson ? true : false;
if (this.area.jsonName) { if (this.area.currentJson) {
let prefix = this.area.drawingContentsHasChanged ? "* " : ""; let prefix = this.area.drawingContentsHasChanged ? "* " : "";
this.drawingNameMenuItem.label.set_text(`<i>${prefix}${this.area.jsonName}</i>`); this.drawingNameMenuItem.label.set_text(`<i>${prefix}${this.area.currentJson.name}</i>`);
this.drawingNameMenuItem.label.get_clutter_text().set_use_markup(true); this.drawingNameMenuItem.label.get_clutter_text().set_use_markup(true);
} }
}, },
_addOpenDrawingSubMenuItem: function(menu) { _addOpenDrawingSubMenuItem: function(menu, label, icon) {
let item = new PopupMenu.PopupSubMenuMenuItem(_("Open drawing"), true); let item = new PopupMenu.PopupSubMenuMenuItem(label, true);
this.openDrawingSubMenuItem = item; this.openDrawingSubMenuItem = item;
this.openDrawingSubMenu = item.menu; this.openDrawingSubMenu = item.menu;
item.setSensitive(Boolean(Files.getJsons().length)); item.setSensitive(Boolean(Files.Jsons.getSorted().length));
item.icon.set_icon_name('document-open-symbolic'); item.icon.set_icon_name(icon);
item.menu.itemActivated = () => { item.menu.itemActivated = item.menu.close;
item.menu.close();
};
item.menu.openOld = item.menu.open; item.menu.openOld = item.menu.open;
item.menu.open = (animate) => { item.menu.open = (animate) => {
@ -467,13 +597,16 @@ var DrawingMenu = new Lang.Class({
_populateOpenDrawingSubMenu: function() { _populateOpenDrawingSubMenu: function() {
this.openDrawingSubMenu.removeAll(); this.openDrawingSubMenu.removeAll();
let jsons = Files.getJsons(); Files.Jsons.getSorted().forEach(json => {
jsons.forEach(json => { if (!json.gicon)
json.addSvgContents(...this.area.getSvgContentsForJson(json));
let subItem = this.openDrawingSubMenu.addAction(`<i>${String(json)}</i>`, () => { let subItem = this.openDrawingSubMenu.addAction(`<i>${String(json)}</i>`, () => {
this.area.loadJson(json.name); this.area.loadJson(json);
this._updateDrawingNameMenuItem(); this._updateDrawingNameMenuItem();
this._updateSaveDrawingSubMenuItemSensitivity(); this._updateActionSensitivity();
}); }, json.gicon);
subItem.label.get_clutter_text().set_use_markup(true); subItem.label.get_clutter_text().set_use_markup(true);
getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment);
@ -483,10 +616,22 @@ var DrawingMenu = new Lang.Class({
}); });
getActor(subItem).add_child(expander); getActor(subItem).add_child(expander);
let deleteButton = new St.Button({ style_class: 'draw-on-your-screen-menu-delete-button', let insertButton = new St.Button({ style_class: 'button draw-on-your-screen-menu-inline-button',
child: new St.Icon({ icon_name: 'insert-image-symbolic',
style_class: 'popup-menu-icon' }) });
getActor(subItem).add_child(insertButton);
insertButton.connect('clicked', () => {
this.area.currentImage = json.image;
this.imageItem.update();
this.area.currentTool = this.drawingTools.IMAGE;
this.toolItem.update();
this._updateSectionVisibility();
});
let deleteButton = new St.Button({ style_class: 'button draw-on-your-screen-menu-inline-button draw-on-your-screen-menu-destructive-button',
child: new St.Icon({ icon_name: 'edit-delete-symbolic', child: new St.Icon({ icon_name: 'edit-delete-symbolic',
style_class: 'popup-menu-icon', style_class: 'popup-menu-icon' }) });
x_align: Clutter.ActorAlign.END }) });
getActor(subItem).add_child(deleteButton); getActor(subItem).add_child(deleteButton);
deleteButton.connect('clicked', () => { deleteButton.connect('clicked', () => {
@ -499,16 +644,13 @@ var DrawingMenu = new Lang.Class({
this.openDrawingSubMenuItem.setSensitive(!this.openDrawingSubMenu.isEmpty()); this.openDrawingSubMenuItem.setSensitive(!this.openDrawingSubMenu.isEmpty());
}, },
_addSaveDrawingSubMenuItem: function(menu) { _addSaveDrawingSubMenuItem: function(menu, label, icon) {
let item = new PopupMenu.PopupSubMenuMenuItem(_("Save drawing"), true); let item = new PopupMenu.PopupSubMenuMenuItem(label, true);
this.saveDrawingSubMenuItem = item; this.saveDrawingSubMenuItem = item;
this._updateSaveDrawingSubMenuItemSensitivity();
this.saveDrawingSubMenu = item.menu; this.saveDrawingSubMenu = item.menu;
item.icon.set_icon_name('document-save-symbolic'); item.icon.set_icon_name(icon);
item.menu.itemActivated = () => { item.menu.itemActivated = item.menu.close;
item.menu.close();
};
item.menu.openOld = item.menu.open; item.menu.openOld = item.menu.open;
item.menu.open = (animate) => { item.menu.open = (animate) => {
@ -530,13 +672,14 @@ var DrawingMenu = new Lang.Class({
_populateSaveDrawingSubMenu: function() { _populateSaveDrawingSubMenu: function() {
this.saveDrawingSubMenu.removeAll(); this.saveDrawingSubMenu.removeAll();
let saveEntry = new DrawingMenuEntry({ initialTextGetter: Files.getDateString, let saveEntry = new Entry({ initialTextGetter: () => this.area.currentJson ? this.area.currentJson.name : "",
entryActivateCallback: (text) => { hint_text: _("Type a name"),
this.area.saveAsJsonWithName(text, this._onDrawingSaved.bind(this)); entryActivateCallback: (text) => {
this.saveDrawingSubMenu.toggle(); this.area.saveAsJsonWithName(text, this._onDrawingSaved.bind(this));
}, this.saveDrawingSubMenu.toggle();
invalidStrings: [Me.metadata['persistent-file-name'], '/'], },
primaryIconName: 'insert-text' }); invalidStrings: [Me.metadata['persistent-file-name'], '/'],
primaryIconName: 'insert-text' });
this.saveDrawingSubMenu.addMenuItem(saveEntry.item); this.saveDrawingSubMenu.addMenuItem(saveEntry.item);
}, },
@ -568,8 +711,51 @@ const updateSubMenuAdjustment = function(itemActor) {
adjustment.set_value(newScrollValue); adjustment.set_value(newScrollValue);
}; };
// An action button that uses upstream dash item tooltips.
const ActionButton = new Lang.Class({
Name: 'DrawOnYourScreenDrawingMenuActionButton',
Extends: St.Bin,
_labelShowing: false,
_resetHoverTimeoutId: 0,
_showLabelTimeoutId: 0,
showLabel: Dash.DashItemContainer.prototype.showLabel,
hideLabel: Dash.DashItemContainer.prototype.hideLabel,
_syncLabel: Dash.Dash.prototype._syncLabel,
_init: function(name, icon, callback, callbackAfter) {
this._labelText = name;
let button = new St.Button({ track_hover: true,
x_align: Clutter.ActorAlign.CENTER,
accessible_name: name,
// use 'popup-menu' and 'popup-menu-item' style classes to provide theme colors
//style_class: 'system-menu-action popup-menu-item popup-menu' });
style_class: 'button draw-on-your-screen-menu-action-button' });
button.child = new St.Icon(typeof icon == 'string' ? { icon_name: icon } : { gicon: icon });
button.connect('clicked', () => {
callback();
if (callbackAfter)
callbackAfter();
});
button.bind_property('reactive', button, 'can_focus', GObject.BindingFlags.DEFAULT);
button.connect('notify::hover', () => this._syncLabel(this));
this.parent({ child: button, x_expand: true });
},
get label() {
if (!this._label) {
this._label = new St.Label({ style_class: 'dash-label' });
Main.layoutManager.uiGroup.add_actor(this._label);
this.connect('destroy', () => this._label.destroy());
}
return this._label;
}
});
// based on searchItem.js, https://github.com/leonardo-bartoli/gnome-shell-extension-Recents // based on searchItem.js, https://github.com/leonardo-bartoli/gnome-shell-extension-Recents
const DrawingMenuEntry = new Lang.Class({ const Entry = new Lang.Class({
Name: 'DrawOnYourScreenDrawingMenuEntry', Name: 'DrawOnYourScreenDrawingMenuEntry',
_init: function(params) { _init: function(params) {
@ -582,6 +768,7 @@ const DrawingMenuEntry = new Lang.Class({
this.itemActor = GS_VERSION < '3.33.0' ? this.item.actor : this.item; this.itemActor = GS_VERSION < '3.33.0' ? this.item.actor : this.item;
this.entry = new St.Entry({ this.entry = new St.Entry({
hint_text: params.hint_text || "",
style_class: 'search-entry draw-on-your-screen-menu-entry', style_class: 'search-entry draw-on-your-screen-menu-entry',
track_hover: true, track_hover: true,
reactive: true, reactive: true,

View File

@ -17,5 +17,5 @@
"3.34", "3.34",
"3.36" "3.36"
], ],
"version": 6.2 "version": 6.3
} }

747
prefs.js
View File

@ -1,4 +1,5 @@
/* jslint esversion: 6 */ /* jslint esversion: 6 */
/* exported init, buildPrefsWidget */
/* /*
* Copyright 2019 Abakkk * Copyright 2019 Abakkk
@ -20,104 +21,27 @@
* 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 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 Lang = imports.lang;
const Config = imports.misc.config;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension(); const Me = ExtensionUtils.getCurrentExtension();
const Convenience = ExtensionUtils.getSettings && ExtensionUtils.initTranslations ? ExtensionUtils : Me.imports.convenience; const Convenience = ExtensionUtils.getSettings && ExtensionUtils.initTranslations ? ExtensionUtils : Me.imports.convenience;
const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; const Shortcuts = Me.imports.shortcuts;
const gettext = imports.gettext.domain(Me.metadata['gettext-domain']).gettext;
const _ = function(string) {
if (!string)
return "";
return gettext(string);
};
const _GTK = imports.gettext.domain('gtk30').gettext; const _GTK = imports.gettext.domain('gtk30').gettext;
const GS_VERSION = Config.PACKAGE_VERSION;
const MARGIN = 10; const MARGIN = 10;
const ROWBOX_MARGIN_PARAMS = { margin_top: MARGIN / 2, margin_bottom: MARGIN / 2, margin_left: MARGIN, margin_right: MARGIN };
var GLOBAL_KEYBINDINGS = {
'toggle-drawing': "Enter/leave drawing mode",
'toggle-modal': "Grab/ungrab keyboard and pointer",
'erase-drawing': "Erase all drawings"
};
var INTERNAL_KEYBINDINGS = {
'undo': "Undo last brushstroke",
'redo': "Redo last brushstroke",
'delete-last-element' : "Erase last brushstroke",
'smooth-last-element': "Smooth last brushstroke",
'-separator-1': '',
'select-none-shape': "Free drawing",
'select-line-shape': "Select line",
'select-ellipse-shape': "Select ellipse",
'select-rectangle-shape': "Select rectangle",
'select-polygon-shape': "Select polygon",
'select-polyline-shape': "Select polyline",
'select-text-shape': "Select text",
'select-image-shape': "Select image",
'select-move-tool': "Select move",
'select-resize-tool': "Select resize",
'select-mirror-tool': "Select mirror",
'-separator-2': '',
'switch-fill': "Toggle fill/outline",
'switch-fill-rule': "Toggle fill rule",
'-separator-3': '',
'increment-line-width': "Increment line width",
'decrement-line-width': "Decrement line width",
'increment-line-width-more': "Increment line width even more",
'decrement-line-width-more': "Decrement line width even more",
'switch-linejoin': "Change linejoin",
'switch-linecap': "Change linecap",
'switch-dash': "Dashed line",
'-separator-4': '',
'switch-font-family': "Change font family",
'reverse-switch-font-family': "Change font family (reverse)",
'switch-font-weight': "Change font weight",
'switch-font-style': "Change font style",
'switch-text-alignment': "Toggle text alignment",
'switch-image-file': "Change image file",
'-separator-5': '',
'toggle-panel-and-dock-visibility': "Hide panel and dock",
'toggle-background': "Add a drawing background",
'toggle-grid': "Add a grid overlay",
'toggle-square-area': "Square drawing area",
'-separator-6': '',
'open-previous-json': "Open previous drawing",
'open-next-json': "Open next drawing",
'save-as-json': "Save drawing",
'save-as-svg': "Save drawing as a SVG file",
'open-user-stylesheet': "Edit style",
'open-preferences': "Open preferences",
'toggle-help': "Show help"
};
if (GS_VERSION < "3.36")
delete INTERNAL_KEYBINDINGS['open-preferences'];
function getKeyLabel(accel) {
let [keyval, mods] = Gtk.accelerator_parse(accel);
return Gtk.accelerator_get_label(keyval, mods);
}
var OTHER_SHORTCUTS = [
{ desc: "Draw", get shortcut() { return _("Left click"); } },
{ desc: "Menu", get shortcut() { return _("Right click"); } },
{ desc: "Toggle fill/outline", get shortcut() { return _("Center click"); } },
{ desc: "Increment/decrement line width", get shortcut() { return _("Scroll"); } },
{ desc: "Select color", get shortcut() { return _("%s … %s").format(getKeyLabel('<Primary>1'), getKeyLabel('<Primary>9')); } },
{ desc: "Ignore pointer movement", get shortcut() { return _("%s held").format(getKeyLabel('space')); } },
{ desc: "Leave", shortcut: getKeyLabel('Escape') },
{ desc: "-separator-1", shortcut: "" },
{ desc: "Select eraser <span alpha=\"50%\">(while starting drawing)</span>", shortcut: getKeyLabel('<Shift>') },
{ desc: "Duplicate <span alpha=\"50%\">(while starting handling)</span>", shortcut: getKeyLabel('<Shift>') },
{ desc: "Rotate rectangle, polygon, polyline", shortcut: getKeyLabel('<Primary>') },
{ desc: "Extend circle to ellipse", shortcut: getKeyLabel('<Primary>') },
{ desc: "Curve line", shortcut: getKeyLabel('<Primary>') },
{ desc: "Smooth free drawing outline", shortcut: getKeyLabel('<Primary>') },
{ desc: "Rotate <span alpha=\"50%\">(while moving)</span>", shortcut: getKeyLabel('<Primary>') },
{ desc: "Stretch <span alpha=\"50%\">(while resizing)</span>", shortcut: getKeyLabel('<Primary>') },
{ desc: "Inverse <span alpha=\"50%\">(while mirroring)</span>", shortcut: getKeyLabel('<Primary>') }
];
function init() { function init() {
Convenience.initTranslations(); Convenience.initTranslations();
@ -128,7 +52,6 @@ function buildPrefsWidget() {
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(); let window = topStack.get_toplevel();
window.resize(720,500);
let headerBar = window.get_titlebar(); let headerBar = window.get_titlebar();
headerBar.custom_title = switcher; headerBar.custom_title = switcher;
return false; return false;
@ -146,8 +69,13 @@ const TopStack = new GObject.Class({
_init: function(params) { _init: function(params) {
this.parent({ transition_type: 1, transition_duration: 500, expand: true }); this.parent({ transition_type: 1, transition_duration: 500, expand: true });
this.prefsPage = new PrefsPage(); this.prefsPage = new PrefsPage();
// Translators: "Preferences" page in preferences
this.add_titled(this.prefsPage, 'prefs', _("Preferences")); this.add_titled(this.prefsPage, 'prefs', _("Preferences"));
this.drawingPage = new DrawingPage();
// Translators: "Drawing" page in preferences
this.add_titled(this.drawingPage, 'drawing', _("Drawing"));
this.aboutPage = new AboutPage(); this.aboutPage = new AboutPage();
// Translators: "About" page in preferences
this.add_titled(this.aboutPage, 'about', _("About")); this.add_titled(this.aboutPage, 'about', _("About"));
} }
}); });
@ -158,14 +86,17 @@ const AboutPage = new GObject.Class({
Extends: Gtk.ScrolledWindow, Extends: Gtk.ScrolledWindow,
_init: function(params) { _init: function(params) {
this.parent(); 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: MARGIN * 3 });
this.add(vbox); this.add(vbox);
let name = "<b> " + _(Me.metadata.name) + "</b>"; // Translators: you are free to translate the extension name, that is displayed in About page, or not
let version = _("Version %d").format(Me.metadata.version); let name = "<b> " + _("Draw On You Screen") + "</b>";
let description = _(Me.metadata.description); // Translators: version number in "About" page
let version = _("Version %f").format(Me.metadata.version);
// Translators: you are free to translate the extension description, that is displayed in About page, or not
let description = _("Start drawing with Super+Alt+D and save your beautiful work by taking a screenshot");
let link = "<span><a href=\"" + Me.metadata.url + "\">" + Me.metadata.url + "</a></span>"; let link = "<span><a href=\"" + Me.metadata.url + "\">" + Me.metadata.url + "</a></span>";
let licenceName = _GTK("GNU General Public License, version 2 or later"); let licenceName = _GTK("GNU General Public License, version 2 or later");
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";
@ -176,7 +107,7 @@ const AboutPage = new GObject.Class({
vbox.add(aboutLabel); vbox.add(aboutLabel);
let creditBox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, margin: 2*MARGIN }); let creditBox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, margin: 2 * MARGIN });
let leftBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); let leftBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
let rightBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); let rightBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
let leftLabel = new Gtk.Label({ wrap: true, valign: 1, halign: 2, justify: 1, use_markup: true, label: "<small>" + _GTK("Created by") + "</small>" }); let leftLabel = new Gtk.Label({ wrap: true, valign: 1, halign: 2, justify: 1, use_markup: true, label: "<small>" + _GTK("Created by") + "</small>" });
@ -187,6 +118,11 @@ const AboutPage = new GObject.Class({
creditBox.pack_start(rightBox, true, true, 5); creditBox.pack_start(rightBox, true, true, 5);
vbox.add(creditBox); vbox.add(creditBox);
// Translators: add your name here or keep it empty, it will be displayed in about page, e.g.
// msgstr ""
// "translator1\n"
// "<a href=\"mailto:translator2@mail.org\">translator2</a>\n"
// "<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.pack_start(new Gtk.Label(), false, false, 0);
rightBox.pack_start(new Gtk.Label(), false, false, 0); rightBox.pack_start(new Gtk.Label(), false, false, 0);
@ -196,7 +132,235 @@ const AboutPage = new GObject.Class({
rightBox.pack_start(rightLabel, false, false, 0); rightBox.pack_start(rightLabel, false, false, 0);
} }
} }
});
const DrawingPage = new GObject.Class({
Name: 'DrawOnYourScreenDrawingPage',
GTypeName: 'DrawOnYourScreenDrawingPage',
Extends: Gtk.ScrolledWindow,
_init: function(params) {
this.parent({ hscrollbar_policy: Gtk.PolicyType.NEVER });
this.settings = Convenience.getSettings(Me.metadata['settings-schema'] + '.drawing');
this.schema = this.settings.settings_schema;
let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin: 3 * MARGIN, spacing: 3 * MARGIN });
this.add(box);
let palettesFrame = new Frame({ label: _("Palettes") });
box.add(palettesFrame);
let palettesScrolledWindow = new Gtk.ScrolledWindow({ vscrollbar_policy: Gtk.PolicyType.NEVER, margin_top: MARGIN / 2, margin_bottom: MARGIN / 2 });
palettesFrame.add(palettesScrolledWindow);
this.palettesListBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true });
this.palettesListBox.get_style_context().add_class('background');
this.palettesListBox.get_accessible().set_name(this.schema.get_key('palettes').get_summary());
this.palettesListBox.get_accessible().set_description(this.schema.get_key('palettes').get_description());
palettesScrolledWindow.add(this.palettesListBox);
this.settings.connect('changed::palettes', this._updatePalettes.bind(this));
this._updatePalettes();
this.addBox = new Gtk.Box(ROWBOX_MARGIN_PARAMS);
this.addBox.margin_bottom = MARGIN; // add space for the scrollbar
let addButton = Gtk.Button.new_from_icon_name('list-add-symbolic', Gtk.IconSize.BUTTON);
addButton.set_tooltip_text(_("Add a new palette"));
this.addBox.pack_start(addButton, true, true, 4);
addButton.connect('clicked', this._addNewPalette.bind(this));
this.palettesListBox.add(this.addBox);
this.addBox.get_parent().set_activatable(false);
let areaFrame = new Frame({ label: _("Area") });
box.add(areaFrame);
let areaListBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: MARGIN / 2, margin_bottom: MARGIN / 2 });
areaListBox.get_style_context().add_class('background');
areaFrame.add(areaListBox);
let squareAreaRow = new PrefRow({ label: this.schema.get_key('square-area-size').get_summary() });
let squareAreaAutoButton = new Gtk.CheckButton({ label: _("Auto"),
name: this.schema.get_key('square-area-auto').get_summary(),
tooltip_text: this.schema.get_key('square-area-auto').get_description() });
let squareAreaSizeButton = new PixelSpinButton({ width_chars: 5, digits: 0, step: 1,
range: this.schema.get_key('square-area-size').get_range(),
name: this.schema.get_key('square-area-size').get_summary(),
tooltip_text: this.schema.get_key('square-area-size').get_description() });
this.settings.bind('square-area-auto', squareAreaAutoButton, 'active', 0);
this.settings.bind('square-area-size', squareAreaSizeButton, 'value', 0);
squareAreaAutoButton.bind_property('active', squareAreaSizeButton, 'sensitive', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN);
squareAreaRow.addWidget(squareAreaAutoButton);
squareAreaRow.addWidget(squareAreaSizeButton);
areaListBox.add(squareAreaRow);
let backgroundColorRow = new PrefRow({ label: this.schema.get_key('background-color').get_summary() });
let backgroundColorButton = new ColorStringButton({ use_alpha: true, show_editor: true,
name: this.schema.get_key('background-color').get_summary(),
tooltip_text: this.schema.get_key('background-color').get_description() });
this.settings.bind('background-color', backgroundColorButton, 'color-string', 0);
backgroundColorRow.addWidget(backgroundColorButton);
areaListBox.add(backgroundColorRow);
let gridLineRow = new PrefRow({ label: _("Grid overlay line") });
let gridLineAutoButton = new Gtk.CheckButton({ label: _("Auto"),
name: this.schema.get_key('grid-line-auto').get_summary(),
tooltip_text: this.schema.get_key('grid-line-auto').get_description() });
let gridLineWidthButton = new PixelSpinButton({ width_chars: 5, digits: 1, step: 0.1,
range: this.schema.get_key('grid-line-width').get_range(),
name: this.schema.get_key('grid-line-width').get_summary(),
tooltip_text: this.schema.get_key('grid-line-width').get_description() });
let gridLineSpacingButton = new PixelSpinButton({ width_chars: 5, digits: 1, step: 1,
range: this.schema.get_key('grid-line-spacing').get_range(),
name: this.schema.get_key('grid-line-spacing').get_summary(),
tooltip_text: this.schema.get_key('grid-line-spacing').get_description() });
this.settings.bind('grid-line-auto', gridLineAutoButton, 'active', 0);
this.settings.bind('grid-line-width', gridLineWidthButton, 'value', 0);
this.settings.bind('grid-line-spacing', gridLineSpacingButton, 'value', 0);
gridLineAutoButton.bind_property('active', gridLineWidthButton, 'sensitive', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN);
gridLineAutoButton.bind_property('active', gridLineSpacingButton, 'sensitive', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN);
gridLineRow.addWidget(gridLineAutoButton);
gridLineRow.addWidget(gridLineWidthButton);
gridLineRow.addWidget(gridLineSpacingButton);
areaListBox.add(gridLineRow);
let gridColorRow = new PrefRow({ label: this.schema.get_key('grid-color').get_summary() });
let gridColorButton = new ColorStringButton({ use_alpha: true, show_editor: true,
name: this.schema.get_key('grid-color').get_summary(),
tooltip_text: this.schema.get_key('grid-color').get_description() });
this.settings.bind('grid-color', gridColorButton, 'color-string', 0);
gridColorRow.addWidget(gridColorButton);
areaListBox.add(gridColorRow);
let toolsFrame = new Frame({ label: _("Tools") });
box.add(toolsFrame);
let toolsListBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: MARGIN / 2, margin_bottom: MARGIN / 2 });
toolsListBox.get_style_context().add_class('background');
toolsFrame.add(toolsListBox);
let dashArrayRow = new PrefRow({ label: _("Dash array") });
let dashArrayAutoButton = new Gtk.CheckButton({ label: _("Auto"),
name: this.schema.get_key('dash-array-auto').get_summary(),
tooltip_text: this.schema.get_key('dash-array-auto').get_description() });
let dashArrayOnButton = new PixelSpinButton({ width_chars: 5, digits: 1, step: 0.1,
range: this.schema.get_key('dash-array-on').get_range(),
name: this.schema.get_key('dash-array-on').get_summary(),
tooltip_text: this.schema.get_key('dash-array-on').get_description() });
let dashArrayOffButton = new PixelSpinButton({ width_chars: 5, digits: 1, step: 0.1,
range: this.schema.get_key('dash-array-off').get_range(),
name: this.schema.get_key('dash-array-off').get_summary(),
tooltip_text: this.schema.get_key('dash-array-off').get_description() });
this.settings.bind('dash-array-auto', dashArrayAutoButton, 'active', 0);
this.settings.bind('dash-array-on', dashArrayOnButton, 'value', 0);
this.settings.bind('dash-array-off', dashArrayOffButton, 'value', 0);
dashArrayAutoButton.bind_property('active', dashArrayOnButton, 'sensitive', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN);
dashArrayAutoButton.bind_property('active', dashArrayOffButton, 'sensitive', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN);
dashArrayRow.addWidget(dashArrayAutoButton);
dashArrayRow.addWidget(dashArrayOnButton);
dashArrayRow.addWidget(dashArrayOffButton);
toolsListBox.add(dashArrayRow);
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,
range: this.schema.get_key('dash-offset').get_range(),
name: this.schema.get_key('dash-offset').get_summary(),
tooltip_text: this.schema.get_key('dash-offset').get_description() });
this.settings.bind('dash-offset', dashOffsetButton, 'value', 0);
dashOffsetRow.addWidget(dashOffsetButton);
toolsListBox.add(dashOffsetRow);
let imageLocationRow = new PrefRow({ label: this.schema.get_key('image-location').get_summary() });
let imageLocationButton = new FileChooserButton({ action: Gtk.FileChooserAction.SELECT_FOLDER,
name: this.schema.get_key('image-location').get_summary(),
tooltip_text: this.schema.get_key('image-location').get_description() });
this.settings.bind('image-location', imageLocationButton, 'location', 0);
imageLocationRow.addWidget(imageLocationButton);
toolsListBox.add(imageLocationRow);
let resetButton = new Gtk.Button({ label: _("Reset settings"), halign: Gtk.Align.CENTER });
resetButton.get_style_context().add_class('destructive-action');
resetButton.connect('clicked', () => this.schema.list_keys().forEach(key => this.settings.reset(key)));
box.add(resetButton);
},
_updatePalettes: function() {
this.palettes = this.settings.get_value('palettes').deep_unpack();
this.palettesListBox.get_children().filter(row=> row.get_child() != this.addBox)
.slice(this.palettes.length)
.forEach(row => this.palettesListBox.remove(row));
let paletteBoxes = this.palettesListBox.get_children().map(row => row.get_child()).filter(child => child != this.addBox);
this.palettes.forEach((palette, paletteIndex) => {
let [name, colors] = palette;
let paletteBox;
if (paletteBoxes[paletteIndex]) {
paletteBox = paletteBoxes[paletteIndex];
let nameEntry = paletteBox.get_children()[0];
if (nameEntry.get_text() !== _(name)) {
GObject.signal_handler_block(nameEntry, nameEntry.paletteNameChangedHandler);
nameEntry.set_text(_(name));
GObject.signal_handler_unblock(nameEntry, nameEntry.paletteNameChangedHandler);
}
} else {
let nameEntry = new Gtk.Entry({ text: name, halign: Gtk.Align.START, tooltip_text: _("Rename the palette") });
nameEntry.paletteNameChangedHandler = nameEntry.connect('changed', this._onPaletteNameChanged.bind(this, paletteIndex));
let removeButton = Gtk.Button.new_from_icon_name('list-remove-symbolic', Gtk.IconSize.BUTTON);
removeButton.set_tooltip_text(_("Remove the palette"));
removeButton.connect('clicked', this._removePalette.bind(this, paletteIndex));
paletteBox = new Gtk.Box(ROWBOX_MARGIN_PARAMS);
paletteBox.pack_start(nameEntry, true, true, 4);
paletteBox.pack_start(new Gtk.Box({ spacing: 4 }), false, false, 4);
paletteBox.pack_start(removeButton, false, false, 4);
this.palettesListBox.insert(paletteBox, paletteIndex);
paletteBox.get_parent().set_activatable(false);
}
colors.splice(9);
while (colors.length < 9)
colors.push('transparent');
let colorsBox = paletteBox.get_children()[1];
let colorButtons = colorsBox.get_children();
colors.forEach((color, colorIndex) => {
if (colorButtons[colorIndex]) {
colorButtons[colorIndex].color_string = color;
} else {
let colorButton = new ColorStringButton({ color_string: color, use_alpha: true, show_editor: true, halign: Gtk.Align.START, hexpand: false });
colorButton.connect('notify::color-string', this._onPaletteColorChanged.bind(this, paletteIndex, colorIndex));
colorsBox.add(colorButton);
}
});
paletteBox.show_all();
});
},
_savePalettes: function() {
this.settings.set_value('palettes', new GLib.Variant('a(sas)', this.palettes));
},
_onPaletteNameChanged: function(index, entry) {
this.palettes[index][0] = entry.get_text();
this._savePalettes();
},
_onPaletteColorChanged: function(paletteIndex, colorIndex, colorButton) {
this.palettes[paletteIndex][1][colorIndex] = colorButton.get_rgba().to_string();
this._savePalettes();
},
_addNewPalette: function() {
let colors = Array(9).fill('Black');
// Translators: default name of a new palette
this.palettes.push([_("New palette"), colors]);
this._savePalettes();
},
_removePalette: function(paletteIndex) {
this.palettes.splice(paletteIndex, 1);
this._savePalettes();
}
}); });
const PrefsPage = new GObject.Class({ const PrefsPage = new GObject.Class({
@ -205,143 +369,276 @@ const PrefsPage = new GObject.Class({
Extends: Gtk.ScrolledWindow, Extends: Gtk.ScrolledWindow,
_init: function(params) { _init: function(params) {
this.parent(); this.parent({ hscrollbar_policy: Gtk.PolicyType.NEVER });
this.settings = Convenience.getSettings(); let settings = Convenience.getSettings();
let schema = settings.settings_schema;
let internalShortcutSettings = Convenience.getSettings(Me.metadata['settings-schema'] + '.internal-shortcuts');
let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin: MARGIN*3 }); let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin: MARGIN * 3, spacing: 3 * MARGIN });
this.add(box); this.add(box);
let globalFrame = new Gtk.Frame({ label_yalign: 1.0 }); let globalFrame = new Frame({ label: _("Global") });
globalFrame.set_label_widget(new Gtk.Label({ margin_bottom: MARGIN/2, use_markup: true, label: "<b><big>" + _("Global") + "</big></b>" }));
box.add(globalFrame); box.add(globalFrame);
let listBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: MARGIN/2, 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');
globalFrame.add(listBox); globalFrame.add(listBox);
let styleContext = listBox.get_style_context(); Shortcuts.GLOBAL_KEYBINDINGS.forEach((settingKeys, index) => {
styleContext.add_class('background'); if (index)
listBox.add(new Gtk.Box(ROWBOX_MARGIN_PARAMS));
let globalKeybindingsRow = new Gtk.ListBoxRow({ activatable: false });
let globalKeybindingsWidget = new KeybindingsWidget(settingKeys, settings);
globalKeybindingsRow.add(globalKeybindingsWidget);
listBox.add(globalKeybindingsRow);
});
let globalKeybindingsWidget = new KeybindingsWidget(GLOBAL_KEYBINDINGS, this.settings); let persistentOverTogglesKey = schema.get_key('persistent-over-toggles');
globalKeybindingsWidget.margin = MARGIN; let persistentOverTogglesRow = new PrefRow({ label: persistentOverTogglesKey.get_summary(), desc: persistentOverTogglesKey.get_description() });
listBox.add(globalKeybindingsWidget); let persistentOverTogglesSwitch = new Gtk.Switch();
settings.bind('persistent-over-toggles', persistentOverTogglesSwitch, 'active', 0);
persistentOverTogglesRow.addWidget(persistentOverTogglesSwitch, true);
listBox.add(persistentOverTogglesRow);
let persistentBox = new Gtk.Box({ margin_top: MARGIN/2, margin_bottom: MARGIN/2, margin_left: MARGIN, margin_right: MARGIN }); let persistentOverRestartsKey = schema.get_key('persistent-over-restarts');
let persistentLabelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); let persistentOverRestartsRow = new PrefRow({ label: persistentOverRestartsKey.get_summary(), desc: persistentOverRestartsKey.get_description() });
let persistentLabel1 = new Gtk.Label({label: _("Persistent")}); let persistentOverRestartsSwitch = new Gtk.Switch();
let persistentLabel2 = new Gtk.Label({ use_markup: true, halign: 1, wrap: true, xalign: 0, label: "<small>" + _("Persistent drawing through session restart") + "</small>" }); settings.bind('persistent-over-restarts', persistentOverRestartsSwitch, 'active', 0);
persistentLabel1.set_halign(1); persistentOverRestartsRow.addWidget(persistentOverRestartsSwitch, true);
persistentLabel2.get_style_context().add_class('dim-label'); persistentOverTogglesSwitch.bind_property('active', persistentOverRestartsSwitch, 'sensitive', GObject.BindingFlags.SYNC_CREATE);
persistentLabelBox.pack_start(persistentLabel1, true, true, 0); listBox.add(persistentOverRestartsRow);
persistentLabelBox.pack_start(persistentLabel2, true, true, 0);
let persistentSwitch = new Gtk.Switch({valign: 3});
this.settings.bind('persistent-drawing', persistentSwitch, 'active', 0);
persistentBox.pack_start(persistentLabelBox, true, true, 4);
persistentBox.pack_start(persistentSwitch, false, false, 4);
listBox.add(persistentBox);
let desktopBox = new Gtk.Box({ margin_top: MARGIN/2, margin_bottom: MARGIN/2, margin_left: MARGIN, margin_right: MARGIN }); let desktopKey = schema.get_key('drawing-on-desktop');
let desktopLabelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); let desktopRow = new PrefRow({ label: desktopKey.get_summary(), desc: desktopKey.get_description() });
let desktopLabel1 = new Gtk.Label({label: _("Drawing on the desktop")}); let desktopSwitch = new Gtk.Switch();
let desktopLabel2 = new Gtk.Label({ use_markup: true, halign: 1, wrap: true, xalign: 0, label: "<small>" + _("<i>Draw On Your Screen</i> becomes <i>Draw On Your Desktop</i>") + "</small>" }); settings.bind('drawing-on-desktop', desktopSwitch, 'active', 0);
desktopLabel1.set_halign(1); desktopRow.addWidget(desktopSwitch, true);
desktopLabel2.get_style_context().add_class('dim-label'); persistentOverTogglesSwitch.bind_property('active', desktopSwitch, 'sensitive', GObject.BindingFlags.SYNC_CREATE);
desktopLabelBox.pack_start(desktopLabel1, true, true, 0); listBox.add(desktopRow);
desktopLabelBox.pack_start(desktopLabel2, true, true, 0);
let desktopSwitch = new Gtk.Switch({valign: 3});
this.settings.bind('drawing-on-desktop', desktopSwitch, 'active', 0);
desktopBox.pack_start(desktopLabelBox, true, true, 4);
desktopBox.pack_start(desktopSwitch, false, false, 4);
listBox.add(desktopBox);
let osdBox = new Gtk.Box({ margin_top: MARGIN/2, margin_bottom: MARGIN/2, margin_left: MARGIN, margin_right: MARGIN }); let osdKey = schema.get_key('osd-disabled');
let osdLabelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); let osdRow = new PrefRow({ label: osdKey.get_summary(), desc: osdKey.get_description() });
let osdLabel1 = new Gtk.Label({label: _("Disable on-screen notifications")}); let osdSwitch = new Gtk.Switch();
osdLabel1.set_halign(1); settings.bind('osd-disabled', osdSwitch, 'active', 0);
osdLabelBox.pack_start(osdLabel1, true, true, 0); osdRow.addWidget(osdSwitch, true);
let osdSwitch = new Gtk.Switch({valign: 3}); listBox.add(osdRow);
this.settings.bind('osd-disabled', osdSwitch, 'active', 0);
osdBox.pack_start(osdLabelBox, true, true, 4);
osdBox.pack_start(osdSwitch, false, false, 4);
listBox.add(osdBox);
let indicatorBox = new Gtk.Box({ margin_top: MARGIN/2, margin_bottom: MARGIN/2, margin_left: MARGIN, margin_right: MARGIN }); let indicatorKey = schema.get_key('indicator-disabled');
let indicatorLabelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); let indicatorRow = new PrefRow({ label: indicatorKey.get_summary(), desc: indicatorKey.get_description() });
let indicatorLabel1 = new Gtk.Label({label: _("Disable panel indicator")}); let indicatorSwitch = new Gtk.Switch();
indicatorLabel1.set_halign(1); settings.bind('indicator-disabled', indicatorSwitch, 'active', 0);
indicatorLabelBox.pack_start(indicatorLabel1, true, true, 0); indicatorRow.addWidget(indicatorSwitch, true);
let indicatorSwitch = new Gtk.Switch({valign: 3}); listBox.add(indicatorRow);
this.settings.bind('indicator-disabled', indicatorSwitch, 'active', 0);
indicatorBox.pack_start(indicatorLabelBox, true, true, 4);
indicatorBox.pack_start(indicatorSwitch, false, false, 4);
listBox.add(indicatorBox);
let children = listBox.get_children(); let internalFrame = new Frame({ label: _("Internal"), desc: _("In drawing mode") });
for (let i = 0; i < children.length; i++) {
if (children[i].activatable)
children[i].set_activatable(false);
}
let internalFrame = new Gtk.Frame({ margin_top: 3*MARGIN, label_yalign: 1.0 });
internalFrame.set_label_widget(new Gtk.Label({ margin_bottom: MARGIN/2, use_markup: true, label: "<b><big>" + _("Internal") + " </big></b>" + _("(in drawing mode)") }));
box.add(internalFrame); box.add(internalFrame);
listBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: MARGIN }); listBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: MARGIN, margin_bottom: MARGIN });
listBox.get_style_context().add_class('background');
internalFrame.add(listBox); internalFrame.add(listBox);
styleContext = listBox.get_style_context(); Shortcuts.OTHERS.forEach((pairs, index) => {
styleContext.add_class('background'); if (index)
listBox.add(new Gtk.Box(ROWBOX_MARGIN_PARAMS));
pairs.forEach(pair => {
let [action, shortcut] = pair;
let otherBox = new Gtk.Box({ margin_left: MARGIN, margin_right: MARGIN });
let otherLabel = new Gtk.Label({ label: action, use_markup: true });
otherLabel.set_halign(1);
let otherLabel2 = new Gtk.Label({ label: shortcut });
otherBox.pack_start(otherLabel, true, true, 4);
otherBox.pack_start(otherLabel2, false, false, 4);
listBox.add(otherBox);
});
});
for (let i = 0; i < OTHER_SHORTCUTS.length; i++) { listBox.add(new Gtk.Box(ROWBOX_MARGIN_PARAMS));
if (OTHER_SHORTCUTS[i].desc.indexOf('-separator-') != -1) {
listBox.add(new Gtk.Box({ margin_top: MARGIN, margin_left: MARGIN, margin_right: MARGIN })); Shortcuts.INTERNAL_KEYBINDINGS.forEach((settingKeys, index) => {
continue; if (index)
listBox.add(new Gtk.Box(ROWBOX_MARGIN_PARAMS));
let internalKeybindingsWidget = new KeybindingsWidget(settingKeys, internalShortcutSettings);
listBox.add(internalKeybindingsWidget);
});
listBox.get_children().forEach(row => row.set_activatable(false));
let resetButton = new Gtk.Button({ label: _("Reset settings"), halign: Gtk.Align.CENTER });
resetButton.get_style_context().add_class('destructive-action');
resetButton.connect('clicked', () => {
internalShortcutSettings.settings_schema.list_keys().forEach(key => internalShortcutSettings.reset(key));
settings.settings_schema.list_keys().forEach(key => settings.reset(key));
});
box.add(resetButton);
}
});
const Frame = new GObject.Class({
Name: 'DrawOnYourScreenFrame',
GTypeName: 'DrawOnYourScreenFrame',
Extends: Gtk.Frame,
_init: function(params) {
let labelWidget = new Gtk.Label({ margin_bottom: MARGIN / 2, use_markup: true, label: `<b><big>${params.label}</big></b>` });
this.parent({ label_yalign: 1.0, label_widget: labelWidget });
if (params.desc) {
labelWidget.set_tooltip_text(params.desc);
this.get_accessible().set_description(params.desc);
}
}
});
const PrefRow = new GObject.Class({
Name: 'DrawOnYourScreenPrefRow',
GTypeName: 'DrawOnYourScreenPrefRow',
Extends: Gtk.ListBoxRow,
_init: function(params) {
this.parent({ activatable: false });
let hbox = new Gtk.Box(ROWBOX_MARGIN_PARAMS);
this.add(hbox);
let labelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
hbox.pack_start(labelBox, true, true, 4);
this.widgetBox = new Gtk.Box({ spacing: 4 });
hbox.pack_start(this.widgetBox, false, false, 4);
this.label = new Gtk.Label({ use_markup: true, label: params.label, halign: Gtk.Align.START });
labelBox.pack_start(this.label, true, true, 0);
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.get_style_context().add_class('dim-label');
labelBox.pack_start(this.desc, true, true, 0);
this.widgetBox.set_valign(Gtk.Align.START);
}
},
addWidget: function(widget, setRelationship) {
this.widgetBox.add(widget);
if (widget.name)
widget.get_accessible().set_name(widget.name);
if (setRelationship) {
this.label.get_accessible().add_relationship(Atk.RelationType.LABEL_FOR, widget.get_accessible());
widget.get_accessible().add_relationship(Atk.RelationType.LABELLED_BY, this.label.get_accessible());
if (this.desc) {
this.desc.get_accessible().add_relationship(Atk.RelationType.DESCRIPTION_FOR, widget.get_accessible());
widget.get_accessible().add_relationship(Atk.RelationType.DESCRIBED_BY, this.desc.get_accessible());
} }
let otherBox = new Gtk.Box({ margin_left: MARGIN, margin_right: MARGIN }); }
let otherLabel = new Gtk.Label({ label: _(OTHER_SHORTCUTS[i].desc), use_markup: true }); }
otherLabel.set_halign(1); });
let otherLabel2 = new Gtk.Label({ label: OTHER_SHORTCUTS[i].shortcut });
otherBox.pack_start(otherLabel, true, true, 4); const PixelSpinButton = new GObject.Class({
otherBox.pack_start(otherLabel2, false, false, 4); Name: 'DrawOnYourScreenPixelSpinButton',
listBox.add(otherBox); GTypeName: 'DrawOnYourScreenPixelSpinButton',
Extends: Gtk.SpinButton,
Properties: {
'range': GObject.param_spec_variant('range', 'range', 'GSettings range',
GLib.VariantType.new('(sv)'), null, GObject.ParamFlags.WRITABLE),
'step': GObject.ParamSpec.double('step', 'step', 'step increment',
GObject.ParamFlags.WRITABLE,
0, 1000, 1)
},
set range(range) {
let [type, variant] = range.deep_unpack();
if (type == 'range') {
let [min, max] = variant.deep_unpack();
this.adjustment.set_lower(min);
this.adjustment.set_upper(max);
}
},
set step(step) {
this.adjustment.set_step_increment(step);
this.adjustment.set_page_increment(step * 10);
},
// Add 'px' unit.
vfunc_output: function() {
this.text = _("%f px").format(Number(this.value).toFixed(2));
return true;
},
// Prevent accidental scrolling.
vfunc_scroll_event: function(event) {
return this.has_focus ? this.parent(event) : Gdk.EVENT_PROPAGATE;
}
});
// A color button that can be easily bound with a color string setting.
const ColorStringButton = new GObject.Class({
Name: 'DrawOnYourScreenColorStringButton',
GTypeName: 'DrawOnYourScreenColorStringButton',
Extends: Gtk.ColorButton,
Properties: {
'color-string': GObject.ParamSpec.string('color-string', 'colorString', 'A string that describes the color',
GObject.ParamFlags.READWRITE, 'black')
},
get color_string() {
return this._color_string || 'black';
},
set color_string(colorString) {
this._color_string = colorString;
let newRgba = new Gdk.RGBA();
newRgba.parse(colorString);
this.set_rgba(newRgba);
},
// Do nothing if the new color is equivalent to the old color (e.g. "black" and "rgb(0,0,0)").
vfunc_color_set(args) {
let oldRgba = new Gdk.RGBA();
oldRgba.parse(this.color_string);
if (!this.rgba.equal(oldRgba)) {
this._color_string = this.rgba.to_string();
this.notify('color-string');
}
}
});
const FileChooserButton = new GObject.Class({
Name: 'DrawOnYourScreenFileChooserButton',
GTypeName: 'DrawOnYourScreenFileChooserButton',
Extends: Gtk.FileChooserButton,
Properties: {
'location': GObject.ParamSpec.string('location', 'location', 'location',
GObject.ParamFlags.READWRITE, '')
},
get location() {
return this.get_file().get_path();
},
set location(location) {
if (!location) {
this.unselect_all();
if (this.get_file())
this.set_file(Gio.File.new_for_path('aFileThatDoesNotExist'));
return;
} }
let internalKeybindingsWidget = new KeybindingsWidget(INTERNAL_KEYBINDINGS, this.settings); let file = Gio.File.new_for_commandline_arg(location);
internalKeybindingsWidget.margin = MARGIN; if (file.query_exists(null))
listBox.add(internalKeybindingsWidget); this.set_file(file);
},
let styleBox = new Gtk.Box({ margin: MARGIN });
let styleLabel = new Gtk.Label({ vfunc_file_set: function(args) {
wrap: true, this.notify('location');
xalign: 0,
use_markup: true,
label: _("<b>Default</b> drawing style attributes (color palette, font, line, dash) are defined in an editable <b>css</b> file.\n" +
"See <i>“%s”</i>.").format(_("Edit style"))
});
styleLabel.set_halign(1);
styleLabel.get_style_context().add_class('dim-label');
styleBox.pack_start(styleLabel, true, true, 4);
listBox.add(styleBox);
let noteBox = new Gtk.Box({ margin: MARGIN });
let noteLabel = new Gtk.Label({
wrap: true,
xalign: 0,
use_markup: true,
label: _("When you save elements made with <b>eraser</b> in a <b>SVG</b> file, " +
"they are colored with background color, transparent if it is disabled.\n" +
"See <i>“%s”</i> or edit the SVG file afterwards.").format(_("Add a drawing background"))
});
noteLabel.set_halign(1);
noteLabel.get_style_context().add_class('dim-label');
noteBox.pack_start(noteLabel, true, true, 4);
listBox.add(noteBox);
children = listBox.get_children();
for (let i = 0; i < children.length; i++) {
if (children[i].activatable)
children[i].set_activatable(false);
}
} }
}); });
@ -351,11 +648,11 @@ const KeybindingsWidget = new GObject.Class({
GTypeName: 'DrawOnYourScreenKeybindingsWidget', GTypeName: 'DrawOnYourScreenKeybindingsWidget',
Extends: Gtk.Box, Extends: Gtk.Box,
_init: function(keybindings, settings) { _init: function(settingKeys, settings) {
this.parent(); this.parent(ROWBOX_MARGIN_PARAMS);
this.set_orientation(Gtk.Orientation.VERTICAL); this.set_orientation(Gtk.Orientation.VERTICAL);
this._keybindings = keybindings; this._settingKeys = settingKeys;
this._settings = settings; this._settings = settings;
this._columns = { this._columns = {
@ -436,17 +733,27 @@ const KeybindingsWidget = new GObject.Class({
this.keybinding_column = keybinding_column; this.keybinding_column = keybinding_column;
this.action_column = action_column; this.action_column = action_column;
this._settings.connect('changed', this._onSettingsChanged.bind(this));
this._refresh(); this._refresh();
}, },
// Support the case where all the settings has been reset.
_onSettingsChanged: function() {
if (this._refreshTimeout)
GLib.source_remove(this._refreshTimeout);
this._refreshTimeout = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
this._refreshTimeout = 0;
this._refresh();
});
},
_refresh: function() { _refresh: function() {
this._store.clear(); this._store.clear();
for(let settings_key in this._keybindings) { this._settingKeys.forEach(settingKey => {
if (settings_key.indexOf('-separator-') != -1)
continue;
let [key, mods] = Gtk.accelerator_parse( let [key, mods] = Gtk.accelerator_parse(
this._settings.get_strv(settings_key)[0] this._settings.get_strv(settingKey)[0] || ''
); );
let iter = this._store.append(); let iter = this._store.append();
@ -458,12 +765,12 @@ const KeybindingsWidget = new GObject.Class({
this._columns.KEY this._columns.KEY
], ],
[ [
settings_key, settingKey,
_(this._keybindings[settings_key]), this._settings.settings_schema.get_key(settingKey).get_summary(),
mods, mods,
key key
] ]
); );
} });
} }
}); });

Binary file not shown.

View File

@ -1,290 +1,342 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<schemalist gettext-domain="gnome-shell-extensions"> <schemalist gettext-domain="draw-on-your-screen">
<schema path="/org/gnome/shell/extensions/draw-on-your-screen/" id="org.gnome.shell.extensions.draw-on-your-screen"> <schema path="/org/gnome/shell/extensions/draw-on-your-screen/" id="org.gnome.shell.extensions.draw-on-your-screen">
<child name="drawing" schema="org.gnome.shell.extensions.draw-on-your-screen.drawing"/>
<child name="internal-shortcuts" schema="org.gnome.shell.extensions.draw-on-your-screen.internal-shortcuts"/>
<key type="b" name="drawing-on-desktop"> <key type="b" name="drawing-on-desktop">
<default>false</default> <default>false</default>
<summary>move drawing on desktop</summary> <summary>Drawing on the desktop</summary>
<description>move drawing on desktop</description> <description><![CDATA[<i>Draw On Your Screen</i> becomes <i>Draw On Your Desktop</i>]]></description>
</key> </key>
<key type="b" name="persistent-drawing"> <key type="as" name="erase-drawings">
<default>false</default> <default>["&lt;Alt&gt;&lt;Super&gt;e"]</default>
<summary>persistent drawing</summary> <summary>Erase all drawings</summary>
<description>persistent drawing</description>
</key>
<key type="b" name="osd-disabled">
<default>false</default>
<summary>disable OSD notifications</summary>
<description>disable on-screen notifications</description>
</key> </key>
<key type="b" name="indicator-disabled"> <key type="b" name="indicator-disabled">
<default>false</default> <default>false</default>
<summary>disable panel indicator</summary> <summary>Disable panel indicator</summary>
<description>disable panel indicator</description> </key>
<key type="b" name="osd-disabled">
<default>false</default>
<summary>Disable on-screen notifications</summary>
</key>
<key type="b" name="persistent-over-toggles">
<default>true</default>
<summary>Persistent over toggles</summary>
<description>Drawing remains when toggling drawing mode</description>
</key>
<key type="b" name="persistent-over-restarts">
<default>false</default>
<summary>Persistent over restarts</summary>
<description>Drawing is automatically saved to a file</description>
</key> </key>
<key type="as" name="toggle-drawing"> <key type="as" name="toggle-drawing">
<default>["&lt;Alt&gt;&lt;Super&gt;d"]</default> <default>["&lt;Alt&gt;&lt;Super&gt;d"]</default>
<summary>toggle drawing</summary> <summary>Enter/leave drawing mode</summary>
<description>enter or leave drawing mode</description>
</key> </key>
<key type="as" name="toggle-modal"> <key type="as" name="toggle-modal">
<default>["&lt;Primary&gt;&lt;Alt&gt;&lt;Super&gt;d"]</default> <default>["&lt;Primary&gt;&lt;Alt&gt;&lt;Super&gt;d"]</default>
<summary>toggle modeless/modal</summary> <!-- Translators: there is a similar text in GNOME Boxes (https://gitlab.gnome.org/GNOME/gnome-boxes/tree/master/po) -->
<description>toggle modeless/modal</description> <summary>Grab/ungrab keyboard and pointer</summary>
<description></description>
</key> </key>
<key type="as" name="erase-drawing"> </schema>
<default>["&lt;Alt&gt;&lt;Super&gt;e"]</default> <schema path="/org/gnome/shell/extensions/draw-on-your-screen/drawing/" id="org.gnome.shell.extensions.draw-on-your-screen.drawing">
<summary>erase drawing</summary> <key type="s" name="background-color">
<description>erase drawing</description> <default>"#2e2e2e"</default>
<summary>Background color</summary>
<description>The color of the drawing area background</description>
</key> </key>
<key type="as" name="undo"> <key type="b" name="dash-array-auto">
<default>["&lt;Primary&gt;z"]</default> <default>true</default>
<summary>undo</summary> <summary>Automatic dash array</summary>
<description>undo</description> <description>Compute the lengths from the line width</description>
</key> </key>
<key type="as" name="redo"> <key type="d" name="dash-array-on">
<default>["&lt;Primary&gt;&lt;Shift&gt;z"]</default> <range min="0.1" max="16384"/>
<summary>redo</summary> <default>5</default>
<description>redo</description> <summary>Dash array on</summary>
<description>The dash length in pixels</description>
</key> </key>
<key type="as" name="delete-last-element"> <key type="d" name="dash-array-off">
<default>["Delete"]</default> <range min="0.1" max="16384"/>
<summary>delete last element</summary> <default>15</default>
<description>delete last element</description> <summary>Dash array off</summary>
<description>The gap between the dashes in pixels</description>
</key> </key>
<key type="as" name="smooth-last-element"> <key type="d" name="dash-offset">
<default>["&lt;Primary&gt;equal"]</default> <range min="-16384" max="16384"/>
<summary>smooth last brushstroke</summary> <default>0</default>
<description>smooth last brushstroke</description> <summary>Dash offset</summary>
<description>The dash offset in pixels</description>
</key> </key>
<key type="as" name="toggle-background"> <key type="s" name="grid-color">
<default>["&lt;Primary&gt;b"]</default> <default>"Gray"</default>
<summary>toggle drawing background</summary> <summary>Grid overlay color</summary>
<description>toggle drawing background</description> <description>The color of the lines</description>
</key> </key>
<key type="as" name="toggle-grid"> <key type="b" name="grid-line-auto">
<default>["&lt;Primary&gt;g"]</default> <default>true</default>
<summary>toggle grid overlay</summary> <summary>Automatic grid overlay line</summary>
<description>toggle grid overlay</description> <description>Compute the lengths from the screen size</description>
</key> </key>
<key type="as" name="toggle-panel-and-dock-visibility"> <key type="u" name="grid-line-spacing">
<default>["&lt;Primary&gt;h"]</default> <range min="1" max="16384"/>
<summary>hide or show panel and dock</summary> <default>10</default>
<description>hide or show panel and dock</description> <summary>Grid overlay line spacing</summary>
<description>The gap between lines in pixels</description>
</key> </key>
<key type="as" name="toggle-square-area"> <key type="d" name="grid-line-width">
<default>["&lt;Primary&gt;n"]</default> <range min="0.1" max="10"/>
<summary>toggle square area</summary> <default>0.5</default>
<description>toggle square area</description> <summary>Grid overlay line width</summary>
<description>The line width in pixels</description>
</key> </key>
<key type="as" name="select-ellipse-shape"> <key type="s" name="image-location">
<default>["&lt;Primary&gt;e"]</default> <default>""</default>
<summary>select cercle</summary> <summary>Image location</summary>
<description>select a cercle</description> <description>The location of the directory in which the image tool picks</description>
</key> </key>
<key type="as" name="select-rectangle-shape"> <key type="a(sas)" name="palettes">
<default>["&lt;Primary&gt;r"]</default> <default>
<summary>select rectangle</summary> [
<description>select rectangle</description> ("Palette", ["HotPink","Cyan","yellow","Orangered","Chartreuse","DarkViolet","White","Gray","Black"]),
("GNOME HIG lighter", ["rgb(153,193,241)","rgb(143,240,164)","rgb(249,240,107)","rgb(255,190,111)","rgb(246,97,81)","rgb(220,138,221)","rgb(205,171,143)","rgb(255,255,255)","rgb(119,118,123)"]),
("GNOME HIG light", ["rgb(98,160,241)","rgb(87,227,137)","rgb(248,228,92)","rgb(255,163,72)","rgb(237,51,59)","rgb(192,97,203)","rgb(181,131,90)","rgb(246,245,244)","rgb(94,92,100)"]),
("GNOME HIG normal", ["rgb(53,132,228)","rgb(51,209,122)","rgb(246,211,45)","rgb(255,120,0)","rgb(224,27,36)","rgb(145,65,172)","rgb(152,106,68)","rgb(222,221,218)","rgb(61,56,70)"]),
("GNOME HIG dark", ["rgb(28,113,216)","rgb(46,194,126)","rgb(245,194,17)","rgb(230,97,0)","rgb(192,28,40)","rgb(129,61,156)","rgb(134,94,60)","rgb(192,191,188)","rgb(36,31,49)"]),
("GNOME HIG darker", ["rgb(26,095,180)","rgb(38,162,105)","rgb(229,165,10)","rgb(198,70,0)","rgb(165,29,45)","rgb(97,53,131)","rgb(99,69,44)","rgb(154,153,150)","rgb(0,0,0)"])
]
</default>
<summary>Color palettes</summary>
<description>The palettes of drawing colors</description>
</key> </key>
<key type="as" name="select-polygon-shape"> <key type="b" name="square-area-auto">
<default>["&lt;Primary&gt;y"]</default> <default>true</default>
<summary>select polygon</summary> <summary>Automatic square area size</summary>
<description>select polygon</description> <description>Compute the area size from the screen size</description>
</key> </key>
<key type="as" name="select-polyline-shape"> <key type="u" name="square-area-size">
<default>["&lt;Primary&gt;u"]</default> <range min="64" max="32768"/>
<summary>select polyline</summary> <default>512</default>
<description>select polyline</description> <summary>Square area size</summary>
</key> <description>The size of the area in pixels</description>
<key type="as" name="select-line-shape">
<default>["&lt;Primary&gt;l"]</default>
<summary>select line</summary>
<description>select a line</description>
</key>
<key type="as" name="select-text-shape">
<default>["&lt;Primary&gt;t"]</default>
<summary>select text</summary>
<description>select text</description>
</key>
<key type="as" name="select-image-shape">
<default>["&lt;Primary&gt;i"]</default>
<summary>select image</summary>
<description>select image</description>
</key>
<key type="as" name="select-none-shape">
<default>["&lt;Primary&gt;p"]</default>
<summary>unselect shape (free drawing)</summary>
<description>unselect shape (free drawing)</description>
</key>
<key type="as" name="select-move-tool">
<default>["&lt;Primary&gt;m"]</default>
<summary>select move tool</summary>
<description>select move tool</description>
</key>
<key type="as" name="select-resize-tool">
<default>["&lt;Primary&gt;x"]</default>
<summary>select resize tool</summary>
<description>select resize tool</description>
</key>
<key type="as" name="select-mirror-tool">
<default>["&lt;Primary&gt;c"]</default>
<summary>select mirror tool</summary>
<description>select mirror tool</description>
</key>
<key type="as" name="increment-line-width">
<default><![CDATA[['<Primary>KP_Add','<Primary><Shift>plus']]]></default>
<summary>increment the line width</summary>
<description>increment the line width</description>
</key> </key>
</schema>
<schema path="/org/gnome/shell/extensions/draw-on-your-screen/internal-shortcuts/" id="org.gnome.shell.extensions.draw-on-your-screen.internal-shortcuts">
<key type="as" name="decrement-line-width"> <key type="as" name="decrement-line-width">
<default><![CDATA[['<Primary>KP_Subtract','<Primary>minus','<Primary><Shift>minus']]]></default> <default><![CDATA[['<Primary>KP_Subtract','<Primary>minus','<Primary><Shift>minus']]]></default>
<summary>decrement the line width</summary> <summary>Decrement line width</summary>
<description>decrement the line width</description>
</key>
<key type="as" name="increment-line-width-more">
<default>["&lt;Primary&gt;&lt;Shift&gt;KP_Add"]</default>
<summary>increment the line width even more</summary>
<description>increment the line width even more</description>
</key> </key>
<key type="as" name="decrement-line-width-more"> <key type="as" name="decrement-line-width-more">
<default>["&lt;Primary&gt;&lt;Shift&gt;KP_Subtract"]</default> <default>["&lt;Primary&gt;&lt;Shift&gt;KP_Subtract"]</default>
<summary>decrement the line width even more</summary> <summary>Decrement line width even more</summary>
<description>decrement the line width even more</description>
</key> </key>
<key type="as" name="switch-linejoin"> <key type="as" name="delete-last-element">
<default>["&lt;Primary&gt;j"]</default> <default>["Delete"]</default>
<summary>switch linejoin</summary> <summary>Erase last brushstroke</summary>
<description>switch linejoin</description>
</key> </key>
<key type="as" name="switch-linecap"> <key type="as" name="export-to-svg">
<default>["&lt;Primary&gt;k"]</default> <default>["&lt;Primary&gt;&lt;Alt&gt;s"]</default>
<summary>switch linecap</summary> <summary>Export drawing to a SVG file</summary>
<description>switch linecap</description>
</key> </key>
<key type="as" name="switch-fill-rule"> <key type="as" name="increment-line-width">
<default><![CDATA[['<Primary>KP_Multiply','<Primary>asterisk','<Primary><Shift>asterisk']]]></default> <default><![CDATA[['<Primary>KP_Add','<Primary><Shift>plus']]]></default>
<summary>switch fill rule</summary> <summary>Increment line width</summary>
<description>switch fill rule</description>
</key> </key>
<key type="as" name="switch-dash"> <key type="as" name="increment-line-width-more">
<default>["&lt;Primary&gt;period"]</default> <default>["&lt;Primary&gt;&lt;Shift&gt;KP_Add"]</default>
<summary>switch dash</summary> <summary>Increment line width even more</summary>
<description>switch dash</description>
</key>
<key type="as" name="switch-fill">
<default>["&lt;Primary&gt;a"]</default>
<summary>switch fill</summary>
<description>switch fill</description>
</key>
<key type="as" name="select-color1">
<default><![CDATA[['<Primary>KP_1','<Primary>1']]]></default>
<summary>select color1</summary>
<description>select color1</description>
</key>
<key type="as" name="select-color2">
<default><![CDATA[['<Primary>KP_2','<Primary>2']]]></default>
<summary>select color2</summary>
<description>select color2</description>
</key>
<key type="as" name="select-color3">
<default><![CDATA[['<Primary>KP_3','<Primary>3']]]></default>
<summary>select color3</summary>
<description>select color3</description>
</key>
<key type="as" name="select-color4">
<default><![CDATA[['<Primary>KP_4','<Primary>4']]]></default>
<summary>select color4</summary>
<description>select color4</description>
</key>
<key type="as" name="select-color5">
<default><![CDATA[['<Primary>KP_5','<Primary>5']]]></default>
<summary>select color5</summary>
<description>select color5</description>
</key>
<key type="as" name="select-color6">
<default><![CDATA[['<Primary>KP_6','<Primary>6']]]></default>
<summary>select color6</summary>
<description>select color6</description>
</key>
<key type="as" name="select-color7">
<default><![CDATA[['<Primary>KP_7','<Primary>7']]]></default>
<summary>select color7</summary>
<description>select color7</description>
</key>
<key type="as" name="select-color8">
<default><![CDATA[['<Primary>KP_8','<Primary>8']]]></default>
<summary>select color8</summary>
<description>select color8</description>
</key>
<key type="as" name="select-color9">
<default><![CDATA[['<Primary>KP_9','<Primary>9']]]></default>
<summary>select color9</summary>
<description>select color9</description>
</key>
<key type="as" name="switch-font-family">
<default>["&lt;Primary&gt;f"]</default>
<summary>switch font family</summary>
<description>switch font family</description>
</key>
<key type="as" name="reverse-switch-font-family">
<default>["&lt;Primary&gt;&lt;Shift&gt;f"]</default>
<summary>switch font family (reverse)</summary>
<description>switch font family (reverse)</description>
</key>
<key type="as" name="switch-font-weight">
<default>["&lt;Primary&gt;w"]</default>
<summary>switch font weight</summary>
<description>switch font weight</description>
</key>
<key type="as" name="switch-font-style">
<default>["&lt;Primary&gt;&lt;Shift&gt;w"]</default>
<summary>switch font style</summary>
<description>switch font style</description>
</key>
<key type="as" name="switch-text-alignment">
<default>["&lt;Primary&gt;&lt;Shift&gt;a"]</default>
<summary>switch text alignment</summary>
<description>switch text alignment</description>
</key>
<key type="as" name="switch-image-file">
<default>["&lt;Primary&gt;&lt;Shift&gt;i"]</default>
<summary>switch image file</summary>
<description>switch image file</description>
</key>
<key type="as" name="open-user-stylesheet">
<default>["&lt;Primary&gt;o"]</default>
<summary>open user stylesheet to edit style</summary>
<description>open user stylesheet to edit style</description>
</key>
<key type="as" name="save-as-svg">
<default>["&lt;Primary&gt;&lt;Shift&gt;s"]</default>
<summary>Save drawing as a svg file</summary>
<description>Save drawing as a svg file</description>
</key>
<key type="as" name="save-as-json">
<default>["&lt;Primary&gt;s"]</default>
<summary>Save drawing as a json file</summary>
<description>Save drawing as a json file</description>
</key>
<key type="as" name="open-previous-json">
<default>["&lt;Primary&gt;Page_Down"]</default>
<summary>Open previous json file</summary>
<description>Open previous json file</description>
</key> </key>
<key type="as" name="open-next-json"> <key type="as" name="open-next-json">
<default>["&lt;Primary&gt;Page_Up"]</default> <default>["&lt;Primary&gt;o"]</default>
<summary>Open next json file</summary> <summary>Open next drawing</summary>
<description>Open next json file</description>
</key> </key>
<key type="as" name="open-preferences"> <key type="as" name="open-preferences">
<default>["&lt;Primary&gt;comma"]</default> <default>["&lt;Primary&gt;comma"]</default>
<summary>Open preferences</summary> <summary>Open preferences</summary>
<description>Open preferences</description> </key>
<key type="as" name="open-previous-json">
<default>["&lt;Primary&gt;&lt;Shift&gt;o"]</default>
<summary>Open previous drawing</summary>
</key>
<key type="as" name="paste-image-files">
<default>["&lt;Primary&gt;v"]</default>
<summary>Add images from the clipboard</summary>
</key>
<key type="as" name="redo">
<default>["&lt;Primary&gt;&lt;Shift&gt;z"]</default>
<summary>Redo last brushstroke</summary>
</key>
<key type="as" name="save-as-json">
<default>["&lt;Primary&gt;s"]</default>
<summary>Save drawing</summary>
</key>
<key type="as" name="select-color1">
<default><![CDATA[['<Primary>KP_1','<Primary>1']]]></default>
<summary>Select color 1</summary>
<description></description>
</key>
<key type="as" name="select-color2">
<default><![CDATA[['<Primary>KP_2','<Primary>2']]]></default>
<summary>Select color 2</summary>
</key>
<key type="as" name="select-color3">
<default><![CDATA[['<Primary>KP_3','<Primary>3']]]></default>
<summary>Select color 3</summary>
</key>
<key type="as" name="select-color4">
<default><![CDATA[['<Primary>KP_4','<Primary>4']]]></default>
<summary>Select color 4</summary>
</key>
<key type="as" name="select-color5">
<default><![CDATA[['<Primary>KP_5','<Primary>5']]]></default>
<summary>Select color 5</summary>
</key>
<key type="as" name="select-color6">
<default><![CDATA[['<Primary>KP_6','<Primary>6']]]></default>
<summary>Select color 6</summary>
</key>
<key type="as" name="select-color7">
<default><![CDATA[['<Primary>KP_7','<Primary>7']]]></default>
<summary>Select color 7</summary>
</key>
<key type="as" name="select-color8">
<default><![CDATA[['<Primary>KP_8','<Primary>8']]]></default>
<summary>Select color 8</summary>
</key>
<key type="as" name="select-color9">
<default><![CDATA[['<Primary>KP_9','<Primary>9']]]></default>
<summary>Select color 9</summary>
</key>
<key type="as" name="select-ellipse-shape">
<default>["&lt;Primary&gt;e"]</default>
<summary>Select ellipse tool</summary>
</key>
<key type="as" name="select-image-shape">
<default>["&lt;Primary&gt;i"]</default>
<summary>Select image tool</summary>
</key>
<key type="as" name="select-line-shape">
<default>["&lt;Primary&gt;l"]</default>
<summary>Select line tool</summary>
</key>
<key type="as" name="select-mirror-tool">
<default>["&lt;Primary&gt;c"]</default>
<summary>Select mirror tool</summary>
</key>
<key type="as" name="select-move-tool">
<default>["&lt;Primary&gt;m"]</default>
<summary>Select move tool</summary>
</key>
<key type="as" name="select-none-shape">
<default>["&lt;Primary&gt;p"]</default>
<summary>Select free drawing</summary>
</key>
<key type="as" name="select-polygon-shape">
<default>["&lt;Primary&gt;y"]</default>
<summary>Select polygon tool</summary>
</key>
<key type="as" name="select-polyline-shape">
<default>["&lt;Primary&gt;u"]</default>
<summary>Select polyline tool</summary>
</key>
<key type="as" name="select-rectangle-shape">
<default>["&lt;Primary&gt;r"]</default>
<summary>Select rectangle tool</summary>
</key>
<key type="as" name="select-resize-tool">
<default>["&lt;Primary&gt;x"]</default>
<summary>Select resize tool</summary>
</key>
<key type="as" name="select-text-shape">
<default>["&lt;Primary&gt;t"]</default>
<summary>Select text tool</summary>
</key>
<key type="as" name="smooth-last-element">
<default>["&lt;Primary&gt;equal"]</default>
<summary>Smooth last brushstroke</summary>
</key>
<key type="as" name="switch-color-palette">
<default>["&lt;Primary&gt;KP_Divide","&lt;Primary&gt;slash"]</default>
<summary>Change color palette</summary>
</key>
<key type="as" name="switch-color-palette-reverse">
<default>["&lt;Primary&gt;&lt;Shift&gt;KP_Divide","&lt;Primary&gt;&lt;Shift&gt;slash"]</default>
<summary>Change color palette (reverse)</summary>
</key>
<key type="as" name="switch-dash">
<default>["&lt;Primary&gt;period"]</default>
<summary>Dashed line</summary>
</key>
<key type="as" name="switch-fill">
<default>["&lt;Primary&gt;a"]</default>
<summary>Toggle fill/outline</summary>
</key>
<key type="as" name="switch-fill-rule">
<default><![CDATA[['<Primary>KP_Multiply','<Primary>asterisk','<Primary><Shift>asterisk']]]></default>
<summary>Toggle fill rule</summary>
</key>
<key type="as" name="switch-font-family">
<default>["&lt;Primary&gt;f"]</default>
<summary>Change font family</summary>
</key>
<key type="as" name="switch-font-family-reverse">
<default>["&lt;Primary&gt;&lt;Shift&gt;f"]</default>
<summary>Change font family (reverse)</summary>
</key>
<key type="as" name="switch-font-style">
<default>["&lt;Primary&gt;&lt;Alt&gt;w"]</default>
<summary>Change font style</summary>
</key>
<key type="as" name="switch-font-weight">
<default>["&lt;Primary&gt;w"]</default>
<summary>Change font weight</summary>
</key>
<key type="as" name="switch-image-file">
<default>["&lt;Primary&gt;&lt;Alt&gt;i"]</default>
<summary>Change image</summary>
</key>
<key type="as" name="switch-image-file-reverse">
<default>["&lt;Primary&gt;&lt;Alt&gt;&lt;Shift&gt;i"]</default>
<summary>Change image (reverse)</summary>
</key>
<key type="as" name="switch-linecap">
<default>["&lt;Primary&gt;k"]</default>
<summary>Change linecap</summary>
</key>
<key type="as" name="switch-linejoin">
<default>["&lt;Primary&gt;j"]</default>
<summary>Change linejoin</summary>
</key>
<key type="as" name="switch-text-alignment">
<default>["&lt;Primary&gt;&lt;Alt&gt;a"]</default>
<summary>Toggle text alignment</summary>
</key>
<key type="as" name="toggle-background">
<default>["&lt;Primary&gt;b"]</default>
<summary>Add a drawing background</summary>
</key>
<key type="as" name="toggle-grid">
<default>["&lt;Primary&gt;g"]</default>
<summary>Add a grid overlay</summary>
</key> </key>
<key type="as" name="toggle-help"> <key type="as" name="toggle-help">
<default>["&lt;Primary&gt;F1"]</default> <default>["&lt;Primary&gt;F1"]</default>
<summary>toggle help</summary> <summary>Show help</summary>
<description>toggle help</description> </key>
<key type="as" name="toggle-panel-and-dock-visibility">
<default>["&lt;Primary&gt;h"]</default>
<summary>Hide panel and dock</summary>
</key>
<key type="as" name="toggle-square-area">
<default>["&lt;Primary&gt;n"]</default>
<!-- Translators: It is an action: "Make the drawing area a square" -->
<summary>Square drawing area</summary>
</key>
<key type="as" name="undo">
<default>["&lt;Primary&gt;z"]</default>
<summary>Undo last brushstroke</summary>
</key> </key>
</schema> </schema>
</schemalist> </schemalist>

104
shortcuts.js Normal file
View File

@ -0,0 +1,104 @@
/* jslint esversion: 6 */
/* exported GLOBAL_KEYBINDINGS, INTERNAL_KEYBINDINGS, OTHERS */
/*
* Copyright 2019 Abakkk
*
* This file is part of DrawOnYourScreen, a drawing extension for GNOME Shell.
* https://framagit.org/abakkk/DrawOnYourScreen
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
const Gtk = imports.gi.Gtk;
const GS_VERSION = imports.misc.config.PACKAGE_VERSION;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Convenience = ExtensionUtils.getSettings && ExtensionUtils.initTranslations ? ExtensionUtils : Me.imports.convenience;
const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext;
const internalShortcutsSchema = Convenience.getSettings(Me.metadata['settings-schema'] + '.internal-shortcuts').settings_schema;
const getKeyLabel = function(accel) {
let [keyval, mods] = Gtk.accelerator_parse(accel);
return Gtk.accelerator_get_label(keyval, mods);
};
// The setting keys of the "org.gnome.shell.extensions.draw-on-your-screen" schema.
var GLOBAL_KEYBINDINGS = [
['toggle-drawing', 'toggle-modal', 'erase-drawings'],
];
// The setting keys of the "org.gnome.shell.extensions.draw-on-your-screen.internal-shortcuts" schema.
var INTERNAL_KEYBINDINGS = [
['undo', 'redo', 'delete-last-element', 'smooth-last-element'],
['select-none-shape', 'select-line-shape', 'select-ellipse-shape', 'select-rectangle-shape', 'select-polygon-shape', 'select-polyline-shape',
'select-text-shape', 'select-image-shape', 'select-move-tool', 'select-resize-tool', 'select-mirror-tool'],
['switch-fill', 'switch-fill-rule', 'switch-color-palette', 'switch-color-palette-reverse'],
['increment-line-width', 'increment-line-width-more', 'decrement-line-width', 'decrement-line-width-more',
'switch-linejoin', 'switch-linecap', 'switch-dash'],
['switch-font-family', 'switch-font-family-reverse', 'switch-font-weight', 'switch-font-style', 'switch-text-alignment'],
['switch-image-file', 'switch-image-file-reverse', 'paste-image-files'],
['toggle-panel-and-dock-visibility', 'toggle-background', 'toggle-grid', 'toggle-square-area'],
['open-next-json', 'open-previous-json', 'save-as-json', 'export-to-svg', 'open-preferences', 'toggle-help'],
];
if (GS_VERSION < '3.36') {
// Remove 'open-preferences' keybinding.
INTERNAL_KEYBINDINGS.forEach(settingKeys => {
let index = settingKeys.indexOf('open-preferences');
if (index != -1)
settingKeys.splice(index, 1);
});
}
const getOthers = function() {
return [
[
[_("Draw"), _("Left click")],
[_("Menu"), _("Right click")],
[internalShortcutsSchema.get_key('switch-fill').get_summary(), _("Center click")],
[_("Increment/decrement line width"), _("Scroll")],
// Translators: %s are key labels (Ctrl+F1 and Ctrl+F9)
[_("Select color"), _("%s … %s").format(getKeyLabel('<Primary>1'), getKeyLabel('<Primary>9'))],
// Translators: %s is a key label
[_("Ignore pointer movement"), _("%s held").format(getKeyLabel('space'))],
[_("Leave"), getKeyLabel('Escape')],
], [
[_("Select eraser <span alpha=\"50%\">(while starting drawing)</span>"), getKeyLabel('<Shift>')],
[_("Duplicate <span alpha=\"50%\">(while starting handling)</span>"), getKeyLabel('<Shift>')],
[_("Rotate rectangle, polygon, polyline"), getKeyLabel('<Primary>')],
[_("Extend circle to ellipse"), getKeyLabel('<Primary>')],
[_("Curve line"), getKeyLabel('<Primary>')],
[_("Smooth free drawing outline"), getKeyLabel('<Primary>')],
[_("Unlock image ratio"), getKeyLabel('<Primary>')],
[_("Rotate <span alpha=\"50%\">(while moving)</span>"), getKeyLabel('<Primary>')],
[_("Stretch <span alpha=\"50%\">(while resizing)</span>"), getKeyLabel('<Primary>')],
[_("Inverse <span alpha=\"50%\">(while mirroring)</span>"), getKeyLabel('<Primary>')],
],
];
};
let _OTHERS;
// Equivalent to "var OTHERS = [[ ... ]]", but as a getter so the translations are got after the initTranslations call.
// 'this' is the module.
Object.defineProperty(this, 'OTHERS', {
get: function() {
if (!_OTHERS)
_OTHERS = getOthers();
return _OTHERS;
}
});

View File

@ -1,7 +1,3 @@
@import "./data/default.css";
/* The following styles don't affect the drawing */
/* square area */ /* square area */
.draw-on-your-screen-square-area { .draw-on-your-screen-square-area {
@ -48,7 +44,7 @@
padding-bottom: .3em; padding-bottom: .3em;
} }
.draw-on-your-screen-menu .popup-menu-icon { .draw-on-your-screen-menu .popup-menu-item > .popup-menu-icon {
icon-size: 1em; /* default: 1.09 */ icon-size: 1em; /* default: 1.09 */
padding-top: 0.03em; padding-top: 0.03em;
} }
@ -80,23 +76,20 @@
margin-top: 0; margin-top: 0;
} }
/* system-menu-action: from GS 3.34- */ .draw-on-your-screen-menu-destructive-button:hover {
.draw-on-your-screen-menu .system-menu-action { color: #e01b24; /* upstream destructive color, light: #e01b24, dark: #b2161d */
min-width: 0;
border: none;
border-radius: 32px;
padding: 12px;
margin: 0;
} }
.draw-on-your-screen-menu .system-menu-action:hover, /* override .button upstream style class */
.draw-on-your-screen-menu .system-menu-action:focus { .draw-on-your-screen-menu-action-button {
border: none; min-height: 0;
min-width: 0;
border-radius: 32px;
padding: 12px; padding: 12px;
} }
.draw-on-your-screen-menu .system-menu-action > StIcon { .draw-on-your-screen-menu-action-button > StIcon {
icon-size: 16px; icon-size: 1em;
} }
.draw-on-your-screen-menu-slider-label { .draw-on-your-screen-menu-slider-label {
@ -124,8 +117,13 @@
padding: 0.35em 0.57em; padding: 0.35em 0.57em;
} }
.draw-on-your-screen-menu-delete-button:hover { /* override .button upstream style class */
color: #f57900; .draw-on-your-screen-menu-inline-button {
min-height: 1px;
padding: 2px 4px; /* default 3px 24px */
} }
.draw-on-your-screen-menu-inline-button .popup-menu-icon {
icon-size: 0.85em; /* default 1.09 */
}