diff --git a/NEWS b/NEWS
index 9b28481..31c38da 100644
--- a/NEWS
+++ b/NEWS
@@ -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
==================
diff --git a/README.md b/README.md
index 83a00b0..88f8f48 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ Then save your beautiful work by taking a screenshot.
## 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)
* Smooth stroke
* Draw over applications
@@ -26,7 +26,7 @@ Then save your beautiful work by taking a screenshot.
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
-## Details
+## Tips and tricks
* Draw arrows:
@@ -42,7 +42,11 @@ Then save your beautiful work by taking a screenshot.
* 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:
diff --git a/area.js b/area.js
index 2684308..d28a846 100644
--- a/area.js
+++ b/area.js
@@ -1,4 +1,5 @@
/* jslint esversion: 6 */
+/* exported Tools, DrawingArea */
/*
* Copyright 2019 Abakkk
@@ -37,24 +38,41 @@ const Screenshot = imports.ui.screenshot;
const Me = ExtensionUtils.getCurrentExtension();
const Convenience = ExtensionUtils.getSettings ? ExtensionUtils : Me.imports.convenience;
-const Extension = Me.imports.extension;
const Elements = Me.imports.elements;
const Files = Me.imports.files;
const Menu = Me.imports.menu;
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 SVG_DEBUG_EXTENDS = false;
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,
- FontWeightNames, FontStyleNames, FontStretchNames, FontVariantNames } = Elements;
+const { Shapes, Transformations } = Elements;
+const { DisplayStrings } = Menu;
+
+const FontGenericFamilies = ['Sans-Serif', 'Serif', 'Monospace', 'Cursive', 'Fantasy'];
const Manipulations = { MOVE: 100, RESIZE: 101, MIRROR: 102 };
-const ManipulationNames = { 100: "Move", 101: "Resize", 102: "Mirror" };
-var Tools = Object.assign({}, Shapes, Manipulations);
-var ToolNames = Object.assign({}, ShapeNames, ManipulationNames);
+var Tools = Object.assign({
+ getNameOf: function(value) {
+ 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.
// 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({
Name: 'DrawOnYourScreenDrawingArea',
Extends: St.DrawingArea,
- Signals: { 'show-osd': { param_types: [GObject.TYPE_STRING, 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] },
+ Signals: { 'show-osd': { param_types: [Gio.Icon.$gtype, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE, GObject.TYPE_BOOLEAN] },
'update-action-mode': {},
'leave-drawing-mode': {} },
_init: function(params, monitor, helper, loadPersistent) {
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.helper = helper;
@@ -81,16 +93,27 @@ var DrawingArea = new Lang.Class({
this.undoneElements = [];
this.currentElement = null;
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.hasGrid = false;
this.hasBackground = false;
this.textHasCursor = false;
this.dashedLine = false;
this.fill = false;
- this.colors = [Clutter.Color.new(0, 0, 0, 255)];
- this.newThemeAttributes = {};
- this.oldThemeAttributes = {};
+
+ this.connect('destroy', this._onDestroy.bind(this));
+ this.connect('notify::reactive', this._onReactiveChanged.bind(this));
+ this.drawingSettingsChangedHandler = Me.drawingSettings.connect('changed', this._onDrawingSettingsChanged.bind(this));
+ this._onDrawingSettingsChanged();
if (loadPersistent)
this._loadPersistent();
@@ -98,7 +121,7 @@ var DrawingArea = new Lang.Class({
get menu() {
if (!this._menu)
- this._menu = new Menu.DrawingMenu(this, this.monitor);
+ this._menu = new Menu.DrawingMenu(this, this.monitor, Tools);
return this._menu;
},
@@ -123,6 +146,52 @@ var DrawingArea = new Lang.Class({
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() {
// No Object.values method in GS 3.24.
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;
},
- 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() {
if (!this._fontFamilies) {
- let pangoFontFamilies = Elements.getPangoFontFamilies().filter(family => {
- return family != this.currentThemeFontFamily && FontGenericFamilies.indexOf(family) == -1;
+ let otherFontFamilies = Elements.getAllFontFamilies().filter(family => {
+ 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;
},
@@ -181,57 +235,44 @@ var DrawingArea = new Lang.Class({
this.queue_repaint();
},
- _updateStyle: function() {
- try {
- let themeNode = this.get_theme_node();
- for (let i = 1; i < 10; i++) {
- this.colors[i] = themeNode.get_color('-drawing-color' + i);
- }
- let font = themeNode.get_font();
- this.newThemeAttributes.ThemeFontFamily = font.get_family();
- try { this.newThemeAttributes.FontWeight = font.get_weight(); } catch(e) { this.newThemeAttributes.FontWeight = Pango.Weight.NORMAL; }
- this.newThemeAttributes.FontStyle = font.get_style();
- this.newThemeAttributes.FontStretch = font.get_stretch();
- this.newThemeAttributes.FontVariant = font.get_variant();
- this.newThemeAttributes.TextRightAligned = themeNode.get_text_align() == St.TextAlign.RIGHT;
- this.newThemeAttributes.LineWidth = themeNode.get_length('-drawing-line-width');
- this.newThemeAttributes.LineJoin = themeNode.get_double('-drawing-line-join');
- this.newThemeAttributes.LineCap = themeNode.get_double('-drawing-line-cap');
- this.newThemeAttributes.FillRule = themeNode.get_double('-drawing-fill-rule');
- 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);
+ _onDrawingSettingsChanged: function() {
+ this.palettes = Me.drawingSettings.get_value('palettes').deep_unpack();
+ if (!this.colors) {
+ if (this.palettes[0])
+ this.currentPalette = this.palettes[0];
+ else
+ this.currentPalette = ['Palette', ['White']];
+ }
+ if (!this.currentColor)
+ this.currentColor = this.colors[0];
+
+ if (Me.drawingSettings.get_boolean('square-area-auto')) {
+ this.squareAreaSize = Math.pow(2, 6);
+ while (this.squareAreaSize * 2 < Math.min(this.monitor.width, this.monitor.height))
+ this.squareAreaSize *= 2;
+ } else {
+ this.squareAreaSize = Me.drawingSettings.get_uint('square-area-size');
}
- for (let i = 1; i < 10; i++) {
- this.colors[i] = this.colors[i].alpha ? this.colors[i] : this.colors[0];
+ this.areaBackgroundColor = getClutterColorFromString(Me.drawingSettings.get_string('background-color'), 'black');
+
+ 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;
- // SVG does not support 'Ultra-heavy' weight (1000)
- this.newThemeAttributes.FontWeight = Math.min(this.newThemeAttributes.FontWeight, 900);
- this.newThemeAttributes.LineWidth = (this.newThemeAttributes.LineWidth > 0) ? this.newThemeAttributes.LineWidth : 3;
- this.newThemeAttributes.LineJoin = ([0, 1, 2].indexOf(this.newThemeAttributes.LineJoin) != -1) ? this.newThemeAttributes.LineJoin : Cairo.LineJoin.ROUND;
- this.newThemeAttributes.LineCap = ([0, 1, 2].indexOf(this.newThemeAttributes.LineCap) != -1) ? this.newThemeAttributes.LineCap : Cairo.LineCap.ROUND;
- this.newThemeAttributes.FillRule = ([0, 1].indexOf(this.newThemeAttributes.FillRule) != -1) ? this.newThemeAttributes.FillRule : Cairo.FillRule.WINDING;
- 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.dashOffset = Math.round(Me.drawingSettings.get_double('dash-offset') * 100) / 100;
+ if (Me.drawingSettings.get_boolean('dash-array-auto')) {
+ this.dashArray = [0, 0];
+ } else {
+ let on = Math.round(Me.drawingSettings.get_double('dash-array-on') * 100) / 100;
+ let off = Math.round(Me.drawingSettings.get_double('dash-array-off') * 100) / 100;
+ this.dashArray = [on, off];
}
- 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) {
@@ -269,27 +310,27 @@ var DrawingArea = new Lang.Class({
cr.restore();
}
- if (this.reactive && this.hasGrid && this.gridGap && this.gridGap >= 1) {
+ if (this.reactive && this.hasGrid) {
cr.save();
Clutter.cairo_set_source_color(cr, this.gridColor);
let [gridX, gridY] = [0, 0];
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.lineTo(this.monitor.width / 2 + gridX, this.monitor.height);
cr.moveTo(this.monitor.width / 2 - gridX, 0);
cr.lineTo(this.monitor.width / 2 - gridX, this.monitor.height);
- gridX += this.gridGap;
+ gridX += this.gridLineSpacing;
cr.stroke();
}
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.lineTo(this.monitor.width, this.monitor.height / 2 + gridY);
cr.moveTo(0, this.monitor.height / 2 - gridY);
cr.lineTo(this.monitor.width, this.monitor.height / 2 - gridY);
- gridY += this.gridGap;
+ gridY += this.gridLineSpacing;
cr.stroke();
}
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_Control_L) {
if (this.currentElement.points.length == 2)
- this.emit('show-osd', null, _("Press %s to get\na fourth control point")
- .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true);
+ // Translators: %s is a key label
+ this.emit('show-osd', Files.Icons.ARC, _("Press %s to get\na fourth control point")
+ .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true);
this.currentElement.addPoint();
this.updatePointerCursor(true);
this._redisplay();
@@ -425,8 +467,9 @@ var DrawingArea = new Lang.Class({
}
// Reduce computing without notable effect.
- if (Math.random() <= 0.75)
+ if (event.get_time() - (this.elementGrabberTimestamp || 0) < ELEMENT_GRABBER_TIME)
return;
+ this.elementGrabberTimestamp = event.get_time();
let coords = event.get_coords();
let [s, x, y] = this.transform_stage_point(coords[0], coords[1]);
@@ -459,7 +502,7 @@ var DrawingArea = new Lang.Class({
if (this.grabbedElementLocked) {
this.updatePointerCursor();
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;
}
}
@@ -473,6 +516,10 @@ var DrawingArea = new Lang.Class({
if (duplicate) {
// deep cloning
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)
copy.image = this.grabbedElement.image;
this.elements.push(copy);
@@ -560,34 +607,27 @@ var DrawingArea = new Lang.Class({
if (this.currentTool == Shapes.TEXT) {
this.currentElement = new Elements.DrawingElement({
shape: this.currentTool,
- color: this.currentColor.to_string(),
+ color: this.currentColor,
eraser: eraser,
- font: {
- family: this.currentFontFamily,
- weight: this.currentFontWeight,
- style: this.currentFontStyle,
- stretch: this.currentFontStretch,
- variant: this.currentFontVariant },
- text: _("Text"),
+ font: this.currentFont.copy(),
+ // Translators: initial content of the text area
+ text: pgettext("text-area-content", "Text"),
textRightAligned: this.currentTextRightAligned,
points: []
});
} else if (this.currentTool == Shapes.IMAGE) {
- let images = this.getImages();
- if (!images.length)
- return;
this.currentElement = new Elements.DrawingElement({
shape: this.currentTool,
- color: this.currentColor.to_string(),
+ color: this.currentColor,
eraser: eraser,
- image: images[this.currentImage],
+ image: this.currentImage,
operator: this.currentOperator,
points: []
});
} else {
this.currentElement = new Elements.DrawingElement({
shape: this.currentTool,
- color: this.currentColor.to_string(),
+ color: this.currentColor,
eraser: eraser,
fill: this.fill,
fillRule: this.currentFillRule,
@@ -599,9 +639,12 @@ var DrawingArea = new Lang.Class({
this.currentElement.startDrawing(startX, startY);
- if (this.currentTool == Shapes.POLYGON || this.currentTool == Shapes.POLYLINE)
- this.emit('show-osd', null, _("Press %s to mark vertices")
+ if (this.currentTool == Shapes.POLYGON || this.currentTool == Shapes.POLYLINE) {
+ let icon = Files.Icons[this.currentTool == Shapes.POLYGON ? 'TOOL_POLYGON' : 'TOOL_POLYLINE'];
+ // Translators: %s is a key label
+ this.emit('show-osd', icon, _("Press %s to mark vertices")
.format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true);
+ }
this.motionHandler = this.connect('motion-event', (actor, event) => {
if (this.spaceKeyPressed)
@@ -661,8 +704,9 @@ var DrawingArea = new Lang.Class({
let [x, y] = [this.currentElement.x, this.currentElement.y];
this.currentElement.text = '';
this.currentElement.cursorPosition = 0;
- this.emit('show-osd', null, _("Type your text and press %s")
- .format(Gtk.accelerator_get_label(Clutter.KEY_Escape, 0)), "", -1, true);
+ // Translators: %s is a key label
+ this.emit('show-osd', Files.Icons.TOOL_TEXT, _("Type your text and press %s")
+ .format(Gtk.accelerator_get_label(Clutter.KEY_Escape, 0)), "", -1, true);
this._updateTextCursorTimeout();
this.textHasCursor = true;
this._redisplay();
@@ -749,7 +793,7 @@ var DrawingArea = new Lang.Class({
setPointerCursor: function(pointerCursorName) {
if (!this.currentPointerCursorName || 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() {
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() {
@@ -846,10 +890,8 @@ var DrawingArea = new Lang.Class({
toggleSquareArea: function() {
this.isSquareArea = !this.isSquareArea;
if (this.isSquareArea) {
- let width = this.squareAreaWidth || this.squareAreaHeight || Math.min(this.monitor.width, this.monitor.height) * 3 / 4;
- let height = this.squareAreaHeight || this.squareAreaWidth || Math.min(this.monitor.width, this.monitor.height) * 3 / 4;
- 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.set_position((this.monitor.width - this.squareAreaSize) / 2, (this.monitor.height - this.squareAreaSize) / 2);
+ this.set_size(this.squareAreaSize, this.squareAreaSize);
this.add_style_class_name('draw-on-your-screen-square-area');
} else {
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) {
+ if (!this.colors[index])
+ return;
+
this.currentColor = this.colors[index];
if (this.currentElement) {
- this.currentElement.color = this.currentColor.to_string();
+ this.currentElement.color = this.currentColor;
this._redisplay();
}
// 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) {
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();
},
switchFill: function() {
this.fill = !this.fill;
- this.emit('show-osd', null, this.fill ? _("Fill") : _("Outline"), "", -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);
+ let icon = Files.Icons[this.fill ? 'FILL' : 'STROKE'];
+ this.emit('show-osd', icon, DisplayStrings.getFill(this.fill), "", -1, false);
},
switchFillRule: function() {
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() {
- 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);
this.currentFontWeight = index == fontWeights.length - 1 ? fontWeights[0] : fontWeights[index + 1];
if (this.currentElement && this.currentElement.font) {
- this.currentElement.font.weight = this.currentFontWeight;
+ this.currentElement.font.set_weight(this.currentFontWeight);
this._redisplay();
}
- this.emit('show-osd', null, `` +
- `${_(FontWeightNames[this.currentFontWeight])}`, "", -1, false);
+ this.emit('show-osd', Files.Icons.FONT_WEIGHT, `` +
+ `${DisplayStrings.FontWeight[this.currentFontWeight]}`, "", -1, false);
},
switchFontStyle: function() {
this.currentFontStyle = this.currentFontStyle == 2 ? 0 : this.currentFontStyle + 1;
if (this.currentElement && this.currentElement.font) {
- this.currentElement.font.style = this.currentFontStyle;
+ this.currentElement.font.set_style(this.currentFontStyle);
this._redisplay();
}
- this.emit('show-osd', null, `` +
- `${_(FontStyleNames[this.currentFontStyle])}`, "", -1, false);
+ this.emit('show-osd', Files.Icons.FONT_STYLE, `` +
+ `${DisplayStrings.FontStyle[this.currentFontStyle]}`, "", -1, false);
},
switchFontFamily: function(reverse) {
@@ -937,10 +990,10 @@ var DrawingArea = new Lang.Class({
else
this.currentFontFamily = (index == this.fontFamilies.length - 1) ? this.fontFamilies[0] : this.fontFamilies[index + 1];
if (this.currentElement && this.currentElement.font) {
- this.currentElement.font.family = this.currentFontFamily;
+ this.currentElement.font.set_family(this.currentFontFamily);
this._redisplay();
}
- this.emit('show-osd', null, `${_(this.currentFontFamily)}`, "", -1, false);
+ this.emit('show-osd', Files.Icons.FONT_FAMILY, `${DisplayStrings.getFontFamily(this.currentFontFamily)}`, "", -1, false);
},
switchTextAlignment: function() {
@@ -949,16 +1002,23 @@ var DrawingArea = new Lang.Class({
this.currentElement.textRightAligned = this.currentTextRightAligned;
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() {
- let images = this.getImages();
- if (!images.length)
- return;
- 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);
+ switchImageFile: function(reverse) {
+ this.currentImage = Files.Images[reverse ? 'getPrevious' : 'getNext'](this.currentImage);
+ if (this.currentImage)
+ this.emit('show-osd', this.currentImage.gicon, 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() {
@@ -984,7 +1044,7 @@ var DrawingArea = new Lang.Class({
},
_onDestroy: function() {
- this.disconnect(this.reactiveHandler);
+ Me.drawingSettings.disconnect(this.drawingSettingsChangedHandler);
this.erase();
if (this._menu)
this._menu.disable();
@@ -1001,11 +1061,10 @@ var DrawingArea = new Lang.Class({
this.buttonPressedHandler = this.connect('button-press-event', this._onButtonPressed.bind(this));
this.keyboardPopupMenuHandler = this.connect('popup-menu', this._onKeyboardPopupMenu.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._updateStyle();
+ this.get_parent().set_background_color(this.reactive && this.hasBackground ? this.areaBackgroundColor : null);
},
- leaveDrawingMode: function(save) {
+ leaveDrawingMode: function(save, erase) {
if (this.stageKeyPressedHandler) {
global.stage.disconnect(this.stageKeyPressedHandler);
this.stageKeyPressedHandler = null;
@@ -1041,14 +1100,49 @@ var DrawingArea = new Lang.Class({
this.currentElement = null;
this._stopTextCursorTimeout();
- this._redisplay();
+ if (erase)
+ this.erase();
+ else
+ this._redisplay();
this.closeMenu();
this.get_parent().set_background_color(null);
+ Files.Images.reset();
if (save)
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 ``;
+ };
+
+ let getImageSvgContent = () => {
+ return ``;
+ };
+
+ return [getGiconSvgContent, getImageSvgContent];
+ },
+
+ exportToSvg: function() {
// stop drawing or writing
if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.isWriting) {
this._stopWriting();
@@ -1062,7 +1156,7 @@ var DrawingArea = new Lang.Class({
let content = `
diff --git a/data/icons/document-export-symbolic.svg b/data/icons/document-export-symbolic.svg
new file mode 100644
index 0000000..eb158e9
--- /dev/null
+++ b/data/icons/document-export-symbolic.svg
@@ -0,0 +1,8 @@
+
+
+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
+
+
+
+
diff --git a/data/icons/fill-symbolic.svg b/data/icons/fill-symbolic.svg
index 041bd49..0a66829 100644
--- a/data/icons/fill-symbolic.svg
+++ b/data/icons/fill-symbolic.svg
@@ -1,3 +1,3 @@
-
+
diff --git a/data/icons/fillrule-evenodd-symbolic.svg b/data/icons/fillrule-evenodd-symbolic.svg
index a74de4c..da0de94 100644
--- a/data/icons/fillrule-evenodd-symbolic.svg
+++ b/data/icons/fillrule-evenodd-symbolic.svg
@@ -1,3 +1,3 @@
-
+
diff --git a/data/icons/fillrule-nonzero-symbolic.svg b/data/icons/fillrule-nonzero-symbolic.svg
index a3b9b2b..be3096c 100644
--- a/data/icons/fillrule-nonzero-symbolic.svg
+++ b/data/icons/fillrule-nonzero-symbolic.svg
@@ -1,3 +1,3 @@
-
+
diff --git a/data/icons/full-line-symbolic.svg b/data/icons/full-line-symbolic.svg
index 1919ef3..b17a91b 100644
--- a/data/icons/full-line-symbolic.svg
+++ b/data/icons/full-line-symbolic.svg
@@ -1,3 +1,3 @@
-
+
diff --git a/data/icons/linecap-symbolic.svg b/data/icons/linecap-symbolic.svg
index 0d3e9df..0df89de 100644
--- a/data/icons/linecap-symbolic.svg
+++ b/data/icons/linecap-symbolic.svg
@@ -1,4 +1,4 @@
-
-
+
+
diff --git a/data/icons/linejoin-symbolic.svg b/data/icons/linejoin-symbolic.svg
index 28a1f5e..8d5951e 100644
--- a/data/icons/linejoin-symbolic.svg
+++ b/data/icons/linejoin-symbolic.svg
@@ -1,3 +1,3 @@
-
+
diff --git a/data/icons/palette-symbolic.svg b/data/icons/palette-symbolic.svg
new file mode 100644
index 0000000..7846c46
--- /dev/null
+++ b/data/icons/palette-symbolic.svg
@@ -0,0 +1,32 @@
+
+
+
+
+Created by potrace 1.15, written by Peter Selinger 2001-2017
+https://svgsilh.com/image/2026954.html
+https://creativecommons.org/publicdomain/zero/1.0/
+
+
+
+
+
diff --git a/data/icons/smooth-symbolic.svg b/data/icons/smooth-symbolic.svg
index 903a0f5..483094e 100644
--- a/data/icons/smooth-symbolic.svg
+++ b/data/icons/smooth-symbolic.svg
@@ -1,6 +1,6 @@
-
-
-
-
+
+
+
+
diff --git a/data/icons/stroke-symbolic.svg b/data/icons/stroke-symbolic.svg
index 78bc817..29ed599 100644
--- a/data/icons/stroke-symbolic.svg
+++ b/data/icons/stroke-symbolic.svg
@@ -1,3 +1,3 @@
-
+
diff --git a/data/icons/tool-ellipse-symbolic.svg b/data/icons/tool-ellipse-symbolic.svg
new file mode 100644
index 0000000..9d93d08
--- /dev/null
+++ b/data/icons/tool-ellipse-symbolic.svg
@@ -0,0 +1,7 @@
+
+
+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
+
+
+
diff --git a/data/icons/tool-line-symbolic.svg b/data/icons/tool-line-symbolic.svg
new file mode 100644
index 0000000..953d9b5
--- /dev/null
+++ b/data/icons/tool-line-symbolic.svg
@@ -0,0 +1,7 @@
+
+
+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
+
+
+
diff --git a/data/icons/tool-mirror-symbolic.svg b/data/icons/tool-mirror-symbolic.svg
new file mode 100644
index 0000000..403970b
--- /dev/null
+++ b/data/icons/tool-mirror-symbolic.svg
@@ -0,0 +1,8 @@
+
+
+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
+
+
+
+
diff --git a/data/icons/tool-move-symbolic.svg b/data/icons/tool-move-symbolic.svg
new file mode 100644
index 0000000..d77f2e3
--- /dev/null
+++ b/data/icons/tool-move-symbolic.svg
@@ -0,0 +1,14 @@
+
+
+
+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
+
+
+
+
+
+
diff --git a/data/icons/tool-none-symbolic.svg b/data/icons/tool-none-symbolic.svg
new file mode 100644
index 0000000..83dad1a
--- /dev/null
+++ b/data/icons/tool-none-symbolic.svg
@@ -0,0 +1,17 @@
+
+
+
+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
+
+
+
+
+
+
+
+
+
+
diff --git a/data/icons/tool-polygon-symbolic.svg b/data/icons/tool-polygon-symbolic.svg
new file mode 100644
index 0000000..9c1a0b6
--- /dev/null
+++ b/data/icons/tool-polygon-symbolic.svg
@@ -0,0 +1,8 @@
+
+
+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
+
+
+
+
diff --git a/data/icons/tool-polyline-symbolic.svg b/data/icons/tool-polyline-symbolic.svg
new file mode 100644
index 0000000..2f7c894
--- /dev/null
+++ b/data/icons/tool-polyline-symbolic.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/data/icons/tool-rectangle-symbolic.svg b/data/icons/tool-rectangle-symbolic.svg
new file mode 100644
index 0000000..e7dbfe0
--- /dev/null
+++ b/data/icons/tool-rectangle-symbolic.svg
@@ -0,0 +1,7 @@
+
+
+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
+
+
+
diff --git a/data/icons/tool-resize-symbolic.svg b/data/icons/tool-resize-symbolic.svg
new file mode 100644
index 0000000..2f4d37f
--- /dev/null
+++ b/data/icons/tool-resize-symbolic.svg
@@ -0,0 +1,15 @@
+
+
+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/
+
+
+
+
+
+
+
+
+
+
+
diff --git a/elements.js b/elements.js
index c2214c7..b89800d 100644
--- a/elements.js
+++ b/elements.js
@@ -1,4 +1,5 @@
/* jslint esversion: 6 */
+/* exported Shapes, Transformations, getAllFontFamilies, DrawingElement */
/*
* Copyright 2019 Abakkk
@@ -26,28 +27,27 @@ const Lang = imports.lang;
const Pango = imports.gi.Pango;
const PangoCairo = imports.gi.PangoCairo;
-const reverseEnumeration = function(obj) {
- let reversed = {};
- Object.keys(obj).forEach(key => {
- reversed[obj[key]] = key.slice(0,1) + key.slice(1).toLowerCase().replace('_', '-');
- });
- return reversed;
+var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6, IMAGE: 7 };
+var Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, STRETCH: 3, REFLECTION: 4, INVERSION: 5 };
+
+var getAllFontFamilies = function() {
+ return PangoCairo.font_map_get_default().list_families().map(fontFamily => fontFamily.get_name()).sort((a,b) => a.localeCompare(b));
};
-var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6, IMAGE: 7 };
-var ShapeNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline", 7: "Image" };
-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);
+const getFillRuleSvgName = function(fillRule) {
+ return fillRule == Cairo.FillRule.EVEN_ODD ? 'evenodd' : 'nonzero';
+};
-var getPangoFontFamilies = function() {
- return PangoCairo.font_map_get_default().list_families().map(fontFamily => fontFamily.get_name()).sort((a,b) => a.localeCompare(b));
+const getLineCapSvgName = function(lineCap) {
+ 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;
@@ -80,10 +80,21 @@ const _DrawingElement = new Lang.Class({
if (params.transformations === undefined)
this.transformations = [];
- if (params.font && params.font.weight === 0)
- this.font.weight = 400;
- if (params.font && params.font.weight === 1)
- this.font.weight = 700;
+
+ if (params.font && !(params.font instanceof Pango.FontDescription)) {
+ // compatibility with v6.2-
+ 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) {
let angle = (params.transform.angle || 0) + (params.transform.startAngle || 0);
@@ -102,7 +113,7 @@ const _DrawingElement = new Lang.Class({
toJSON: function() {
return {
shape: this.shape,
- color: this.color,
+ color: this.color.toString(),
line: this.line,
dash: this.dash,
fill: this.fill,
@@ -114,11 +125,8 @@ const _DrawingElement = new Lang.Class({
},
buildCairo: function(cr, params) {
- if (this.color) {
- let [success, color] = Clutter.Color.from_string(this.color);
- if (success)
- Clutter.cairo_set_source_color(cr, color);
- }
+ if (this.color)
+ Clutter.cairo_set_source_color(cr, this.color);
if (this.showSymmetryElement) {
let transformation = this.lastTransformation;
@@ -244,63 +252,89 @@ const _DrawingElement = new Lang.Class({
return inElement;
},
- buildSVG: function(bgColor) {
- let transAttribute = '';
+ buildSVG: function(bgcolorString) {
+ let transforms = [];
this.transformations.slice(0).reverse().forEach(transformation => {
- transAttribute += transAttribute ? ' ' : ' transform="';
let center = this._getTransformedCenter(transformation);
if (transformation.type == Transformations.TRANSLATION) {
- transAttribute += `translate(${transformation.slideX},${transformation.slideY})`;
+ transforms.push(['translate', transformation.slideX, transformation.slideY]);
} else if (transformation.type == Transformations.ROTATION) {
- transAttribute += `translate(${center[0]},${center[1]}) `;
- transAttribute += `rotate(${transformation.angle * RADIAN}) `;
- transAttribute += `translate(${-center[0]},${-center[1]})`;
+ transforms.push(['translate', center[0], center[1]]);
+ transforms.push(['rotate', transformation.angle * RADIAN]);
+ transforms.push(['translate', -center[0], -center[1]]);
} else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.STRETCH) {
- transAttribute += `translate(${center[0]},${center[1]}) `;
- transAttribute += `rotate(${transformation.angle * RADIAN}) `;
- transAttribute += `scale(${transformation.scaleX},${transformation.scaleY}) `;
- transAttribute += `rotate(${-transformation.angle * RADIAN}) `;
- transAttribute += `translate(${-center[0]},${-center[1]})`;
+ transforms.push(['translate', center[0], center[1]]);
+ transforms.push(['rotate', transformation.angle * RADIAN]);
+ transforms.push(['scale', transformation.scaleX, transformation.scaleY]);
+ transforms.push(['rotate', -transformation.angle * RADIAN]);
+ transforms.push(['translate', -center[0], -center[1]]);
} else if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) {
- transAttribute += `translate(${transformation.slideX}, ${transformation.slideY}) `;
- transAttribute += `rotate(${transformation.angle * RADIAN}) `;
- transAttribute += `scale(${transformation.scaleX}, ${transformation.scaleY}) `;
- transAttribute += `rotate(${-transformation.angle * RADIAN}) `;
- transAttribute += `translate(${-transformation.slideX}, ${-transformation.slideY})`;
+ transforms.push(['translate', transformation.slideX, transformation.slideY]);
+ transforms.push(['rotate', transformation.angle * RADIAN]);
+ transforms.push(['scale', transformation.scaleX, transformation.scaleY]);
+ transforms.push(['rotate', -transformation.angle * RADIAN]);
+ 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 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 attributes = '';
+ let attributes = this.eraser ? `class="eraser" ` : '';
if (fill) {
- attributes = `fill="${color}"`;
+ attributes += `fill="${color}"`;
if (this.fillRule)
- attributes += ` fill-rule="${FillRuleNames[this.fillRule].toLowerCase()}"`;
+ attributes += ` fill-rule="${getFillRuleSvgName(this.fillRule)}"`;
} else {
- attributes = `fill="none"`;
+ attributes += `fill="none"`;
}
if (this.line && this.line.lineWidth) {
attributes += ` stroke="${color}"` +
` stroke-width="${this.line.lineWidth}"`;
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)
- 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])
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) {
@@ -594,15 +628,18 @@ const TextElement = new Lang.Class({
Extends: _DrawingElement,
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 {
shape: this.shape,
- color: this.color,
+ color: this.color.toString(),
eraser: this.eraser,
transformations: this.transformations,
text: this.text,
lineIndex: this.lineIndex !== undefined ? this.lineIndex : undefined,
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])
};
},
@@ -629,15 +666,8 @@ const TextElement = new Lang.Class({
if (this.points.length == 2) {
let layout = PangoCairo.create_layout(cr);
let fontSize = this.height * Pango.SCALE;
- let fontDescription = new Pango.FontDescription();
- fontDescription.set_absolute_size(fontSize);
- ['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);
+ this.font.set_absolute_size(fontSize);
+ layout.set_font_description(this.font);
layout.set_text(this.text, -1);
this.textWidth = layout.get_pixel_size()[0];
cr.moveTo(this.x, this.y - layout.get_baseline() / Pango.SCALE);
@@ -670,29 +700,29 @@ const TextElement = new Lang.Class({
return cr.inFill(x, y);
},
- _drawSvg: function(transAttribute) {
+ _drawSvg: function(transAttribute, bgcolorString) {
let row = "\n ";
let [x, y, height] = [Math.round(this.x*100)/100, Math.round(this.y*100)/100, Math.round(this.height*100)/100];
- let color = this.eraser ? bgColor : this.color;
- let attributes = '';
+ let color = this.eraser ? bgcolorString : this.color.toString();
+ let attributes = this.eraser ? `class="eraser" ` : '';
if (this.points.length == 2) {
- attributes = `fill="${color}" ` +
- `stroke="transparent" ` +
- `stroke-opacity="0" ` +
- `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()}"`;
+ attributes += `fill="${color}" ` +
+ `font-size="${height}" ` +
+ `font-family="${this.font.get_family()}"`;
+ // this.font.to_string() is not valid to fill the svg 'font' shorthand property.
+ // Each property must be filled separately.
+ ['Stretch', 'Style', 'Variant'].forEach(attribute => {
+ let lower = attribute.toLowerCase();
+ if (this.font[`get_${lower}`]() != Pango[attribute].NORMAL) {
+ let font = new Pango.FontDescription();
+ font[`set_${lower}`](this.font[`get_${lower}`]());
+ attributes += ` font-${lower}="${font.to_string()}"`;
+ }
+ });
+ if (this.font.get_weight() != Pango.Weight.NORMAL)
+ attributes += ` font-weight="${this.font.get_weight()}"`;
row += `${this.text}`;
}
@@ -745,7 +775,7 @@ const ImageElement = new Lang.Class({
toJSON: function() {
return {
shape: this.shape,
- color: this.color,
+ color: this.color.toString(),
fill: this.fill,
eraser: this.eraser,
transformations: this.transformations,
@@ -789,10 +819,10 @@ const ImageElement = new Lang.Class({
_drawSvg: function(transAttribute) {
let points = this.points;
let row = "\n ";
- let attributes = '';
+ let attributes = this.eraser ? `class="eraser" ` : '';
if (points.length == 2) {
- attributes = `fill="none"`;
+ attributes += `fill="none"`;
row += `.
*/
-const Gio = imports.gi.Gio;
-const GLib = imports.gi.GLib;
const Lang = imports.lang;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Config = imports.misc.config;
+const ExtensionUtils = imports.misc.extensionUtils;
const Main = imports.ui.main;
const OsdWindow = imports.ui.osdWindow;
const PanelMenu = imports.ui.panelMenu;
-const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Convenience = ExtensionUtils.getSettings && ExtensionUtils.initTranslations ? ExtensionUtils : Me.imports.convenience;
const Area = Me.imports.area;
+const Files = Me.imports.files;
const Helper = Me.imports.helper;
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 WRITING_ACTION_MODE = Math.pow(2,15);
// 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';
-
-var manager;
+const WARNING_COLOR_STYLE_CLASS_NAME = 'login-dialog-message-warning';
function init() {
- Convenience.initTranslations();
+ return new Extension();
}
-function enable() {
- manager = new AreaManager();
-}
+const Extension = new Lang.Class({
+ Name: 'DrawOnYourScreenExtension',
+
+ _init: function() {
+ Convenience.initTranslations();
+ },
-function disable() {
- manager.disable();
- manager = null;
-}
+ enable() {
+ if (ExtensionUtils.isOutOfDate(Me))
+ 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()),
// distributes keybinding callbacks to the active area
// and handles stylesheet and monitor changes.
-var AreaManager = new Lang.Class({
+const AreaManager = new Lang.Class({
Name: 'DrawOnYourScreenAreaManager',
_init: function() {
- this.settings = Convenience.getSettings();
this.areas = [];
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',
- this.settings,
+ Me.settings,
Meta.KeyBindingFlags.NONE,
Shell.ActionMode.ALL,
this.toggleDrawing.bind(this));
Main.wm.addKeybinding('toggle-modal',
- this.settings,
+ Me.settings,
Meta.KeyBindingFlags.NONE,
Shell.ActionMode.ALL,
this.toggleModal.bind(this));
- Main.wm.addKeybinding('erase-drawing',
- this.settings,
+ Main.wm.addKeybinding('erase-drawings',
+ Me.settings,
Meta.KeyBindingFlags.NONE,
Shell.ActionMode.ALL,
- this.eraseDrawing.bind(this));
+ this.eraseDrawings.bind(this));
this.updateAreas();
this.monitorChangedHandler = Main.layoutManager.connect('monitors-changed', this.updateAreas.bind(this));
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.persistentSettingHandler = this.settings.connect('changed::persistent-drawing', this.onPersistentSettingChanged.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)) {
- let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
- theme.load_stylesheet(this.userStyleFile);
- }
-
- this.userStyleMonitor = this.userStyleFile.monitor_file(Gio.FileMonitorFlags.WATCH_MOVES, null);
- 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)
- return;
-
- 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);
- });
+ this.desktopSettingHandler = Me.settings.connect('changed::drawing-on-desktop', this.onDesktopSettingChanged.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));
+ },
+
+ get persistentOverToggles() {
+ return Me.settings.get_boolean('persistent-over-toggles');
+ },
+
+ get persistentOverRestarts() {
+ return Me.settings.get_boolean('persistent-over-toggles') && Me.settings.get_boolean('persistent-over-restarts');
+ },
+
+ get onDesktop() {
+ return Me.settings.get_boolean('persistent-over-toggles') && Me.settings.get_boolean('drawing-on-desktop');
},
onDesktopSettingChanged: function() {
- if (this.settings.get_boolean("drawing-on-desktop"))
+ if (this.onDesktop)
this.areas.forEach(area => area.get_parent().show());
else
this.areas.forEach(area => area.get_parent().hide());
},
- onPersistentSettingChanged: function() {
- if (this.settings.get_boolean('persistent-drawing'))
+ onPersistentOverRestartsSettingChanged: function() {
+ if (this.persistentOverRestarts)
this.areas[Main.layoutManager.primaryIndex].syncPersistent();
},
+ onPersistentOverTogglesSettingChanged: function() {
+ if (!this.persistentOverToggles && !this.activeArea)
+ this.eraseDrawings();
+
+ this.onPersistentOverRestartsSettingChanged();
+ this.onDesktopSettingChanged();
+ },
+
updateIndicator: function() {
if (this.indicator) {
this.indicator.disable();
this.indicator = null;
}
- if (!this.settings.get_boolean('indicator-disabled'))
+ if (!Me.settings.get_boolean('indicator-disabled'))
this.indicator = new DrawingIndicator();
},
@@ -156,13 +168,13 @@ var AreaManager = new Lang.Class({
let monitor = this.monitors[i];
let container = new St.Widget({ name: 'drawOnYourSreenContainer' + i });
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);
container.add_child(area);
container.add_child(helper);
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.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.updateActionModeHandler = area.connect('update-action-mode', this.updateActionMode.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);
}
},
@@ -187,12 +198,14 @@ var AreaManager = new Lang.Class({
'decrement-line-width': () => this.activeArea.incrementLineWidth(-1),
'increment-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-linecap': this.activeArea.switchLineCap.bind(this.activeArea),
'switch-fill-rule': this.activeArea.switchFillRule.bind(this.activeArea),
'switch-dash' : this.activeArea.switchDash.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-line-shape': () => this.activeArea.selectTool(Area.Tools.LINE),
'select-ellipse-shape': () => this.activeArea.selectTool(Area.Tools.ELLIPSE),
@@ -208,27 +221,28 @@ var AreaManager = new Lang.Class({
// available when writing
this.internalKeybindings2 = {
- 'save-as-svg': this.activeArea.saveAsSvg.bind(this.activeArea),
- 'save-as-json': this.activeArea.saveAsJson.bind(this.activeArea),
+ 'export-to-svg': this.activeArea.exportToSvg.bind(this.activeArea),
+ 'save-as-json': this.activeArea.saveAsJson.bind(this.activeArea, true, null),
'open-previous-json': this.activeArea.loadPreviousJson.bind(this.activeArea),
'open-next-json': this.activeArea.loadNextJson.bind(this.activeArea),
'toggle-background': this.activeArea.toggleBackground.bind(this.activeArea),
'toggle-grid': this.activeArea.toggleGrid.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-reverse': this.activeArea.switchFontFamily.bind(this.activeArea, true),
'switch-font-weight': this.activeArea.switchFontWeight.bind(this.activeArea),
'switch-font-style': this.activeArea.switchFontStyle.bind(this.activeArea),
'switch-text-alignment': this.activeArea.switchTextAlignment.bind(this.activeArea),
'toggle-panel-and-dock-visibility': this.togglePanelAndDockOpacity.bind(this),
'toggle-help': this.activeArea.toggleHelp.bind(this.activeArea),
- 'open-user-stylesheet': this.openUserStyleFile.bind(this),
'open-preferences': this.openPreferences.bind(this)
};
for (let key in this.internalKeybindings1) {
Main.wm.addKeybinding(key,
- this.settings,
+ Me.internalShortcutSettings,
Meta.KeyBindingFlags.NONE,
DRAWING_ACTION_MODE,
this.internalKeybindings1[key]);
@@ -236,7 +250,7 @@ var AreaManager = new Lang.Class({
for (let key in this.internalKeybindings2) {
Main.wm.addKeybinding(key,
- this.settings,
+ Me.internalShortcutSettings,
Meta.KeyBindingFlags.NONE,
DRAWING_ACTION_MODE | WRITING_ACTION_MODE,
this.internalKeybindings2[key]);
@@ -245,10 +259,10 @@ var AreaManager = new Lang.Class({
for (let i = 1; i < 10; i++) {
let iCaptured = i;
Main.wm.addKeybinding('select-color' + i,
- this.settings,
+ Me.internalShortcutSettings,
Meta.KeyBindingFlags.NONE,
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() {
- 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() {
+ eraseDrawings: function() {
for (let i = 0; i < this.areas.length; i++)
this.areas[i].erase();
- if (this.settings.get_boolean('persistent-drawing'))
+ if (this.persistentOverRestarts)
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.remove_actor(activeContainer);
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();
} else {
Main.layoutManager._backgroundGroup.remove_actor(activeContainer);
@@ -365,8 +362,9 @@ var AreaManager = new Lang.Class({
if (Main._findModal(this.activeArea) != -1) {
Main.popModal(this.activeArea);
if (source && source == global.display)
- this.showOsd(null, 'touchpad-disabled-symbolic', _("Keyboard and pointer released"), null, null, false);
- setCursor('DEFAULT');
+ // Translators: "released" as the opposite of "grabbed"
+ this.showOsd(null, Files.Icons.UNGRAB, _("Keyboard and pointer released"), null, null, false);
+ this.setCursor('DEFAULT');
this.activeArea.reactive = false;
this.removeInternalKeybindings();
} else {
@@ -378,7 +376,7 @@ var AreaManager = new Lang.Class({
this.activeArea.reactive = true;
this.activeArea.initPointerCursor();
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;
@@ -387,10 +385,11 @@ var AreaManager = new Lang.Class({
toggleDrawing: function() {
if (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.activeArea.leaveDrawingMode(save);
+ this.showOsd(null, Files.Icons.LEAVE, _("Leaving drawing mode"));
+ this.activeArea.leaveDrawingMode(save, erase);
if (this.hiddenList)
this.togglePanelAndDockOpacity();
@@ -410,9 +409,10 @@ var AreaManager = new Lang.Class({
}
this.activeArea.enterDrawingMode();
- this.osdDisabled = this.settings.get_boolean('osd-disabled');
- let label = _("Press %s for help").format(this.activeArea.helper.helpKeyLabel) + "\n\n" + _("Entering drawing mode");
- this.showOsd(null, this.enterGicon, label, null, null, true);
+ this.osdDisabled = Me.settings.get_boolean('osd-disabled');
+ // Translators: %s is a key label
+ let label = "" + _("Press %s for help").format(this.activeArea.helper.helpKeyLabel) + "\n\n" + _("Entering drawing mode");
+ this.showOsd(null, Files.Icons.ENTER, label, null, null, true);
}
if (this.indicator)
@@ -446,10 +446,8 @@ var AreaManager = new Lang.Class({
if (level && GS_VERSION > '3.33.0')
level = level / 100;
- if (icon && typeof icon == 'string')
- icon = new Gio.ThemedIcon({ name: icon });
- else if (!icon)
- icon = this.enterGicon;
+ if (!icon)
+ icon = Files.Icons.ENTER;
let osdWindow = Main.osdWindowManager._osdWindows[activeIndex];
@@ -503,13 +501,20 @@ var AreaManager = new Lang.Class({
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() {
for (let i = 0; i < this.areas.length; i++) {
let area = this.areas[i];
area.disconnect(area.leaveDrawingHandler);
area.disconnect(area.updateActionModeHandler);
area.disconnect(area.showOsdHandler);
- area.disconnect(area.showOsdGiconHandler);
let container = area.get_parent();
container.get_parent().remove_actor(container);
container.destroy();
@@ -518,37 +523,35 @@ var AreaManager = new Lang.Class({
},
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) {
Main.layoutManager.disconnect(this.monitorChangedHandler);
this.monitorChangedHandler = null;
}
if (this.indicatorSettingHandler) {
- this.settings.disconnect(this.indicatorSettingHandler);
+ Me.settings.disconnect(this.indicatorSettingHandler);
this.indicatorSettingHandler = null;
}
if (this.desktopSettingHandler) {
- this.settings.disconnect(this.desktopSettingHandler);
+ Me.settings.disconnect(this.desktopSettingHandler);
this.desktopSettingHandler = null;
}
- if (this.persistentSettingHandler) {
- this.settings.disconnect(this.persistentSettingHandler);
- this.persistentSettingHandler = null;
+ if (this.persistentOverTogglesSettingHandler) {
+ Me.settings.disconnect(this.persistentOverTogglesSettingHandler);
+ this.persistentOverTogglesSettingHandler = null;
+ }
+ if (this.persistentOverRestartsSettingHandler) {
+ Me.settings.disconnect(this.persistentOverRestartsSettingHandler);
+ this.persistentOverRestartsSettingHandler = null;
}
if (this.activeArea)
this.toggleDrawing();
Main.wm.removeKeybinding('toggle-drawing');
Main.wm.removeKeybinding('toggle-modal');
- Main.wm.removeKeybinding('erase-drawing');
+ Main.wm.removeKeybinding('erase-drawings');
this.removeAreas();
+ Files.Images.disable();
+ Files.Jsons.disable();
if (this.indicator)
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]);
-}
-
-
diff --git a/files.js b/files.js
index 0001cc4..22a31bc 100644
--- a/files.js
+++ b/files.js
@@ -1,4 +1,5 @@
/* jslint esversion: 6 */
+/* exported Icons, Image, Images, Json, Jsons, getDateString, saveSvg */
/*
* Copyright 2019 Abakkk
@@ -26,13 +27,54 @@ const GdkPixbuf = imports.gi.GdkPixbuf;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Lang = imports.lang;
+const St = imports.gi.St;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
-const EXAMPLE_IMAGES = 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 EXAMPLE_IMAGE_DIRECTORY = Me.dir.get_child('data').get_child('images');
+const DEFAULT_USER_IMAGE_LOCATION = GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir'], 'images']);
+const Clipboard = St.Clipboard.get_default();
+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({
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() {
- if (!this._bytes) {
- if (this.file)
- 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));
- }
+ if (!this._bytes)
+ this._bytes = new GLib.Bytes(GLib.base64_decode(this.base64));
return this._bytes;
},
@@ -124,34 +146,207 @@ var Image = new Lang.Class({
}
});
-var getImages = function() {
- let images = [];
+// Add a gicon generator to Image. It is used with image files and it takes { file, info } as params.
+const ImageWithGicon = new Lang.Class({
+ Name: 'DrawOnYourScreenImageWithGicon',
+ Extends: Image,
- [EXAMPLE_IMAGES, USER_IMAGES].forEach(directory => {
- let enumerator;
- try {
- enumerator = directory.enumerate_children('standard::display-name,standard::content-type', Gio.FileQueryInfoFlags.NONE, null);
- } catch(e) {
- return;
+ get displayName() {
+ return this.info.get_display_name();
+ },
+
+ get contentType() {
+ 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);
- 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() }));
- fileInfo = enumerator.next_file(null);
+ return this.gicon;
+ },
+
+ get bytes() {
+ 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) => {
- return a.displayName.localeCompare(b.displayName);
- });
+ get bytes() {
+ 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({
Name: 'DrawOnYourScreenJson',
@@ -160,6 +355,10 @@ var Json = new Lang.Class({
this[key] = params[key];
},
+ get isPersistent() {
+ return this.name == Me.metadata['persistent-file-name'];
+ },
+
toString: function() {
return this.displayName || this.name;
},
@@ -169,10 +368,10 @@ var Json = new Lang.Class({
},
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`]));
- return this._file || null;
+ return this._file;
},
set file(file) {
@@ -180,62 +379,181 @@ var Json = new Lang.Class({
},
get contents() {
- let success_, contents;
- try {
- [success_, contents] = this.file.load_contents(null);
- if (contents instanceof Uint8Array)
- contents = ByteArray.toString(contents);
- } catch(e) {
- return null;
+ if (this._contents === undefined) {
+ try {
+ [, this._contents] = this.file.load_contents(null);
+ if (this._contents instanceof Uint8Array)
+ this._contents = ByteArray.toString(this._contents);
+ } catch(e) {
+ this._contents = null;
+ }
}
- return contents;
+
+ return this._contents;
},
set contents(contents) {
+ if (this.isPersistent && (this.contents == contents || !this.contents && contents == '[]'))
+ return;
+
try {
this.file.replace_contents(contents, null, false, Gio.FileCreateFlags.NONE, null);
} catch(e) {
this.file.get_parent().make_directory_with_parents(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() {
- let directory = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir']]));
+// Access jsons with getPersistent, getDated, getNamed, getPrevious, getNext, getSorted or by iterating over it.
+var Jsons = {
+ _jsons: [],
+ _upToDate: false,
- let enumerator;
- try {
- enumerator = directory.enumerate_children('standard::name,standard::display-name,standard::content-type,time::modified', Gio.FileQueryInfoFlags.NONE, null);
- } catch(e) {
- 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')
- }));
+ disable: function() {
+ if (this._monitor) {
+ this._monitor.disconnect(this._monitorHandler);
+ this._monitor.cancel();
}
- 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() {
let date = GLib.DateTime.new_now_local();
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;
+ }
+};
+
diff --git a/helper.js b/helper.js
index e3bfe2e..444ff93 100644
--- a/helper.js
+++ b/helper.js
@@ -1,4 +1,5 @@
/* jslint esversion: 6 */
+/* exported DrawingHelper */
/*
* Copyright 2019 Abakkk
@@ -25,24 +26,19 @@ const Lang = imports.lang;
const St = imports.gi.St;
const Config = imports.misc.config;
+const ExtensionUtils = imports.misc.extensionUtils;
const Tweener = imports.ui.tweener;
-const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
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 GS_VERSION = Config.PACKAGE_VERSION;
const HELPER_ANIMATION_TIME = 0.25;
const MEDIA_KEYS_SCHEMA = 'org.gnome.settings-daemon.plugins.media-keys';
-const MEDIA_KEYS_KEYS = {
- 'screenshot': "Screenshot",
- 'screenshot-clip': "Screenshot to clipboard",
- 'area-screenshot': "Area screenshot",
- 'area-screenshot-clip': "Area screenshot to clipboard"
-};
+const MEDIA_KEYS_KEYS = ['screenshot', 'screenshot-clip', 'area-screenshot', 'area-screenshot-clip'];
// DrawingHelper provides the "help osd" (Ctrl + F1)
// It uses the same texts as in prefs
@@ -55,24 +51,27 @@ var DrawingHelper = new Lang.Class({
this.parent(params);
this.monitor = monitor;
this.hide();
- this.settings = Convenience.getSettings();
- this.settingHandler = this.settings.connect('changed', this._onSettingChanged.bind(this));
- this.connect('destroy', () => this.settings.disconnect(this.settingHandler));
+ this.settingsHandler = Me.settings.connect('changed', this._onSettingsChanged.bind(this));
+ 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')
this._updateHelpKeyLabel();
if (this.vbox) {
this.vbox.destroy();
- this.vbox = null;
+ delete this.vbox;
}
},
_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);
},
@@ -88,63 +87,72 @@ var DrawingHelper = new Lang.Class({
this.add_actor(this.vbox);
this.vbox.add_child(new St.Label({ text: _("Global") }));
- for (let settingKey in Prefs.GLOBAL_KEYBINDINGS) {
- let hbox = new St.BoxLayout({ vertical: false });
- if (settingKey.indexOf('-separator-') != -1) {
+ Shortcuts.GLOBAL_KEYBINDINGS.forEach((settingKeys, index) => {
+ if (index)
+ 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);
- 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") }));
- for (let i = 0; i < Prefs.OTHER_SHORTCUTS.length; i++) {
- if (Prefs.OTHER_SHORTCUTS[i].desc.indexOf('-separator-') != -1) {
+ Shortcuts.OTHERS.forEach((pairs, index) => {
+ if (index)
this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' }));
- continue;
- }
- let hbox = new St.BoxLayout({ vertical: false });
- hbox.add_child(new St.Label({ text: _(Prefs.OTHER_SHORTCUTS[i].desc) }));
- hbox.add_child(new St.Label({ text: Prefs.OTHER_SHORTCUTS[i].shortcut, x_expand: true }));
- hbox.get_children()[0].get_clutter_text().set_use_markup(true);
- this.vbox.add_child(hbox);
- }
+
+ pairs.forEach(pair => {
+ let [action, shortcut] = pair;
+ let hbox = new St.BoxLayout({ vertical: false });
+ hbox.add_child(new St.Label({ text: action }));
+ hbox.add_child(new St.Label({ text: shortcut, x_expand: true }));
+ 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' }));
- for (let settingKey in Prefs.INTERNAL_KEYBINDINGS) {
- if (settingKey.indexOf('-separator-') != -1) {
+ Shortcuts.INTERNAL_KEYBINDINGS.forEach((settingKeys, index) => {
+ if (index)
this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' }));
- continue;
- }
- let hbox = new St.BoxLayout({ vertical: false });
- 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.INTERNAL_KEYBINDINGS[settingKey]) }));
- hbox.add_child(new St.Label({ text: Gtk.accelerator_get_label(keyval, mods), x_expand: true }));
- this.vbox.add_child(hbox);
- }
+
+ settingKeys.forEach(settingKey => {
+ if (!Me.internalShortcutSettings.get_strv(settingKey)[0])
+ return;
+
+ let hbox = new St.BoxLayout({ vertical: false });
+ let [keyval, mods] = Gtk.accelerator_parse(Me.internalShortcutSettings.get_strv(settingKey)[0] || '');
+ hbox.add_child(new St.Label({ text: Me.internalShortcutSettings.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);
+ });
+ });
let mediaKeysSettings;
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") }));
- for (let settingKey in MEDIA_KEYS_KEYS) {
+ for (let settingKey of MEDIA_KEYS_KEYS) {
if (!mediaKeysSettings.settings_schema.has_key(settingKey))
continue;
let shortcut = GS_VERSION < '3.33.0' ? mediaKeysSettings.get_string(settingKey) : mediaKeysSettings.get_strv(settingKey)[0];
if (!shortcut)
continue;
- let [keyval, mods] = Gtk.accelerator_parse(shortcut);
+ let [keyval, mods] = Gtk.accelerator_parse(shortcut || '');
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 }));
this.vbox.add_child(hbox);
}
diff --git a/locale/POTFILES.in b/locale/POTFILES.in
new file mode 100644
index 0000000..3a9591f
--- /dev/null
+++ b/locale/POTFILES.in
@@ -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
diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot
index 15ef699..e94b500 100644
--- a/locale/draw-on-your-screen.pot
+++ b/locale/draw-on-your-screen.pot
@@ -8,9 +8,9 @@
# You are free to translate them or not.
msgid ""
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"
-"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"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -19,66 +19,344 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-msgid "About"
+#. Translators: %s is a key label
+#, javascript-format
+msgid ""
+"Press %s to get\n"
+"a fourth control point"
msgstr ""
-# You are free to translate the extension name, that is displayed in About page, or not.
-msgid "Draw On You Screen"
+msgid "Mark a point of symmetry"
msgstr ""
-msgid "Version %d"
+msgid "Draw a line of symmetry"
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 ""
-# Add your name here, for example:
-# (add "\n" as separator if there is many translators)
-# msgid "translator-credits"
-# msgstr "Me"
-# or, with mail:
-# msgid "translator-credits"
-# msgstr "Me"
-# or, with page:
-# msgid "translator-credits"
-# msgstr "Me"
-# else keep it empty.
-# It will be displayed in about page
-msgid "translator-credits"
+#. Translators: %s is a key label
+#, javascript-format
+msgid "Press %s to mark vertices"
msgstr ""
-msgid "Preferences"
+#. Translators: %s is a key label
+#, javascript-format
+msgid "Type your text and press %s"
+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 %s for help"
+msgstr ""
+
+msgid "Entering drawing mode"
msgstr ""
msgid "Global"
msgstr ""
-msgid "Enter/leave drawing mode"
+msgid "Internal"
msgstr ""
-# There is a similar text in GNOME Boxes (https://gitlab.gnome.org/GNOME/gnome-boxes/tree/master/po)
-msgid "Grab/ungrab keyboard and pointer"
+msgid "System"
msgstr ""
-msgid "Erase all drawings"
+msgid "Dashed line"
msgstr ""
-msgid "Persistent"
+#. Translators: as the alternative to "Dashed line"
+msgid "Full line"
msgstr ""
-msgid "Persistent drawing through session restart"
+msgid "Fill"
msgstr ""
-msgid "Drawing on the desktop"
+#. Translators: as the alternative to "Fill"
+msgid "Outline"
msgstr ""
-msgid "Draw On Your Screen becomes Draw On Your Desktop"
+#. Translators: fill-rule SVG attribute
+msgid "Nonzero"
msgstr ""
-msgid "Disable on-screen notifications"
+msgid "Evenodd"
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"
+#. "translator2\n"
+#. "translator3"
+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 ""
msgid "Draw"
@@ -102,17 +380,19 @@ msgstr ""
msgid "Scroll"
msgstr ""
+#. Translators: %s are key labels (Ctrl+F1 and Ctrl+F9)
msgid "Select color"
msgstr ""
-# %s are key labels (Ctrl+F1 and Ctrl+F9)
+#, javascript-format
msgid "%s … %s"
msgstr ""
+#. Translators: %s is a key label
msgid "Ignore pointer movement"
msgstr ""
-# %s is a key label
+#, javascript-format
msgid "%s held"
msgstr ""
@@ -137,6 +417,9 @@ msgstr ""
msgid "Smooth free drawing outline"
msgstr ""
+msgid "Unlock image ratio"
+msgstr ""
+
msgid "Rotate (while moving)"
msgstr ""
@@ -146,73 +429,224 @@ msgstr ""
msgid "Inverse (while mirroring)"
msgstr ""
-msgid "Internal"
+msgid "Drawing on the desktop"
msgstr ""
-msgid "(in drawing mode)"
+msgid "Draw On Your Screen becomes Draw On Your Desktop"
msgstr ""
-msgid "Undo last brushstroke"
+msgid "Erase all drawings"
msgstr ""
-msgid "Redo last brushstroke"
+msgid "Disable panel indicator"
msgstr ""
-msgid "Erase last brushstroke"
+msgid "Disable on-screen notifications"
msgstr ""
-msgid "Smooth last brushstroke"
+msgid "Persistent over toggles"
msgstr ""
-msgid "Select line"
+msgid "Drawing remains when toggling drawing mode"
msgstr ""
-msgid "Select ellipse"
+msgid "Persistent over restarts"
msgstr ""
-msgid "Select rectangle"
+msgid "Drawing is automatically saved to a file"
msgstr ""
-msgid "Select polygon"
+msgid "Enter/leave drawing mode"
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 ""
-msgid "Select image"
+msgid "Background color"
msgstr ""
-msgid "Select text"
+msgid "The color of the drawing area background"
msgstr ""
-msgid "Select move"
+msgid "Automatic dash array"
msgstr ""
-msgid "Select resize"
+msgid "Compute the lengths from the line width"
msgstr ""
-msgid "Select mirror"
+msgid "Dash array on"
msgstr ""
-msgid "Toggle fill/outline"
+msgid "The dash length in pixels"
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 ""
msgid "Decrement line width"
msgstr ""
-msgid "Increment line width even more"
-msgstr ""
-
msgid "Decrement line width even more"
msgstr ""
-msgid "Change linejoin"
+msgid "Erase last brushstroke"
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 ""
msgid "Toggle fill rule"
@@ -224,289 +658,42 @@ msgstr ""
msgid "Change font family (reverse)"
msgstr ""
-msgid "Change font weight"
-msgstr ""
-
msgid "Change font style"
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"
msgstr ""
-msgid "Change image file"
-msgstr ""
-
-msgid "Hide panel and dock"
-msgstr ""
-
msgid "Add a drawing background"
msgstr ""
msgid "Add a grid overlay"
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"
msgstr ""
-msgid ""
-"Default drawing style attributes (color palette, font, line, dash) are defined in an editable css file.\n"
-"See “%s”."
+msgid "Hide panel and dock"
msgstr ""
-msgid ""
-"When you save elements made with eraser in a SVG file, "
-"they are colored with background color, transparent if it is disabled.\n"
-"See “%s” or edit the SVG file afterwards."
+#. Translators: It is an action: "Make the drawing area a square"
+msgid "Square drawing area"
msgstr ""
-msgid "Screenshot"
+msgid "Undo last brushstroke"
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 "Press %s for help"
-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 %s 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 %s to mark vertices"
-msgstr ""
-
-# %s is a key label
-msgid ""
-"Type your text and press %s"
-msgstr ""
-
-# as the alternative to "Fill"
-msgid "Outline"
-msgstr ""
-
-msgid "Dashed line"
-msgstr ""
-
-msgid "Full line"
-msgstr ""
-
-msgid "Left aligned"
-msgstr ""
-
-msgid "Nonzero"
-msgstr ""
-
-
diff --git a/menu.js b/menu.js
index 911f082..86a6032 100644
--- a/menu.js
+++ b/menu.js
@@ -1,4 +1,5 @@
/* jslint esversion: 6 */
+/* exported DisplayStrings, DrawingMenu */
/*
* Copyright 2019 Abakkk
@@ -21,7 +22,6 @@
*/
const Clutter = imports.gi.Clutter;
-const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
@@ -30,44 +30,121 @@ const St = imports.gi.St;
const BoxPointer = imports.ui.boxpointer;
const Config = imports.misc.config;
+const Dash = imports.ui.dash;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
const Slider = imports.ui.slider;
const ExtensionUtils = imports.misc.extensionUtils;
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 _ = 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 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
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) {
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({
Name: 'DrawOnYourScreenDrawingMenu',
- _init: function(area, monitor) {
+ _init: function(area, monitor, drawingTools) {
this.area = area;
+ this.drawingTools = drawingTools;
+
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.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();
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() {
+ delete this.area;
+ delete this.drawingTools;
this.menuManager.removeMenu(this.menu);
Main.layoutManager.uiGroup.remove_actor(this.menu.actor);
this.menu.destroy();
@@ -154,106 +222,101 @@ var DrawingMenu = new Lang.Class({
_redisplay: function() {
this.menu.removeAll();
- this.actionButtons = [];
- let groupItem = new PopupMenu.PopupBaseMenuItem({ reactive: false, can_focus: false, style_class: "draw-on-your-screen-menu-group-item" });
- getActor(groupItem).add_child(this._createActionButton(_("Undo"), this.area.undo.bind(this.area), 'edit-undo-symbolic'));
- getActor(groupItem).add_child(this._createActionButton(_("Redo"), this.area.redo.bind(this.area), 'edit-redo-symbolic'));
- getActor(groupItem).add_child(this._createActionButton(_("Erase"), this.area.deleteLastElement.bind(this.area), 'edit-clear-all-symbolic'));
- getActor(groupItem).add_child(this._createActionButton(_("Smooth"), this.area.smoothLastElement.bind(this.area), this.smoothIcon));
+ let groupItem = new PopupMenu.PopupBaseMenuItem({ reactive: false, can_focus: false, style_class: 'draw-on-your-screen-menu-group-item' });
+ this.undoButton = new ActionButton(_("Undo"), 'edit-undo-symbolic', this.area.undo.bind(this.area), this._updateActionSensitivity.bind(this));
+ this.redoButton = new ActionButton(_("Redo"), 'edit-redo-symbolic', this.area.redo.bind(this.area), this._updateActionSensitivity.bind(this));
+ this.eraseButton = new ActionButton(_("Erase"), 'edit-clear-all-symbolic', this.area.deleteLastElement.bind(this.area), this._updateActionSensitivity.bind(this));
+ this.smoothButton = new ActionButton(_("Smooth"), Files.Icons.SMOOTH, this.area.smoothLastElement.bind(this.area), this._updateActionSensitivity.bind(this));
+ this.eraseButton.child.add_style_class_name('draw-on-your-screen-menu-destructive-button');
+ this.smoothButton.child.add_style_class_name('draw-on-your-screen-menu-destructive-button');
+ getActor(groupItem).add_child(this.undoButton);
+ getActor(groupItem).add_child(this.redoButton);
+ getActor(groupItem).add_child(this.eraseButton);
+ getActor(groupItem).add_child(this.smoothButton);
this.menu.addMenuItem(groupItem);
this._addSeparator(this.menu, true);
- this._addSubMenuItem(this.menu, 'document-edit-symbolic', Area.ToolNames, this.area, 'currentTool', this._updateSectionVisibility.bind(this));
- this.colorItem = this._addColorSubMenuItem(this.menu);
- this.fillItem = this._addSwitchItem(this.menu, _("Fill"), this.strokeIcon, this.fillIcon, this.area, 'fill', this._updateSectionVisibility.bind(this));
+ this.toolItem = this._addToolSubMenuItem(this.menu, this._updateSectionVisibility.bind(this));
+ this.paletteItem = this._addPaletteSubMenuItem(this.menu, Files.Icons.PALETTE);
+ 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.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._addSeparator(this.menu);
let lineSection = new PopupMenu.PopupMenuSection();
this._addSliderItem(lineSection, this.area, 'currentLineWidth');
- this._addSubMenuItem(lineSection, this.linejoinIcon, Elements.LineJoinNames, this.area, 'currentLineJoin');
- this._addSubMenuItem(lineSection, this.linecapIcon, Elements.LineCapNames, this.area, 'currentLineCap');
- this._addSwitchItem(lineSection, _("Dashed"), this.fullLineIcon, this.dashedLineIcon, this.area, 'dashedLine');
+ this._addSubMenuItem(lineSection, Files.Icons.LINEJOIN, DisplayStrings.LineJoin, this.area, 'currentLineJoin');
+ this._addSubMenuItem(lineSection, Files.Icons.LINECAP, DisplayStrings.LineCap, this.area, 'currentLineCap');
+ this._addSwitchItem(lineSection, DisplayStrings.getDashedLine(true), Files.Icons.FULL_LINE, Files.Icons.DASHED_LINE, this.area, 'dashedLine');
this._addSeparator(lineSection);
this.menu.addMenuItem(lineSection);
lineSection.itemActivated = () => {};
this.lineSection = lineSection;
let fontSection = new PopupMenu.PopupMenuSection();
- this._addFontFamilySubMenuItem(fontSection, 'font-x-generic-symbolic');
- this._addSubMenuItem(fontSection, 'format-text-bold-symbolic', Elements.FontWeightNames, this.area, 'currentFontWeight');
- this._addSubMenuItem(fontSection, 'format-text-italic-symbolic', Elements.FontStyleNames, this.area, 'currentFontStyle');
- this._addSwitchItem(fontSection, _("Right aligned"), 'format-justify-left-symbolic', 'format-justify-right-symbolic', this.area, 'currentTextRightAligned');
+ this._addFontFamilySubMenuItem(fontSection, Files.Icons.FONT_FAMILY);
+ this._addSubMenuItem(fontSection, Files.Icons.FONT_WEIGHT, DisplayStrings.FontWeight, this.area, 'currentFontWeight');
+ this._addSubMenuItem(fontSection, Files.Icons.FONT_STYLE, DisplayStrings.FontStyle, this.area, 'currentFontStyle');
+ this._addSwitchItem(fontSection, DisplayStrings.getTextAlignment(true), Files.Icons.LEFT_ALIGNED, Files.Icons.RIGHT_ALIGNED, this.area, 'currentTextRightAligned');
this._addSeparator(fontSection);
this.menu.addMenuItem(fontSection);
fontSection.itemActivated = () => {};
this.fontSection = fontSection;
let imageSection = new PopupMenu.PopupMenuSection();
- let images = this.area.getImages();
- 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.imageItem = this._addImageSubMenuItem(imageSection);
this._addSeparator(imageSection);
this.menu.addMenuItem(imageSection);
imageSection.itemActivated = () => {};
this.imageSection = imageSection;
- let manager = Extension.manager;
- this._addSimpleSwitchItem(this.menu, _("Hide panel and dock"), manager.hiddenList ? true : false, manager.togglePanelAndDockOpacity.bind(manager));
- this._addSimpleSwitchItem(this.menu, _("Add a drawing 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, _("Square drawing area"), this.area.isSquareArea, this.area.toggleSquareArea.bind(this.area));
+ let areaManager = Me.stateObj.areaManager;
+ this._addSimpleSwitchItem(this.menu, getSummary('toggle-panel-and-dock-visibility'), !!areaManager.hiddenList, areaManager.togglePanelAndDockOpacity.bind(areaManager));
+ this._addSimpleSwitchItem(this.menu, getSummary('toggle-background'), this.area.hasBackground, this.area.toggleBackground.bind(this.area));
+ this._addSimpleSwitchItem(this.menu, getSummary('toggle-grid'), this.area.hasGrid, this.area.toggleGrid.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._addDrawingNameItem(this.menu);
- this._addOpenDrawingSubMenuItem(this.menu);
- this._addSaveDrawingSubMenuItem(this.menu);
+ this._addOpenDrawingSubMenuItem(this.menu, _("Open drawing"), 'document-open-symbolic');
+ 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');
- this.menu.addAction(_("Edit style"), manager.openUserStyleFile.bind(manager), 'document-page-setup-symbolic');
- this.menu.addAction(_("Show help"), () => { this.close(); this.area.toggleHelp(); }, 'preferences-desktop-keyboard-shortcuts-symbolic');
+ groupItem = new PopupMenu.PopupBaseMenuItem({ reactive: false, can_focus: false, style_class: 'draw-on-your-screen-menu-group-item' });
+ this.saveButton = new ActionButton(getSummary('save-as-json'), 'document-save-symbolic', this.area.saveAsJson.bind(this.area, false, this._onDrawingSaved.bind(this)), null);
+ 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._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() {
- let [undoButton, redoButton, eraseButton, smoothButton] = this.actionButtons;
- undoButton.reactive = this.area.elements.length > 0;
- redoButton.reactive = this.area.undoneElements.length > 0;
- eraseButton.reactive = this.area.elements.length > 0;
- smoothButton.reactive = this.area.elements.length > 0 && this.area.elements[this.area.elements.length - 1].shape == Area.Tools.NONE;
+ this.undoButton.child.reactive = this.area.elements.length > 0;
+ this.redoButton.child.reactive = this.area.undoneElements.length > 0;
+ this.eraseButton.child.reactive = this.area.elements.length > 0;
+ this.smoothButton.child.reactive = this.area.elements.length > 0 && this.area.elements[this.area.elements.length - 1].shape == this.drawingTools.NONE;
+ this.saveButton.child.reactive = this.area.elements.length > 0;
+ this.svgButton.child.reactive = this.area.elements.length > 0;
+ this.saveDrawingSubMenuItem.setSensitive(this.area.elements.length > 0);
},
_updateSectionVisibility: function() {
- let [isText, isImage] = [this.area.currentTool == 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.fontSection.actor.visible = isText;
this.imageSection.actor.visible = isImage;
this.colorItem.setSensitive(!isImage);
+ this.paletteItem.setSensitive(!isImage);
this.fillItem.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' });
getActor(item).insert_child_at_index(item.icon, 1);
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);
- else if (icon)
- item.icon.set_icon_name(icon);
item.connect('toggled', (item, state) => {
target[targetProperty] = state;
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);
- else if (icon)
- item.icon.set_icon_name(icon);
if (onToggled)
onToggled();
});
@@ -296,26 +355,26 @@ var DrawingMenu = new Lang.Class({
_addSliderItem: function(menu, target, targetProperty) {
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);
if (GS_VERSION < '3.33.0') {
slider.connect('value-changed', (slider, value, property) => {
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)
- label.add_style_class_name(Extension.WARNING_COLOR_STYLE_CLASS_NAME);
+ label.add_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME);
else
- label.remove_style_class_name(Extension.WARNING_COLOR_STYLE_CLASS_NAME);
+ label.remove_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME);
});
} else {
slider.connect('notify::value', () => {
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)
- label.add_style_class_name(Extension.WARNING_COLOR_STYLE_CLASS_NAME);
+ label.add_style_class_name(WARNING_COLOR_STYLE_CLASS_NAME);
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);
},
- _addSubMenuItem: function(menu, icon, obj, target, targetProperty, callback) {
- if (targetProperty == 'currentImage')
- 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);
+ _addSubMenuItem: function(menu, icon, obj, target, targetProperty) {
+ let item = new PopupMenu.PopupSubMenuMenuItem(String(obj[target[targetProperty]]), icon ? true : false);
- item.menu.itemActivated = () => {
- item.menu.close();
- };
+ item.icon.set_gicon(icon);
+ item.menu.itemActivated = item.menu.close;
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
- for (let i in obj) {
- let text;
- if (targetProperty == 'currentFontWeight')
- text = `${_(obj[i])}`;
- else if (targetProperty == 'currentFontStyle')
- text = `${_(obj[i])}`;
- else
- text = _(String(obj[i]));
+ Object.keys(obj).forEach(key => {
+ let text = targetProperty == 'currentFontWeight' ? `${obj[key]}` :
+ targetProperty == 'currentFontStyle' ? `${obj[key]}` :
+ String(obj[key]);
- let iCaptured = Number(i);
let subItem = item.menu.addAction(text, () => {
- item.label.set_text(_(String(obj[iCaptured])));
- target[targetProperty] = iCaptured;
- if (targetProperty == 'currentImage')
- item.icon.set_gicon(obj[iCaptured].gicon);
- if (callback)
- callback();
+ item.label.set_text(String(obj[key]));
+ target[targetProperty] = Number(key);
});
subItem.label.get_clutter_text().set_use_markup(true);
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
- if (obj == Area.ToolNames && i == Area.Tools.POLYGON)
- item.menu.moveMenuItem(subItem, 4);
- else if (obj == Area.ToolNames && i == Area.Tools.POLYLINE)
- item.menu.moveMenuItem(subItem, 5);
- }
+ if (key == this.drawingTools.POLYGON)
+ item.menu.moveMenuItem(subItem, Number(this.drawingTools.TEXT));
+ else if (key == this.drawingTools.POLYLINE)
+ item.menu.moveMenuItem(subItem, Number(this.drawingTools.TEXT) + 1);
+ });
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);
return item;
},
- _addFontFamilySubMenuItem: function(menu, icon) {
- let item = new PopupMenu.PopupSubMenuMenuItem(this.area.currentFontFamily, true);
- item.icon.set_icon_name(icon);
+ _addPaletteSubMenuItem: function(menu, icon) {
+ let text = _(this.area.currentPalette[0] || "Palette");
+ let item = new PopupMenu.PopupSubMenuMenuItem(text, true);
+ item.icon.set_gicon(icon);
- item.menu.itemActivated = () => {
- item.menu.close();
- };
+ item.menu.itemActivated = 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.open = (animate) => {
if (!item.menu.isOpen && item.menu.isEmpty()) {
this.area.fontFamilies.forEach(family => {
- let subItem = item.menu.addAction(_(family), () => {
- item.label.set_text(_(family));
+ let subItem = item.menu.addAction(DisplayStrings.getFontFamily(family), () => {
+ item.label.set_text(DisplayStrings.getFontFamily(family));
this.area.currentFontFamily = family;
});
if (FONT_FAMILY_STYLE)
@@ -428,6 +532,34 @@ var DrawingMenu = new Lang.Class({
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) {
this.drawingNameMenuItem = new PopupMenu.PopupMenuItem('', { reactive: false, activate: false });
this.drawingNameMenuItem.setSensitive(false);
@@ -436,24 +568,22 @@ var DrawingMenu = new Lang.Class({
},
_updateDrawingNameMenuItem: function() {
- getActor(this.drawingNameMenuItem).visible = this.area.jsonName ? true : false;
- if (this.area.jsonName) {
+ getActor(this.drawingNameMenuItem).visible = this.area.currentJson ? true : false;
+ if (this.area.currentJson) {
let prefix = this.area.drawingContentsHasChanged ? "* " : "";
- this.drawingNameMenuItem.label.set_text(`${prefix}${this.area.jsonName}`);
+ this.drawingNameMenuItem.label.set_text(`${prefix}${this.area.currentJson.name}`);
this.drawingNameMenuItem.label.get_clutter_text().set_use_markup(true);
}
},
- _addOpenDrawingSubMenuItem: function(menu) {
- let item = new PopupMenu.PopupSubMenuMenuItem(_("Open drawing"), true);
+ _addOpenDrawingSubMenuItem: function(menu, label, icon) {
+ let item = new PopupMenu.PopupSubMenuMenuItem(label, true);
this.openDrawingSubMenuItem = item;
this.openDrawingSubMenu = item.menu;
- item.setSensitive(Boolean(Files.getJsons().length));
- item.icon.set_icon_name('document-open-symbolic');
+ item.setSensitive(Boolean(Files.Jsons.getSorted().length));
+ item.icon.set_icon_name(icon);
- item.menu.itemActivated = () => {
- item.menu.close();
- };
+ item.menu.itemActivated = item.menu.close;
item.menu.openOld = item.menu.open;
item.menu.open = (animate) => {
@@ -467,13 +597,16 @@ var DrawingMenu = new Lang.Class({
_populateOpenDrawingSubMenu: function() {
this.openDrawingSubMenu.removeAll();
- let jsons = Files.getJsons();
- jsons.forEach(json => {
+ Files.Jsons.getSorted().forEach(json => {
+ if (!json.gicon)
+ json.addSvgContents(...this.area.getSvgContentsForJson(json));
+
let subItem = this.openDrawingSubMenu.addAction(`${String(json)}`, () => {
- this.area.loadJson(json.name);
+ this.area.loadJson(json);
this._updateDrawingNameMenuItem();
- this._updateSaveDrawingSubMenuItemSensitivity();
- });
+ this._updateActionSensitivity();
+ }, json.gicon);
+
subItem.label.get_clutter_text().set_use_markup(true);
getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment);
@@ -483,10 +616,22 @@ var DrawingMenu = new Lang.Class({
});
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',
- style_class: 'popup-menu-icon',
- x_align: Clutter.ActorAlign.END }) });
+ style_class: 'popup-menu-icon' }) });
getActor(subItem).add_child(deleteButton);
deleteButton.connect('clicked', () => {
@@ -499,16 +644,13 @@ var DrawingMenu = new Lang.Class({
this.openDrawingSubMenuItem.setSensitive(!this.openDrawingSubMenu.isEmpty());
},
- _addSaveDrawingSubMenuItem: function(menu) {
- let item = new PopupMenu.PopupSubMenuMenuItem(_("Save drawing"), true);
+ _addSaveDrawingSubMenuItem: function(menu, label, icon) {
+ let item = new PopupMenu.PopupSubMenuMenuItem(label, true);
this.saveDrawingSubMenuItem = item;
- this._updateSaveDrawingSubMenuItemSensitivity();
this.saveDrawingSubMenu = item.menu;
- item.icon.set_icon_name('document-save-symbolic');
+ item.icon.set_icon_name(icon);
- item.menu.itemActivated = () => {
- item.menu.close();
- };
+ item.menu.itemActivated = item.menu.close;
item.menu.openOld = item.menu.open;
item.menu.open = (animate) => {
@@ -530,13 +672,14 @@ var DrawingMenu = new Lang.Class({
_populateSaveDrawingSubMenu: function() {
this.saveDrawingSubMenu.removeAll();
- let saveEntry = new DrawingMenuEntry({ initialTextGetter: Files.getDateString,
- entryActivateCallback: (text) => {
- this.area.saveAsJsonWithName(text, this._onDrawingSaved.bind(this));
- this.saveDrawingSubMenu.toggle();
- },
- invalidStrings: [Me.metadata['persistent-file-name'], '/'],
- primaryIconName: 'insert-text' });
+ let saveEntry = new Entry({ initialTextGetter: () => this.area.currentJson ? this.area.currentJson.name : "",
+ hint_text: _("Type a name"),
+ entryActivateCallback: (text) => {
+ this.area.saveAsJsonWithName(text, this._onDrawingSaved.bind(this));
+ this.saveDrawingSubMenu.toggle();
+ },
+ invalidStrings: [Me.metadata['persistent-file-name'], '/'],
+ primaryIconName: 'insert-text' });
this.saveDrawingSubMenu.addMenuItem(saveEntry.item);
},
@@ -568,8 +711,51 @@ const updateSubMenuAdjustment = function(itemActor) {
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
-const DrawingMenuEntry = new Lang.Class({
+const Entry = new Lang.Class({
Name: 'DrawOnYourScreenDrawingMenuEntry',
_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.entry = new St.Entry({
+ hint_text: params.hint_text || "",
style_class: 'search-entry draw-on-your-screen-menu-entry',
track_hover: true,
reactive: true,
diff --git a/metadata.json b/metadata.json
index 4690c5b..c581739 100644
--- a/metadata.json
+++ b/metadata.json
@@ -17,5 +17,5 @@
"3.34",
"3.36"
],
- "version": 6.2
+ "version": 6.3
}
diff --git a/prefs.js b/prefs.js
index 8ff013d..30369ea 100644
--- a/prefs.js
+++ b/prefs.js
@@ -1,4 +1,5 @@
/* jslint esversion: 6 */
+/* exported init, buildPrefsWidget */
/*
* Copyright 2019 Abakkk
@@ -20,104 +21,27 @@
* along with this program. If not, see .
*/
+const Atk = imports.gi.Atk;
+const Gdk = imports.gi.Gdk;
+const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
-const Lang = imports.lang;
-const Config = imports.misc.config;
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 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 GS_VERSION = Config.PACKAGE_VERSION;
const MARGIN = 10;
-
-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('1'), getKeyLabel('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 (while starting drawing)", shortcut: getKeyLabel('') },
- { desc: "Duplicate (while starting handling)", shortcut: getKeyLabel('') },
- { desc: "Rotate rectangle, polygon, polyline", shortcut: getKeyLabel('') },
- { desc: "Extend circle to ellipse", shortcut: getKeyLabel('') },
- { desc: "Curve line", shortcut: getKeyLabel('') },
- { desc: "Smooth free drawing outline", shortcut: getKeyLabel('') },
- { desc: "Rotate (while moving)", shortcut: getKeyLabel('') },
- { desc: "Stretch (while resizing)", shortcut: getKeyLabel('') },
- { desc: "Inverse (while mirroring)", shortcut: getKeyLabel('') }
-];
+const ROWBOX_MARGIN_PARAMS = { margin_top: MARGIN / 2, margin_bottom: MARGIN / 2, margin_left: MARGIN, margin_right: MARGIN };
function init() {
Convenience.initTranslations();
@@ -128,7 +52,6 @@ function buildPrefsWidget() {
let switcher = new Gtk.StackSwitcher({halign: Gtk.Align.CENTER, visible: true, stack: topStack});
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
let window = topStack.get_toplevel();
- window.resize(720,500);
let headerBar = window.get_titlebar();
headerBar.custom_title = switcher;
return false;
@@ -146,8 +69,13 @@ const TopStack = new GObject.Class({
_init: function(params) {
this.parent({ transition_type: 1, transition_duration: 500, expand: true });
this.prefsPage = new PrefsPage();
+ // Translators: "Preferences" page in 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();
+ // Translators: "About" page in preferences
this.add_titled(this.aboutPage, 'about', _("About"));
}
});
@@ -158,14 +86,17 @@ const AboutPage = new GObject.Class({
Extends: Gtk.ScrolledWindow,
_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);
- let name = " " + _(Me.metadata.name) + "";
- let version = _("Version %d").format(Me.metadata.version);
- let description = _(Me.metadata.description);
+ // Translators: you are free to translate the extension name, that is displayed in About page, or not
+ let name = " " + _("Draw On You Screen") + "";
+ // 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 = "" + Me.metadata.url + "";
let licenceName = _GTK("GNU General Public License, version 2 or later");
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);
- 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 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: "" + _GTK("Created by") + "" });
@@ -187,6 +118,11 @@ const AboutPage = new GObject.Class({
creditBox.pack_start(rightBox, true, true, 5);
vbox.add(creditBox);
+ // Translators: add your name here or keep it empty, it will be displayed in about page, e.g.
+ // msgstr ""
+ // "translator1\n"
+ // "translator2\n"
+ // "translator3"
if (_("translator-credits") != "translator-credits" && _("translator-credits") != "") {
leftBox.pack_start(new Gtk.Label(), false, false, 0);
rightBox.pack_start(new Gtk.Label(), false, false, 0);
@@ -196,7 +132,235 @@ const AboutPage = new GObject.Class({
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({
@@ -205,143 +369,276 @@ const PrefsPage = new GObject.Class({
Extends: Gtk.ScrolledWindow,
_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);
- let globalFrame = new Gtk.Frame({ label_yalign: 1.0 });
- globalFrame.set_label_widget(new Gtk.Label({ margin_bottom: MARGIN/2, use_markup: true, label: "" + _("Global") + "" }));
+ let globalFrame = new Frame({ label: _("Global") });
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);
- let styleContext = listBox.get_style_context();
- styleContext.add_class('background');
+ Shortcuts.GLOBAL_KEYBINDINGS.forEach((settingKeys, index) => {
+ 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);
- globalKeybindingsWidget.margin = MARGIN;
- listBox.add(globalKeybindingsWidget);
+ let persistentOverTogglesKey = schema.get_key('persistent-over-toggles');
+ let persistentOverTogglesRow = new PrefRow({ label: persistentOverTogglesKey.get_summary(), desc: persistentOverTogglesKey.get_description() });
+ 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 persistentLabelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
- let persistentLabel1 = new Gtk.Label({label: _("Persistent")});
- let persistentLabel2 = new Gtk.Label({ use_markup: true, halign: 1, wrap: true, xalign: 0, label: "" + _("Persistent drawing through session restart") + "" });
- persistentLabel1.set_halign(1);
- persistentLabel2.get_style_context().add_class('dim-label');
- persistentLabelBox.pack_start(persistentLabel1, true, true, 0);
- 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 persistentOverRestartsKey = schema.get_key('persistent-over-restarts');
+ let persistentOverRestartsRow = new PrefRow({ label: persistentOverRestartsKey.get_summary(), desc: persistentOverRestartsKey.get_description() });
+ let persistentOverRestartsSwitch = new Gtk.Switch();
+ settings.bind('persistent-over-restarts', persistentOverRestartsSwitch, 'active', 0);
+ persistentOverRestartsRow.addWidget(persistentOverRestartsSwitch, true);
+ persistentOverTogglesSwitch.bind_property('active', persistentOverRestartsSwitch, 'sensitive', GObject.BindingFlags.SYNC_CREATE);
+ listBox.add(persistentOverRestartsRow);
- let desktopBox = new Gtk.Box({ margin_top: MARGIN/2, margin_bottom: MARGIN/2, margin_left: MARGIN, margin_right: MARGIN });
- let desktopLabelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
- let desktopLabel1 = new Gtk.Label({label: _("Drawing on the desktop")});
- let desktopLabel2 = new Gtk.Label({ use_markup: true, halign: 1, wrap: true, xalign: 0, label: "" + _("Draw On Your Screen becomes Draw On Your Desktop") + "" });
- desktopLabel1.set_halign(1);
- desktopLabel2.get_style_context().add_class('dim-label');
- desktopLabelBox.pack_start(desktopLabel1, true, true, 0);
- 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 desktopKey = schema.get_key('drawing-on-desktop');
+ let desktopRow = new PrefRow({ label: desktopKey.get_summary(), desc: desktopKey.get_description() });
+ let desktopSwitch = new Gtk.Switch();
+ settings.bind('drawing-on-desktop', desktopSwitch, 'active', 0);
+ desktopRow.addWidget(desktopSwitch, true);
+ persistentOverTogglesSwitch.bind_property('active', desktopSwitch, 'sensitive', GObject.BindingFlags.SYNC_CREATE);
+ listBox.add(desktopRow);
- let osdBox = new Gtk.Box({ margin_top: MARGIN/2, margin_bottom: MARGIN/2, margin_left: MARGIN, margin_right: MARGIN });
- let osdLabelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
- let osdLabel1 = new Gtk.Label({label: _("Disable on-screen notifications")});
- osdLabel1.set_halign(1);
- osdLabelBox.pack_start(osdLabel1, true, true, 0);
- let osdSwitch = new Gtk.Switch({valign: 3});
- 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 osdKey = schema.get_key('osd-disabled');
+ let osdRow = new PrefRow({ label: osdKey.get_summary(), desc: osdKey.get_description() });
+ let osdSwitch = new Gtk.Switch();
+ settings.bind('osd-disabled', osdSwitch, 'active', 0);
+ osdRow.addWidget(osdSwitch, true);
+ listBox.add(osdRow);
- let indicatorBox = new Gtk.Box({ margin_top: MARGIN/2, margin_bottom: MARGIN/2, margin_left: MARGIN, margin_right: MARGIN });
- let indicatorLabelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
- let indicatorLabel1 = new Gtk.Label({label: _("Disable panel indicator")});
- indicatorLabel1.set_halign(1);
- indicatorLabelBox.pack_start(indicatorLabel1, true, true, 0);
- let indicatorSwitch = new Gtk.Switch({valign: 3});
- 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 indicatorKey = schema.get_key('indicator-disabled');
+ let indicatorRow = new PrefRow({ label: indicatorKey.get_summary(), desc: indicatorKey.get_description() });
+ let indicatorSwitch = new Gtk.Switch();
+ settings.bind('indicator-disabled', indicatorSwitch, 'active', 0);
+ indicatorRow.addWidget(indicatorSwitch, true);
+ listBox.add(indicatorRow);
- let children = listBox.get_children();
- 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: "" + _("Internal") + " " + _("(in drawing mode)") }));
+ let internalFrame = new Frame({ label: _("Internal"), desc: _("In drawing mode") });
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);
- styleContext = listBox.get_style_context();
- styleContext.add_class('background');
+ Shortcuts.OTHERS.forEach((pairs, index) => {
+ 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++) {
- if (OTHER_SHORTCUTS[i].desc.indexOf('-separator-') != -1) {
- listBox.add(new Gtk.Box({ margin_top: MARGIN, margin_left: MARGIN, margin_right: MARGIN }));
- continue;
+ listBox.add(new Gtk.Box(ROWBOX_MARGIN_PARAMS));
+
+ Shortcuts.INTERNAL_KEYBINDINGS.forEach((settingKeys, index) => {
+ 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: `${params.label}` });
+ 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: `${params.desc}`, 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);
- otherBox.pack_start(otherLabel2, false, false, 4);
- listBox.add(otherBox);
+ }
+ }
+});
+
+const PixelSpinButton = new GObject.Class({
+ Name: 'DrawOnYourScreenPixelSpinButton',
+ 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);
- internalKeybindingsWidget.margin = MARGIN;
- listBox.add(internalKeybindingsWidget);
-
- let styleBox = new Gtk.Box({ margin: MARGIN });
- let styleLabel = new Gtk.Label({
- wrap: true,
- xalign: 0,
- use_markup: true,
- label: _("Default drawing style attributes (color palette, font, line, dash) are defined in an editable css file.\n" +
- "See “%s”.").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 eraser in a SVG file, " +
- "they are colored with background color, transparent if it is disabled.\n" +
- "See “%s” 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);
- }
+ let file = Gio.File.new_for_commandline_arg(location);
+ if (file.query_exists(null))
+ this.set_file(file);
+ },
+
+ vfunc_file_set: function(args) {
+ this.notify('location');
}
});
@@ -351,11 +648,11 @@ const KeybindingsWidget = new GObject.Class({
GTypeName: 'DrawOnYourScreenKeybindingsWidget',
Extends: Gtk.Box,
- _init: function(keybindings, settings) {
- this.parent();
+ _init: function(settingKeys, settings) {
+ this.parent(ROWBOX_MARGIN_PARAMS);
this.set_orientation(Gtk.Orientation.VERTICAL);
- this._keybindings = keybindings;
+ this._settingKeys = settingKeys;
this._settings = settings;
this._columns = {
@@ -436,17 +733,27 @@ const KeybindingsWidget = new GObject.Class({
this.keybinding_column = keybinding_column;
this.action_column = action_column;
+ this._settings.connect('changed', this._onSettingsChanged.bind(this));
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() {
this._store.clear();
- for(let settings_key in this._keybindings) {
- if (settings_key.indexOf('-separator-') != -1)
- continue;
+ this._settingKeys.forEach(settingKey => {
let [key, mods] = Gtk.accelerator_parse(
- this._settings.get_strv(settings_key)[0]
+ this._settings.get_strv(settingKey)[0] || ''
);
let iter = this._store.append();
@@ -458,12 +765,12 @@ const KeybindingsWidget = new GObject.Class({
this._columns.KEY
],
[
- settings_key,
- _(this._keybindings[settings_key]),
+ settingKey,
+ this._settings.settings_schema.get_key(settingKey).get_summary(),
mods,
key
]
);
- }
+ });
}
});
diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled
index 92584ee..76b17ef 100644
Binary files a/schemas/gschemas.compiled and b/schemas/gschemas.compiled differ
diff --git a/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml b/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml
index 4fe2746..d7a9158 100644
--- a/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml
+++ b/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml
@@ -1,290 +1,342 @@
-
+
+
+
false
- move drawing on desktop
- move drawing on desktop
+ Drawing on the desktop
+ Draw On Your Screen becomes Draw On Your Desktop]]>
-
- false
- persistent drawing
- persistent drawing
-
-
- false
- disable OSD notifications
- disable on-screen notifications
+
+ ["<Alt><Super>e"]
+ Erase all drawings
false
- disable panel indicator
- disable panel indicator
+ Disable panel indicator
+
+
+ false
+ Disable on-screen notifications
+
+
+ true
+ Persistent over toggles
+ Drawing remains when toggling drawing mode
+
+
+ false
+ Persistent over restarts
+ Drawing is automatically saved to a file
["<Alt><Super>d"]
- toggle drawing
- enter or leave drawing mode
+ Enter/leave drawing mode
["<Primary><Alt><Super>d"]
- toggle modeless/modal
- toggle modeless/modal
+
+ Grab/ungrab keyboard and pointer
+
-
- ["<Alt><Super>e"]
- erase drawing
- erase drawing
+
+
+
+ "#2e2e2e"
+ Background color
+ The color of the drawing area background
-
- ["<Primary>z"]
- undo
- undo
+
+ true
+ Automatic dash array
+ Compute the lengths from the line width
-
- ["<Primary><Shift>z"]
- redo
- redo
+
+
+ 5
+ Dash array on
+ The dash length in pixels
-
- ["Delete"]
- delete last element
- delete last element
+
+
+ 15
+ Dash array off
+ The gap between the dashes in pixels
-
- ["<Primary>equal"]
- smooth last brushstroke
- smooth last brushstroke
+
+
+ 0
+ Dash offset
+ The dash offset in pixels
-
- ["<Primary>b"]
- toggle drawing background
- toggle drawing background
+
+ "Gray"
+ Grid overlay color
+ The color of the lines
-
- ["<Primary>g"]
- toggle grid overlay
- toggle grid overlay
+
+ true
+ Automatic grid overlay line
+ Compute the lengths from the screen size
-
- ["<Primary>h"]
- hide or show panel and dock
- hide or show panel and dock
+
+
+ 10
+ Grid overlay line spacing
+ The gap between lines in pixels
-
- ["<Primary>n"]
- toggle square area
- toggle square area
+
+
+ 0.5
+ Grid overlay line width
+ The line width in pixels
-
- ["<Primary>e"]
- select cercle
- select a cercle
+
+ ""
+ Image location
+ The location of the directory in which the image tool picks
-
- ["<Primary>r"]
- select rectangle
- select rectangle
+
+
+ [
+ ("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)"])
+ ]
+
+ Color palettes
+ The palettes of drawing colors
-
- ["<Primary>y"]
- select polygon
- select polygon
+
+ true
+ Automatic square area size
+ Compute the area size from the screen size
-
- ["<Primary>u"]
- select polyline
- select polyline
-
-
- ["<Primary>l"]
- select line
- select a line
-
-
- ["<Primary>t"]
- select text
- select text
-
-
- ["<Primary>i"]
- select image
- select image
-
-
- ["<Primary>p"]
- unselect shape (free drawing)
- unselect shape (free drawing)
-
-
- ["<Primary>m"]
- select move tool
- select move tool
-
-
- ["<Primary>x"]
- select resize tool
- select resize tool
-
-
- ["<Primary>c"]
- select mirror tool
- select mirror tool
-
-
- KP_Add','plus']]]>
- increment the line width
- increment the line width
+
+
+ 512
+ Square area size
+ The size of the area in pixels
+
+
KP_Subtract','minus','minus']]]>
- decrement the line width
- decrement the line width
-
-
- ["<Primary><Shift>KP_Add"]
- increment the line width even more
- increment the line width even more
+ Decrement line width
["<Primary><Shift>KP_Subtract"]
- decrement the line width even more
- decrement the line width even more
+ Decrement line width even more
-
- ["<Primary>j"]
- switch linejoin
- switch linejoin
+
+ ["Delete"]
+ Erase last brushstroke
-
- ["<Primary>k"]
- switch linecap
- switch linecap
+
+ ["<Primary><Alt>s"]
+ Export drawing to a SVG file
-
- KP_Multiply','asterisk','asterisk']]]>
- switch fill rule
- switch fill rule
+
+ KP_Add','plus']]]>
+ Increment line width
-
- ["<Primary>period"]
- switch dash
- switch dash
-
-
- ["<Primary>a"]
- switch fill
- switch fill
-
-
- KP_1','1']]]>
- select color1
- select color1
-
-
- KP_2','2']]]>
- select color2
- select color2
-
-
- KP_3','3']]]>
- select color3
- select color3
-
-
- KP_4','4']]]>
- select color4
- select color4
-
-
- KP_5','5']]]>
- select color5
- select color5
-
-
- KP_6','6']]]>
- select color6
- select color6
-
-
- KP_7','7']]]>
- select color7
- select color7
-
-
- KP_8','8']]]>
- select color8
- select color8
-
-
- KP_9','9']]]>
- select color9
- select color9
-
-
- ["<Primary>f"]
- switch font family
- switch font family
-
-
- ["<Primary><Shift>f"]
- switch font family (reverse)
- switch font family (reverse)
-
-
- ["<Primary>w"]
- switch font weight
- switch font weight
-
-
- ["<Primary><Shift>w"]
- switch font style
- switch font style
-
-
- ["<Primary><Shift>a"]
- switch text alignment
- switch text alignment
-
-
- ["<Primary><Shift>i"]
- switch image file
- switch image file
-
-
- ["<Primary>o"]
- open user stylesheet to edit style
- open user stylesheet to edit style
-
-
- ["<Primary><Shift>s"]
- Save drawing as a svg file
- Save drawing as a svg file
-
-
- ["<Primary>s"]
- Save drawing as a json file
- Save drawing as a json file
-
-
- ["<Primary>Page_Down"]
- Open previous json file
- Open previous json file
+
+ ["<Primary><Shift>KP_Add"]
+ Increment line width even more
- ["<Primary>Page_Up"]
- Open next json file
- Open next json file
+ ["<Primary>o"]
+ Open next drawing
["<Primary>comma"]
Open preferences
- Open preferences
+
+
+ ["<Primary><Shift>o"]
+ Open previous drawing
+
+
+ ["<Primary>v"]
+ Add images from the clipboard
+
+
+ ["<Primary><Shift>z"]
+ Redo last brushstroke
+
+
+ ["<Primary>s"]
+ Save drawing
+
+
+ KP_1','1']]]>
+ Select color 1
+
+
+
+ KP_2','2']]]>
+ Select color 2
+
+
+ KP_3','3']]]>
+ Select color 3
+
+
+ KP_4','4']]]>
+ Select color 4
+
+
+ KP_5','5']]]>
+ Select color 5
+
+
+ KP_6','6']]]>
+ Select color 6
+
+
+ KP_7','7']]]>
+ Select color 7
+
+
+ KP_8','8']]]>
+ Select color 8
+
+
+ KP_9','9']]]>
+ Select color 9
+
+
+ ["<Primary>e"]
+ Select ellipse tool
+
+
+ ["<Primary>i"]
+ Select image tool
+
+
+ ["<Primary>l"]
+ Select line tool
+
+
+ ["<Primary>c"]
+ Select mirror tool
+
+
+ ["<Primary>m"]
+ Select move tool
+
+
+ ["<Primary>p"]
+ Select free drawing
+
+
+ ["<Primary>y"]
+ Select polygon tool
+
+
+ ["<Primary>u"]
+ Select polyline tool
+
+
+ ["<Primary>r"]
+ Select rectangle tool
+
+
+ ["<Primary>x"]
+ Select resize tool
+
+
+ ["<Primary>t"]
+ Select text tool
+
+
+ ["<Primary>equal"]
+ Smooth last brushstroke
+
+
+ ["<Primary>KP_Divide","<Primary>slash"]
+ Change color palette
+
+
+ ["<Primary><Shift>KP_Divide","<Primary><Shift>slash"]
+ Change color palette (reverse)
+
+
+ ["<Primary>period"]
+ Dashed line
+
+
+ ["<Primary>a"]
+ Toggle fill/outline
+
+
+ KP_Multiply','asterisk','asterisk']]]>
+ Toggle fill rule
+
+
+ ["<Primary>f"]
+ Change font family
+
+
+ ["<Primary><Shift>f"]
+ Change font family (reverse)
+
+
+ ["<Primary><Alt>w"]
+ Change font style
+
+
+ ["<Primary>w"]
+ Change font weight
+
+
+ ["<Primary><Alt>i"]
+ Change image
+
+
+ ["<Primary><Alt><Shift>i"]
+ Change image (reverse)
+
+
+ ["<Primary>k"]
+ Change linecap
+
+
+ ["<Primary>j"]
+ Change linejoin
+
+
+ ["<Primary><Alt>a"]
+ Toggle text alignment
+
+
+ ["<Primary>b"]
+ Add a drawing background
+
+
+ ["<Primary>g"]
+ Add a grid overlay
["<Primary>F1"]
- toggle help
- toggle help
+ Show help
+
+
+ ["<Primary>h"]
+ Hide panel and dock
+
+
+ ["<Primary>n"]
+
+ Square drawing area
+
+
+ ["<Primary>z"]
+ Undo last brushstroke
diff --git a/shortcuts.js b/shortcuts.js
new file mode 100644
index 0000000..488485a
--- /dev/null
+++ b/shortcuts.js
@@ -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 .
+ */
+
+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('1'), getKeyLabel('9'))],
+ // Translators: %s is a key label
+ [_("Ignore pointer movement"), _("%s held").format(getKeyLabel('space'))],
+ [_("Leave"), getKeyLabel('Escape')],
+ ], [
+ [_("Select eraser (while starting drawing)"), getKeyLabel('')],
+ [_("Duplicate (while starting handling)"), getKeyLabel('')],
+ [_("Rotate rectangle, polygon, polyline"), getKeyLabel('')],
+ [_("Extend circle to ellipse"), getKeyLabel('')],
+ [_("Curve line"), getKeyLabel('')],
+ [_("Smooth free drawing outline"), getKeyLabel('')],
+ [_("Unlock image ratio"), getKeyLabel('')],
+ [_("Rotate (while moving)"), getKeyLabel('')],
+ [_("Stretch (while resizing)"), getKeyLabel('')],
+ [_("Inverse (while mirroring)"), getKeyLabel('')],
+ ],
+ ];
+};
+
+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;
+ }
+});
+
diff --git a/stylesheet.css b/stylesheet.css
index 8da1267..768f8ca 100644
--- a/stylesheet.css
+++ b/stylesheet.css
@@ -1,7 +1,3 @@
-@import "./data/default.css";
-
-/* The following styles don't affect the drawing */
-
/* square area */
.draw-on-your-screen-square-area {
@@ -48,7 +44,7 @@
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 */
padding-top: 0.03em;
}
@@ -80,23 +76,20 @@
margin-top: 0;
}
-/* system-menu-action: from GS 3.34- */
-.draw-on-your-screen-menu .system-menu-action {
- min-width: 0;
- border: none;
- border-radius: 32px;
- padding: 12px;
- margin: 0;
+.draw-on-your-screen-menu-destructive-button:hover {
+ color: #e01b24; /* upstream destructive color, light: #e01b24, dark: #b2161d */
}
-.draw-on-your-screen-menu .system-menu-action:hover,
-.draw-on-your-screen-menu .system-menu-action:focus {
- border: none;
+/* override .button upstream style class */
+.draw-on-your-screen-menu-action-button {
+ min-height: 0;
+ min-width: 0;
+ border-radius: 32px;
padding: 12px;
}
-.draw-on-your-screen-menu .system-menu-action > StIcon {
- icon-size: 16px;
+.draw-on-your-screen-menu-action-button > StIcon {
+ icon-size: 1em;
}
.draw-on-your-screen-menu-slider-label {
@@ -124,8 +117,13 @@
padding: 0.35em 0.57em;
}
-.draw-on-your-screen-menu-delete-button:hover {
- color: #f57900;
+/* override .button upstream style class */
+.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 */
+}
+