diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..c8590f2 --- /dev/null +++ b/NEWS @@ -0,0 +1,51 @@ +v5.1 - March 2020 +================= + +* Add "Open" and "Save drawing" +* Change some keyboard shortcuts +* Replace "smoothed stroke" preference with `Ctrl` key modifier +* Add `space` key modifier to ignore pointer movement #20 +* User style is now stored in user data directory (`user.css`) +* Customizable square area size #22 + +v5 - December 2019 +================== + +* Improve pointer cursor +* Add 3.24 version as supported +* Use maxLevel in line-width OSD +* Small fix with text shape that displayed keybindings + + +v4.1 - October 2019 +=================== + +* GS 3.34 compatibility +* Create drawing menu on demand +* Allow 0 px line width because stroke lines cannot have color with some transparency + + +v4 - April 2019 +=============== + +* Add drawing menu +* Add panel indicator +* Prefs to disable indicator and notifications +* Change middle click action + +v3 - March 2019 +=============== + +* Fix area container integration #1 +* Add persistence + +v2 - March 2019 +=============== + +* Add transformations +* Add square area + +v1 - March 2019 +=============== + +* Initial release diff --git a/README.md b/README.md index 8eb7998..39e555a 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,11 @@ -__Draw On Your Screen__ -======================= +# Draw On Your Screen + Start drawing with Super+Alt+D. Then save your beautiful work by taking a screenshot. ![](https://framagit.org/abakkk/DrawOnYourScreen/raw/ressources/screenshot.jpg) -Features : ----------- +## Features * Basic shapes (rectangle, circle, ellipse, line, curve, text, free) * Smooth stroke @@ -15,13 +14,26 @@ Features : * Multi-monitor support * Export to SVG -Install : ----------- +## Install 1. Download and decompress or clone the repository -2. Place the resulting directory in ~/.local/share/gnome-shell/extensions -3. IMPORTANT: change the directory name to drawOnYourScreen@abakkk.framagit.org -4. A small shot of `alt + F2` `r` to restart Gnome-shell under Xorg, restart or relogin under Wayland -5. Enable the extension in Gnome-tweak-tool +2. Place the resulting directory in `~/.local/share/gnome-shell/extensions` +3. **Change the directory name** to `drawOnYourScreen@abakkk.framagit.org` +4. Xorg: type `alt + F2` and `r` to restart gnome-shell + Wayland: restart or re-login +5. Enable the extension in gnome-tweaks 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 + +* Draw arrows: + + Intersect two lines and curve the second thanks to the `Ctrl` key. + + ![How to draw an arrow](https://framagit.org/abakkk/DrawOnYourScreen/uploads/af8f96d33cfeff49bb922a1ef9f4a4ce/arrow-screencast.webm) + +* Screenshot Tool extension: + + [Screenshot Tool](https://extensions.gnome.org/extension/1112/screenshot-tool/) is a convenient extension to “create, copy, store and upload screenshots”. To use it while drawing mode is active, toggle the area selection mode thanks to the Screenshot Tool shortcut (`Super + F11` by default, see its preferences) and **hold** the `space` key when selecting the area with pointer to avoid drawing. + diff --git a/data/default.css b/data/default.css new file mode 100644 index 0000000..b5ab3ef --- /dev/null +++ b/data/default.css @@ -0,0 +1,54 @@ +/* + * Except for the font, you don't need to restart the extension. + * Just save this file as ~/.local/share/drawOnYourScreen/user.css and the changes will be applied for your next brushstroke. + * + * ~/.local/share/drawOnYourScreen/user.css file is automatically generated by activating "Edit style". + * Delete ~/.local/share/drawOnYourScreen/user.css file to retrieve default drawing style. + * + * line-join (no string): + * 0 : miter, 1 : round, 2 : bevel + * line-cap (no string): + * 0 : butt, 1 : round, 2 : square + * + * dash: + * dash-array-on is the length of dashes (no dashes if 0, you can put 0.1 to get dots or square according to line-cap). + * dash-array-off is the length of gaps (no dashes if 0). + * + * square area: + * Drawing in a square area is convenient when using the extension as a vector graphics editor. By default, + * when toggling 'Square drawing area', the area is sized to 75% of monitor size. You can fix and customize this size + * by uncommenting square-area-width and square-area-height lines. + * + * font: + * Only one family : no comma separated list of families like "font1, font2, ..., Sans-Serif". + * Font family can be any font installed, or a generic family name (Serif, Sans-Serif, Monospace, Cursive, Fantasy). + * Font weight and font style : no upper case when string. + * Weight <= 500 (or lighter, normal, medium) is rendered as normal. + * Weight > 500 (or bolder, bold) is rendered as bold. + * Oblique and italic style supports depend on the font family and seem to be rendered identically. + * + */ + +.draw-on-your-screen { + -drawing-line-width: 5px; + -drawing-line-join: 1; + -drawing-line-cap: 1; + -drawing-dash-array-on: 5px; + -drawing-dash-array-off: 15px; + -drawing-dash-offset: 0px; + -drawing-color1: HotPink; + -drawing-color2: Cyan; + -drawing-color3: yellow; + -drawing-color4: Orangered; + -drawing-color5: Chartreuse; + -drawing-color6: DarkViolet; + -drawing-color7: #ffffff; + -drawing-color8: rgba(130, 130, 130, 0.3); + -drawing-color9: rgb(0, 0, 0); + -drawing-background-color: #2e3436; + /*-drawing-square-area-width: 512px;*/ + /*-drawing-square-area-height: 512px;*/ + font-family: Cantarell; + font-weight: normal; + font-style: normal; +} diff --git a/icons/dashed-line-symbolic.svg b/data/icons/dashed-line-symbolic.svg similarity index 100% rename from icons/dashed-line-symbolic.svg rename to data/icons/dashed-line-symbolic.svg diff --git a/icons/fill-symbolic.svg b/data/icons/fill-symbolic.svg similarity index 100% rename from icons/fill-symbolic.svg rename to data/icons/fill-symbolic.svg diff --git a/icons/full-line-symbolic.svg b/data/icons/full-line-symbolic.svg similarity index 100% rename from icons/full-line-symbolic.svg rename to data/icons/full-line-symbolic.svg diff --git a/icons/linecap-symbolic.svg b/data/icons/linecap-symbolic.svg similarity index 100% rename from icons/linecap-symbolic.svg rename to data/icons/linecap-symbolic.svg diff --git a/icons/linejoin-symbolic.svg b/data/icons/linejoin-symbolic.svg similarity index 100% rename from icons/linejoin-symbolic.svg rename to data/icons/linejoin-symbolic.svg diff --git a/icons/stroke-symbolic.svg b/data/icons/stroke-symbolic.svg similarity index 100% rename from icons/stroke-symbolic.svg rename to data/icons/stroke-symbolic.svg diff --git a/draw.js b/draw.js index 20b8b05..ae75252 100644 --- a/draw.js +++ b/draw.js @@ -3,7 +3,7 @@ /* * Copyright 2019 Abakkk * - * This file is part of DrowOnYourScreen, a drawing extension for GNOME Shell. + * 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 @@ -20,6 +20,7 @@ * along with this program. If not, see . */ +const ByteArray = imports.byteArray; const Cairo = imports.cairo; const Clutter = imports.gi.Clutter; const Gio = imports.gi.Gio; @@ -40,29 +41,66 @@ const Screenshot = imports.ui.screenshot; const Tweener = imports.ui.tweener; const ExtensionUtils = imports.misc.extensionUtils; -const Extension = ExtensionUtils.getCurrentExtension(); -const Convenience = Extension.imports.convenience; -const ExtensionJs = Extension.imports.extension; -const Prefs = Extension.imports.prefs; -const _ = imports.gettext.domain(Extension.metadata["gettext-domain"]).gettext; +const Me = ExtensionUtils.getCurrentExtension(); +const Convenience = ExtensionUtils.getSettings ? ExtensionUtils : Me.imports.convenience; +const Extension = Me.imports.extension; +const Prefs = Me.imports.prefs; +const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; const GS_VERSION = Config.PACKAGE_VERSION; -const FILL_ICON_PATH = Extension.dir.get_child('icons').get_child('fill-symbolic.svg').get_path(); -const STROKE_ICON_PATH = Extension.dir.get_child('icons').get_child('stroke-symbolic.svg').get_path(); -const LINEJOIN_ICON_PATH = Extension.dir.get_child('icons').get_child('linejoin-symbolic.svg').get_path(); -const LINECAP_ICON_PATH = Extension.dir.get_child('icons').get_child('linecap-symbolic.svg').get_path(); -const DASHED_LINE_ICON_PATH = Extension.dir.get_child('icons').get_child('dashed-line-symbolic.svg').get_path(); -const FULL_LINE_ICON_PATH = Extension.dir.get_child('icons').get_child('full-line-symbolic.svg').get_path(); +const FILL_ICON_PATH = Me.dir.get_child('data').get_child('icons').get_child('fill-symbolic.svg').get_path(); +const STROKE_ICON_PATH = Me.dir.get_child('data').get_child('icons').get_child('stroke-symbolic.svg').get_path(); +const LINEJOIN_ICON_PATH = Me.dir.get_child('data').get_child('icons').get_child('linejoin-symbolic.svg').get_path(); +const LINECAP_ICON_PATH = Me.dir.get_child('data').get_child('icons').get_child('linecap-symbolic.svg').get_path(); +const DASHED_LINE_ICON_PATH = Me.dir.get_child('data').get_child('icons').get_child('dashed-line-symbolic.svg').get_path(); +const FULL_LINE_ICON_PATH = Me.dir.get_child('data').get_child('icons').get_child('full-line-symbolic.svg').get_path(); var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4 }; -var TextState = { DRAWING: 0, WRITING: 1 }; -var ShapeNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text" }; -var LineCapNames = { 0: 'Butt', 1: 'Round', 2: 'Square' }; -var LineJoinNames = { 0: 'Miter', 1: 'Round', 2: 'Bevel' }; -var FontWeightNames = { 0: 'Normal', 1: 'Bold' }; -var FontStyleNames = { 0: 'Normal', 1: 'Italic', 2: 'Oblique' }; -var FontFamilyNames = { 0: 'Default', 1: 'Sans-Serif', 2: 'Serif', 3: 'Monospace', 4: 'Cursive', 5: 'Fantasy' }; +const TextState = { DRAWING: 0, WRITING: 1 }; +const ShapeNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text" }; +const LineCapNames = { 0: 'Butt', 1: 'Round', 2: 'Square' }; +const LineJoinNames = { 0: 'Miter', 1: 'Round', 2: 'Bevel' }; +const FontWeightNames = { 0: 'Normal', 1: 'Bold' }; +const FontStyleNames = { 0: 'Normal', 1: 'Italic', 2: 'Oblique' }; +const FontFamilyNames = { 0: 'Default', 1: 'Sans-Serif', 2: 'Serif', 3: 'Monospace', 4: 'Cursive', 5: 'Fantasy' }; + +const getDateString = function() { + let date = GLib.DateTime.new_now_local(); + return `${date.format("%F")} ${date.format("%X")}`; +}; + +const getJsonFiles = function() { + let directory = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir']])); + + 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 jsonFiles = []; + 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); + jsonFiles.push({ 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'), + delete: () => file.delete(null) }); + } + fileInfo = enumerator.next_file(null); + } + enumerator.close(null); + + jsonFiles.sort((a, b) => { + return b.modificationUnixTime - a.modificationUnixTime; + }); + + return jsonFiles; +}; // DrawingArea is the widget in which we draw, thanks to Cairo. // It creates and manages a DrawingElement for each "brushstroke". @@ -70,13 +108,11 @@ var FontFamilyNames = { 0: 'Default', 1: 'Sans-Serif', 2: 'Serif', 3: 'Monospac var DrawingArea = new Lang.Class({ Name: 'DrawOnYourScreenDrawingArea', Extends: St.DrawingArea, - Signals: { 'show-osd': { param_types: [GObject.TYPE_STRING, GObject.TYPE_DOUBLE] }, + Signals: { 'show-osd': { param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE] }, 'stop-drawing': {} }, - _init: function(params, monitor, helper, loadJson) { - this.parent({ style_class: 'draw-on-your-screen', name: params && params.name ? params.name : ""}); - - this.connect('repaint', this._repaint.bind(this)); + _init: function(params, monitor, helper, loadPersistent) { + this.parent({ style_class: 'draw-on-your-screen', name: params.name}); this.settings = Convenience.getSettings(); this.monitor = monitor; @@ -93,13 +129,13 @@ var DrawingArea = new Lang.Class({ this.fill = false; this.colors = [Clutter.Color.new(0, 0, 0, 255)]; - if (loadJson) - this._loadJson(); + if (loadPersistent) + this._loadPersistent(); }, get menu() { if (!this._menu) - this._menu = new DrawingMenu(this); + this._menu = new DrawingMenu(this, this.monitor); return this._menu; }, @@ -124,6 +160,8 @@ var DrawingArea = new Lang.Class({ this.fontFamily = font.get_family(); this.currentFontWeight = font.get_weight(); this.currentFontStyle = font.get_style(); + this.squareAreaWidth = themeNode.get_length('-drawing-square-area-width'); + this.squareAreaHeight = themeNode.get_length('-drawing-square-area-height'); } catch(e) { logError(e); } @@ -142,8 +180,8 @@ var DrawingArea = new Lang.Class({ this.currentFontStyle = this.currentFontStyle == 2 ? 1 : ( this.currentFontStyle == 1 ? 2 : 0); }, - _repaint: function(area) { - let cr = area.get_context(); + vfunc_repaint: function() { + let cr = this.get_context(); for (let i = 0; i < this.elements.length; i++) { let isStraightLine = this.elements[i].shape == Shapes.LINE && @@ -173,6 +211,9 @@ var DrawingArea = new Lang.Class({ }, _onButtonPressed: function(actor, event) { + if (this.spaceKeyPressed) + return Clutter.EVENT_PROPAGATE; + let button = event.get_button(); let [x, y] = event.get_coords(); let shiftPressed = event.has_shift_modifier(); @@ -210,6 +251,20 @@ var DrawingArea = new Lang.Class({ return Clutter.EVENT_STOP; }, + _onStageKeyPressed: function(actor, event) { + if (event.get_key_symbol() == Clutter.KEY_space) + this.spaceKeyPressed = true; + + return Clutter.EVENT_PROPAGATE; + }, + + _onStageKeyReleased: function(actor, event) { + if (event.get_key_symbol() == Clutter.KEY_space) + this.spaceKeyPressed = false; + + return Clutter.EVENT_PROPAGATE; + }, + _onKeyPressed: function(actor, event) { if (event.get_key_symbol() == Clutter.Escape) { this.emit('stop-drawing'); @@ -218,12 +273,12 @@ var DrawingArea = new Lang.Class({ } else if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.WRITING) { if (event.get_key_symbol() == Clutter.KEY_BackSpace) { this.currentElement.text = this.currentElement.text.slice(0, -1); - this._updateCursorTimeout(); + this._updateTextCursorTimeout(); } else if (event.has_control_modifier() && event.get_key_symbol() == 118) { // Ctrl + V St.Clipboard.get_default().get_text(St.ClipboardType.CLIPBOARD, (clipBoard, clipText) => { this.currentElement.text += clipText; - this._updateCursorTimeout(); + this._updateTextCursorTimeout(); this._redisplay(); }); return Clutter.EVENT_STOP; @@ -237,7 +292,7 @@ var DrawingArea = new Lang.Class({ } else { let unicode = event.get_key_unicode(); this.currentElement.text += unicode; - this._updateCursorTimeout(); + this._updateTextCursorTimeout(); } this._redisplay(); return Clutter.EVENT_STOP; @@ -270,8 +325,6 @@ var DrawingArea = new Lang.Class({ this._stopDrawing(); }); - this.smoothedStroke = this.settings.get_boolean('smoothed-stroke'); - this.currentElement = new DrawingElement ({ shape: this.currentShape, color: this.currentColor.to_string(), @@ -294,6 +347,9 @@ var DrawingArea = new Lang.Class({ } this.motionHandler = this.connect('motion-event', (actor, event) => { + if (this.spaceKeyPressed) + return; + let coords = event.get_coords(); let [s, x, y] = this.transform_stage_point(coords[0], coords[1]); if (!s) @@ -322,8 +378,8 @@ var DrawingArea = new Lang.Class({ if (this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.DRAWING) { this.currentElement.state = TextState.WRITING; this.currentElement.text = ''; - this.emit('show-osd', _("Type your text\nand press Enter"), -1); - this._updateCursorTimeout(); + this.emit('show-osd', null, _("Type your text\nand press Enter"), -1); + this._updateTextCursorTimeout(); this.textHasCursor = true; this._redisplay(); this.updatePointerCursor(); @@ -342,7 +398,7 @@ var DrawingArea = new Lang.Class({ if (!this.currentElement) return; if (this.currentElement.shape == Shapes.NONE) - this.currentElement.addPoint(x, y, this.smoothedStroke); + this.currentElement.addPoint(x, y, controlPressed); else if ((this.currentElement.shape == Shapes.RECTANGLE || this.currentElement.shape == Shapes.TEXT) && (controlPressed || this.currentElement.transform.active)) this.currentElement.transformRectangle(x, y); else if (this.currentElement.shape == Shapes.ELLIPSE && (controlPressed || this.currentElement.transform.active)) @@ -360,14 +416,14 @@ var DrawingArea = new Lang.Class({ if (this.currentElement.text.length > 0) this.elements.push(this.currentElement); this.currentElement = null; - this._stopCursorTimeout(); + this._stopTextCursorTimeout(); this._redisplay(); }, setPointerCursor: function(pointerCursorName) { if (!this.currentPointerCursorName || this.currentPointerCursorName != pointerCursorName) { this.currentPointerCursorName = pointerCursorName; - ExtensionJs.setCursor(pointerCursorName); + Extension.setCursor(pointerCursorName); } }, @@ -378,20 +434,20 @@ var DrawingArea = new Lang.Class({ this.setPointerCursor('MOVE_OR_RESIZE_WINDOW'); }, - _stopCursorTimeout: function() { - if (this.cursorTimeoutId) { - Mainloop.source_remove(this.cursorTimeoutId); - this.cursorTimeoutId = null; + _stopTextCursorTimeout: function() { + if (this.textCursorTimeoutId) { + Mainloop.source_remove(this.textCursorTimeoutId); + this.textCursorTimeoutId = null; } this.textHasCursor = false; }, - _updateCursorTimeout: function() { - this._stopCursorTimeout(); - this.cursorTimeoutId = Mainloop.timeout_add(600, () => { + _updateTextCursorTimeout: function() { + this._stopTextCursorTimeout(); + this.textCursorTimeoutId = Mainloop.timeout_add(600, () => { this.textHasCursor = !this.textHasCursor; this._redisplay(); - return true; + return GLib.SOURCE_CONTINUE; }); }, @@ -413,7 +469,7 @@ var DrawingArea = new Lang.Class({ this.buttonReleasedHandler = null; } this.currentElement = null; - this._stopCursorTimeout(); + this._stopTextCursorTimeout(); } else { this.elements.pop(); } @@ -447,9 +503,10 @@ var DrawingArea = new Lang.Class({ toggleSquareArea: function() { this.isSquareArea = !this.isSquareArea; if (this.isSquareArea) { - let squareWidth = Math.min(this.monitor.width, this.monitor.height) * 3 / 4; - this.set_position(Math.floor(this.monitor.width / 2 - squareWidth / 2), Math.floor(this.monitor.height / 2 - squareWidth / 2)); - this.set_size(squareWidth, squareWidth); + 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.add_style_class_name('draw-on-your-screen-square-area'); } else { this.set_position(0, 0); @@ -468,38 +525,38 @@ var DrawingArea = new Lang.Class({ this.currentElement.color = this.currentColor.to_string(); this._redisplay(); } - this.emit('show-osd', `${this.currentColor.to_string()}`, -1); + this.emit('show-osd', null, `${this.currentColor.to_string()}`, -1); }, selectShape: function(shape) { this.currentShape = shape; - this.emit('show-osd', _(ShapeNames[shape]), -1); + this.emit('show-osd', null, _(ShapeNames[shape]), -1); this.updatePointerCursor(); }, toggleFill: function() { this.fill = !this.fill; - this.emit('show-osd', this.fill ? _("Fill") : _("Stroke"), -1); + this.emit('show-osd', null, this.fill ? _("Fill") : _("Stroke"), -1); }, toggleDash: function() { this.dashedLine = !this.dashedLine; - this.emit('show-osd', this.dashedLine ? _("Dashed line") : _("Full line"), -1); + this.emit('show-osd', null, this.dashedLine ? _("Dashed line") : _("Full line"), -1); }, incrementLineWidth: function(increment) { this.currentLineWidth = Math.max(this.currentLineWidth + increment, 0); - this.emit('show-osd', this.currentLineWidth + " " + _("px"), 2 * this.currentLineWidth); + this.emit('show-osd', null, this.currentLineWidth + " " + _("px"), 2 * this.currentLineWidth); }, toggleLineJoin: function() { this.currentLineJoin = this.currentLineJoin == 2 ? 0 : this.currentLineJoin + 1; - this.emit('show-osd', _(LineJoinNames[this.currentLineJoin]), -1); + this.emit('show-osd', null, _(LineJoinNames[this.currentLineJoin]), -1); }, toggleLineCap: function() { this.currentLineCap = this.currentLineCap == 2 ? 0 : this.currentLineCap + 1; - this.emit('show-osd', _(LineCapNames[this.currentLineCap]), -1); + this.emit('show-osd', null, _(LineCapNames[this.currentLineCap]), -1); }, toggleFontWeight: function() { @@ -508,7 +565,7 @@ var DrawingArea = new Lang.Class({ this.currentElement.font.weight = this.currentFontWeight; this._redisplay(); } - this.emit('show-osd', `${_(FontWeightNames[this.currentFontWeight])}`, -1); + this.emit('show-osd', null, `${_(FontWeightNames[this.currentFontWeight])}`, -1); }, toggleFontStyle: function() { @@ -517,7 +574,7 @@ var DrawingArea = new Lang.Class({ this.currentElement.font.style = this.currentFontStyle; this._redisplay(); } - this.emit('show-osd', `${_(FontStyleNames[this.currentFontStyle])}`, -1); + this.emit('show-osd', null, `${_(FontStyleNames[this.currentFontStyle])}`, -1); }, toggleFontFamily: function() { @@ -527,7 +584,7 @@ var DrawingArea = new Lang.Class({ this.currentElement.font.family = currentFontFamily; this._redisplay(); } - this.emit('show-osd', `${_(currentFontFamily)}`, -1); + this.emit('show-osd', null, `${_(currentFontFamily)}`, -1); }, toggleHelp: function() { @@ -538,7 +595,9 @@ var DrawingArea = new Lang.Class({ }, enterDrawingMode: function() { - this.keyPressedHandler = this.connect('key-press-event', this._onKeyPressed.bind(this)); + this.stageKeyPressedHandler = global.stage.connect('key-press-event', this._onStageKeyPressed.bind(this)); + this.stageKeyReleasedHandler = global.stage.connect('key-release-event', this._onStageKeyReleased.bind(this)); + this.keyPressedHandler = this.connect('key-press-event', this._onKeyPressed.bind(this)); this.buttonPressedHandler = this.connect('button-press-event', this._onButtonPressed.bind(this)); this._onKeyboardPopupMenuHandler = this.connect('popup-menu', this._onKeyboardPopupMenu.bind(this)); this.scrollHandler = this.connect('scroll-event', this._onScroll.bind(this)); @@ -547,6 +606,14 @@ var DrawingArea = new Lang.Class({ }, leaveDrawingMode: function(save) { + if (this.stageKeyPressedHandler) { + global.stage.disconnect(this.stageKeyPressedHandler); + this.stageKeyPressedHandler = null; + } + if (this.stageKeyReleasedHandler) { + global.stage.disconnect(this.stageKeyReleasedHandler); + this.stageKeyReleasedHandler = null; + } if (this.keyPressedHandler) { this.disconnect(this.keyPressedHandler); this.keyPressedHandler = null; @@ -576,15 +643,16 @@ var DrawingArea = new Lang.Class({ this.helper.hideHelp(); this.currentElement = null; - this._stopCursorTimeout(); + this._stopTextCursorTimeout(); this.currentShape = Shapes.NONE; this.dashedLine = false; this.fill = false; this._redisplay(); - this.menu.close(); + if (this._menu) + this._menu.close(); this.get_parent().set_background_color(null); if (save) - this.saveAsJson(); + this.savePersistent(); }, saveAsSvg: function() { @@ -605,8 +673,7 @@ var DrawingArea = new Lang.Class({ } content += "\n"; - let date = GLib.DateTime.new_now_local(); - let filename = `DrawOnYourScreen ${date.format("%F")} ${date.format("%X")}.svg`; + 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]); if (GLib.file_test(path, GLib.FileTest.EXISTS)) @@ -626,34 +693,73 @@ var DrawingArea = new Lang.Class({ } }, - saveAsJson: function() { - let filename = `DrawOnYourScreen.json`; - let dir = GLib.get_user_data_dir(); - let path = GLib.build_filenamev([dir, filename]); - - let oldContents; - if (GLib.file_test(path, GLib.FileTest.EXISTS)) { - oldContents = GLib.file_get_contents(path)[1]; - if (oldContents instanceof Uint8Array) - oldContents = imports.byteArray.toString(oldContents); + _saveAsJson: function(name, notify) { + // stop drawing or writing + if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.WRITING) { + this._stopWriting(); + } else if (this.currentElement && this.currentElement.shape != Shapes.TEXT) { + this._stopDrawing(); } - // do not create a file to write just an empty array - if (!oldContents && this.elements.length == 0) - return; + let dir = GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir']]); + if (!GLib.file_test(dir, GLib.FileTest.EXISTS)) + GLib.mkdir_with_parents(dir, 0o700); + let path = GLib.build_filenamev([dir, `${name}.json`]); + + let oldContents; + + if (name == Me.metadata['persistent-file-name']) { + if (GLib.file_test(path, GLib.FileTest.EXISTS)) { + oldContents = GLib.file_get_contents(path)[1]; + if (oldContents instanceof Uint8Array) + oldContents = ByteArray.toString(oldContents); + } + + // do not create a file to write just an empty array + if (!oldContents && this.elements.length == 0) + return; + } // do not use "content = JSON.stringify(this.elements, null, 2);", neither "content = JSON.stringify(this.elements);" // because of compromise between disk usage and human readability let contents = `[\n ` + new Array(...this.elements.map(element => JSON.stringify(element))).join(`,\n\n `) + `\n]`; - if (contents != oldContents) - GLib.file_set_contents(path, contents); + if (name == Me.metadata['persistent-file-name'] && contents == oldContents) + return; + + GLib.file_set_contents(path, contents); + if (notify) + this.emit('show-osd', 'document-save-symbolic', name, -1); + if (name != Me.metadata['persistent-file-name']) { + this.jsonName = name; + this.lastJsonContents = contents; + } }, - _loadJson: function() { - let filename = `DrawOnYourScreen.json`; + saveAsJsonWithName: function(name) { + this._saveAsJson(name); + }, + + saveAsJson: function() { + this._saveAsJson(getDateString(), true); + }, + + savePersistent: function() { + this._saveAsJson(Me.metadata['persistent-file-name']); + }, + + syncPersistent: function() { + // do not override peristent.json with an empty drawing when changing persistency setting + if (!this.elements.length) + this._loadPersistent(); + else + this.savePersistent(); + + }, + + _loadJson: function(name, notify) { let dir = GLib.get_user_data_dir(); - let path = GLib.build_filenamev([dir, filename]); + let path = GLib.build_filenamev([dir, Me.metadata['data-dir'], `${name}.json`]); if (!GLib.file_test(path, GLib.FileTest.EXISTS)) return; @@ -661,8 +767,52 @@ var DrawingArea = new Lang.Class({ if (!success) return; if (contents instanceof Uint8Array) - contents = imports.byteArray.toString(contents); + contents = ByteArray.toString(contents); this.elements.push(...JSON.parse(contents).map(object => new DrawingElement(object))); + + if (notify) + this.emit('show-osd', 'document-open-symbolic', name, -1); + if (name != Me.metadata['persistent-file-name']) { + this.jsonName = name; + this.lastJsonContents = contents; + } + }, + + _loadPersistent: function() { + this._loadJson(Me.metadata['persistent-file-name']); + }, + + loadJson: function(name, notify) { + this.elements = []; + this.currentElement = null; + this._stopTextCursorTimeout(); + this._loadJson(name, notify); + this._redisplay(); + }, + + loadNextJson: function() { + let names = getJsonFiles().map(file => file.name); + + if (!names.length) + return; + + let nextName = names[this.jsonName && names.indexOf(this.jsonName) != names.length - 1 ? names.indexOf(this.jsonName) + 1 : 0]; + this.loadJson(nextName, true); + }, + + loadPreviousJson: function() { + let names = getJsonFiles().map(file => file.name); + + if (!names.length) + return; + + let previousName = names[this.jsonName && names.indexOf(this.jsonName) > 0 ? names.indexOf(this.jsonName) - 1 : names.length - 1]; + this.loadJson(previousName, true); + }, + + get drawingContentsHasChanged() { + let contents = `[\n ` + new Array(...this.elements.map(element => JSON.stringify(element))).join(`,\n\n `) + `\n]`; + return contents != this.lastJsonContents; }, disable: function() { @@ -674,7 +824,7 @@ var DrawingArea = new Lang.Class({ // DrawingElement represents a "brushstroke". // It can be converted into a cairo path as well as a svg element. // See DrawingArea._startDrawing() to know its params. -var DrawingElement = new Lang.Class({ +const DrawingElement = new Lang.Class({ Name: 'DrawOnYourScreenDrawingElement', _init: function(params) { @@ -893,7 +1043,7 @@ var DrawingElement = new Lang.Class({ }, }); -function getAngle(xO, yO, xA, yA, xB, yB) { +const getAngle = function(xO, yO, xA, yA, xB, yB) { // calculate angle of rotation in absolute value // cos(AOB) = (OA.OB)/(||OA||*||OB||) where OA.OB = (xA-xO)*(xB-xO) + (yA-yO)*(yB-yO) let angle = Math.acos( ((xA - xO)*(xB - xO) + (yA - yO)*(yB - yO)) / (Math.hypot(xA - xO, yA - yO) * Math.hypot(xB - xO, yB - yO)) ); @@ -907,11 +1057,11 @@ function getAngle(xO, yO, xA, yA, xB, yB) { if (xA < xO) angle = - angle; return angle; -} +}; -var HELPER_ANIMATION_TIME = 0.25; -var MEDIA_KEYS_SCHEMA = 'org.gnome.settings-daemon.plugins.media-keys'; -var MEDIA_KEYS_KEYS = { +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", @@ -952,7 +1102,7 @@ var DrawingHelper = new Lang.Class({ for (let i = 0; i < Prefs.OTHER_SHORTCUTS.length; i++) { if (Prefs.OTHER_SHORTCUTS[i].desc.indexOf('-separator-') != -1) { - this.vbox.add(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-separator' })); + this.vbox.add(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' })); continue; } let hbox = new St.BoxLayout({ vertical: false }); @@ -961,11 +1111,11 @@ var DrawingHelper = new Lang.Class({ this.vbox.add(hbox); } - this.vbox.add(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-separator' })); + this.vbox.add(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) { - this.vbox.add(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-separator' })); + this.vbox.add(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' })); continue; } let hbox = new St.BoxLayout({ vertical: false }); @@ -997,7 +1147,7 @@ var DrawingHelper = new Lang.Class({ this.opacity = 0; this.show(); - let maxHeight = this.monitor.height*(3/4); + let maxHeight = this.monitor.height * 3 / 4; this.set_height(Math.min(this.height, maxHeight)); this.set_position(Math.floor(this.monitor.width / 2 - this.width / 2), Math.floor(this.monitor.height / 2 - this.height / 2)); @@ -1024,10 +1174,14 @@ var DrawingHelper = new Lang.Class({ }, }); -var DrawingMenu = new Lang.Class({ +const getActor = function(object) { + return GS_VERSION < '3.33.0' ? object.actor : object; +}; + +const DrawingMenu = new Lang.Class({ Name: 'DrawOnYourScreenDrawingMenu', - _init: function(area) { + _init: function(area, monitor) { this.area = area; 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); @@ -1036,12 +1190,25 @@ var DrawingMenu = new Lang.Class({ Main.layoutManager.uiGroup.add_actor(this.menu.actor); this.menu.actor.add_style_class_name('background-menu draw-on-your-screen-menu'); + this.menu.actor.set_style('max-height:' + monitor.height + 'px;'); this.menu.actor.hide(); // do not close the menu on item activated this.menu.itemActivated = () => {}; this.menu.connect('open-state-changed', this._onMenuOpenStateChanged.bind(this)); + // Case where the menu is closed (escape key) while the save entry clutter_text is active: + // St.Entry clutter_text set the DEFAULT cursor on leave event with a delay and + // overrides the cursor set by area.updatePointerCursor(). + // In order to update drawing cursor on menu closed, we need to leave the saveEntry before closing menu. + // Since escape key press event can't be captured easily, the job is done in the menu close function. + let menuCloseFunc = this.menu.close; + this.menu.close = (animate) => { + if (this.saveDrawingSubMenu && this.saveDrawingSubMenu.isOpen) + this.saveDrawingSubMenu.close(); + menuCloseFunc.bind(this.menu)(animate); + }; + 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.linejoinIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(LINEJOIN_ICON_PATH) }); @@ -1062,7 +1229,8 @@ var DrawingMenu = new Lang.Class({ } else { this.area.updatePointerCursor(); // actionMode has changed, set previous actionMode in order to keep internal shortcuts working - Main.actionMode = ExtensionJs.DRAWING_ACTION_MODE | Shell.ActionMode.NORMAL; + Main.actionMode = Extension.DRAWING_ACTION_MODE | Shell.ActionMode.NORMAL; + this.area.grab_key_focus(); } }, @@ -1127,15 +1295,19 @@ var DrawingMenu = new Lang.Class({ this.menu.addMenuItem(fontSection); this.fontSection = fontSection; - let manager = ExtensionJs.manager; + let manager = Extension.manager; this._addSwitchItemWithCallback(this.menu, _("Hide panel and dock"), manager.hiddenList ? true : false, manager.togglePanelAndDockOpacity.bind(manager)); this._addSwitchItemWithCallback(this.menu, _("Add a drawing background"), this.area.hasBackground, this.area.toggleBackground.bind(this.area)); this._addSwitchItemWithCallback(this.menu, _("Square drawing area"), this.area.isSquareArea, this.area.toggleSquareArea.bind(this.area)); this._addSeparator(this.menu); - this.menu.addAction(_("Save drawing as a SVG file"), this.area.saveAsSvg.bind(this.area), 'document-save-symbolic'); - this.menu.addAction(_("Open stylesheet.css"), manager.openStylesheetFile.bind(manager), 'document-open-symbolic'); - this.menu.addAction(_("Show help"), this.area.toggleHelp.bind(this.area), 'preferences-desktop-keyboard-shortcuts-symbolic'); + this._addDrawingNameItem(this.menu); + this._addOpenDrawingSubMenuItem(this.menu); + this._addSaveDrawingSubMenuItem(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'); this.updateSectionVisibility(); }, @@ -1154,10 +1326,9 @@ var DrawingMenu = new Lang.Class({ _addSwitchItem: function(menu, label, iconFalse, iconTrue, target, targetProperty) { let item = new PopupMenu.PopupSwitchMenuItem(label, target[targetProperty]); - let itemActor = GS_VERSION < '3.33.0' ? item.actor : item; item.icon = new St.Icon({ style_class: 'popup-menu-icon' }); - itemActor.insert_child_at_index(item.icon, 1); + getActor(item).insert_child_at_index(item.icon, 1); item.icon.set_gicon(target[targetProperty] ? iconTrue : iconFalse); item.connect('toggled', (item, state) => { @@ -1176,34 +1347,32 @@ var DrawingMenu = new Lang.Class({ _addSliderItem: function(menu, target, targetProperty) { let item = new PopupMenu.PopupBaseMenuItem({ activate: false }); - let itemActor = GS_VERSION < '3.33.0' ? item.actor : item; let label = new St.Label({ text: target[targetProperty] + " " + _("px"), style_class: 'draw-on-your-screen-menu-slider-label' }); let slider = new Slider.Slider(target[targetProperty] / 50); - let sliderActor = GS_VERSION < '3.33.0' ? slider.actor : slider; 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"); if (target[targetProperty] === 0) - label.add_style_class_name(ExtensionJs.WARNING_COLOR_STYLE_CLASS_NAME); + label.add_style_class_name(Extension.WARNING_COLOR_STYLE_CLASS_NAME); else - label.remove_style_class_name(ExtensionJs.WARNING_COLOR_STYLE_CLASS_NAME); + label.remove_style_class_name(Extension.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"); if (target[targetProperty] === 0) - label.add_style_class_name(ExtensionJs.WARNING_COLOR_STYLE_CLASS_NAME); + label.add_style_class_name(Extension.WARNING_COLOR_STYLE_CLASS_NAME); else - label.remove_style_class_name(ExtensionJs.WARNING_COLOR_STYLE_CLASS_NAME); + label.remove_style_class_name(Extension.WARNING_COLOR_STYLE_CLASS_NAME); }); } - itemActor.add(sliderActor, { expand: true }); - itemActor.add(label); - itemActor.connect('key-press-event', slider.onKeyPressEvent.bind(slider)); + getActor(item).add(getActor(slider), { expand: true }); + getActor(item).add(label); + getActor(item).connect('key-press-event', slider.onKeyPressEvent.bind(slider)); menu.addMenuItem(item); }, @@ -1269,11 +1438,200 @@ var DrawingMenu = new Lang.Class({ menu.addMenuItem(item); }, + _addDrawingNameItem: function(menu) { + this.drawingNameMenuItem = new PopupMenu.PopupMenuItem('', { reactive: false, activate: false }); + this.drawingNameMenuItem.setSensitive(false); + menu.addMenuItem(this.drawingNameMenuItem); + this._updateDrawingNameMenuItem(); + }, + + _updateDrawingNameMenuItem: function() { + getActor(this.drawingNameMenuItem).visible = this.area.jsonName ? true : false; + if (this.area.jsonName) { + let prefix = this.area.drawingContentsHasChanged ? "* " : ""; + this.drawingNameMenuItem.label.set_text(`${prefix}${this.area.jsonName}`); + this.drawingNameMenuItem.label.get_clutter_text().set_use_markup(true); + } + }, + + _addOpenDrawingSubMenuItem: function(menu) { + let item = new PopupMenu.PopupSubMenuMenuItem(_("Open drawing"), true); + this.openDrawingSubMenuItem = item; + this.openDrawingSubMenu = item.menu; + item.icon.set_icon_name('document-open-symbolic'); + + item.menu.itemActivated = () => { + item.menu.close(); + }; + + Mainloop.timeout_add(0, () => { + this._populateOpenDrawingSubMenu(); + // small trick to prevent the menu from "jumping" on first opening + item.menu.open(); + item.menu.close(); + return GLib.SOURCE_REMOVE; + }); + menu.addMenuItem(item); + }, + + _populateOpenDrawingSubMenu: function() { + this.openDrawingSubMenu.removeAll(); + let jsonFiles = getJsonFiles(); + jsonFiles.forEach(file => { + let item = this.openDrawingSubMenu.addAction(`${file.displayName}`, () => { + this.area.loadJson(file.name); + this._updateDrawingNameMenuItem(); + this._updateSaveDrawingSubMenuItemSensitivity(); + }); + item.label.get_clutter_text().set_use_markup(true); + + let expander = new St.Bin({ + style_class: 'popup-menu-item-expander', + x_expand: true, + }); + getActor(item).add_child(expander); + + let deleteButton = new St.Button({ style_class: 'draw-on-your-screen-menu-delete-button', + child: new St.Icon({ icon_name: 'edit-delete-symbolic', + style_class: 'popup-menu-icon', + x_align: Clutter.ActorAlign.END }) }); + getActor(item).add_child(deleteButton); + + deleteButton.connect('clicked', () => { + file.delete(); + this._populateOpenDrawingSubMenu(); + }); + }); + + this.openDrawingSubMenuItem.setSensitive(!this.openDrawingSubMenu.isEmpty()); + }, + + _addSaveDrawingSubMenuItem: function(menu) { + let item = new PopupMenu.PopupSubMenuMenuItem(_("Save drawing"), true); + this.saveDrawingSubMenuItem = item; + this._updateSaveDrawingSubMenuItemSensitivity(); + this.saveDrawingSubMenu = item.menu; + item.icon.set_icon_name('document-save-symbolic'); + + item.menu.itemActivated = () => { + item.menu.close(); + }; + + Mainloop.timeout_add(0, () => { + this._populateSaveDrawingSubMenu(); + // small trick to prevent the menu from "jumping" on first opening + item.menu.open(); + item.menu.close(); + return GLib.SOURCE_REMOVE; + }); + menu.addMenuItem(item); + }, + + _updateSaveDrawingSubMenuItemSensitivity: function() { + this.saveDrawingSubMenuItem.setSensitive(this.area.elements.length > 0); + }, + + _populateSaveDrawingSubMenu: function() { + this.saveEntry = new DrawingMenuEntry({ initialTextGetter: getDateString, + entryActivateCallback: (text) => { + this.area.saveAsJsonWithName(text); + this.saveDrawingSubMenu.toggle(); + this._updateDrawingNameMenuItem(); + this._populateOpenDrawingSubMenu(); + }, + invalidStrings: [Me.metadata['persistent-file-name'], '/'], + primaryIconName: 'insert-text' }); + this.saveDrawingSubMenu.addMenuItem(this.saveEntry.item); + }, + _addSeparator: function(menu) { - let separator = new PopupMenu.PopupSeparatorMenuItem(' '); - let separatorActor = GS_VERSION < '3.33.0' ? separator.actor : separator; - separatorActor.add_style_class_name('draw-on-your-screen-menu-separator'); - menu.addMenuItem(separator); + let separatorItem = new PopupMenu.PopupSeparatorMenuItem(' '); + getActor(separatorItem).add_style_class_name('draw-on-your-screen-menu-separator-item'); + menu.addMenuItem(separatorItem); } }); +// based on searchItem.js, https://github.com/leonardo-bartoli/gnome-shell-extension-Recents +const DrawingMenuEntry = new Lang.Class({ + Name: 'DrawOnYourScreenDrawingMenuEntry', + + _init: function(params) { + this.params = params; + this.item = new PopupMenu.PopupBaseMenuItem({ style_class: 'draw-on-your-screen-menu-entry-item', + activate: false, + reactive: true, + can_focus: false }); + + this.itemActor = GS_VERSION < '3.33.0' ? this.item.actor : this.item; + + this.entry = new St.Entry({ + style_class: 'search-entry draw-on-your-screen-menu-entry', + track_hover: true, + reactive: true, + can_focus: true + }); + + this.entry.set_primary_icon(new St.Icon({ style_class: 'search-entry-icon', + icon_name: this.params.primaryIconName })); + + this.entry.clutter_text.connect('text-changed', this._onTextChanged.bind(this)); + this.entry.clutter_text.connect('activate', this._onTextActivated.bind(this)); + + this.clearIcon = new St.Icon({ + style_class: 'search-entry-icon', + icon_name: 'edit-clear-symbolic' + }); + this.entry.connect('secondary-icon-clicked', this._reset.bind(this)); + + getActor(this.item).add(this.entry, { expand: true }); + getActor(this.item).connect('notify::mapped', (actor) => { + if (actor.mapped) { + this.entry.set_text(this.params.initialTextGetter()); + this.entry.clutter_text.grab_key_focus(); + } + }); + }, + + _setError: function(hasError) { + if (hasError) + this.entry.add_style_class_name('draw-on-your-screen-menu-entry-error'); + else + this.entry.remove_style_class_name('draw-on-your-screen-menu-entry-error'); + }, + + _reset: function() { + this.entry.text = ''; + this.entry.clutter_text.set_cursor_visible(true); + this.entry.clutter_text.set_selection(0, 0); + this._setError(false); + }, + + _onTextActivated: function(clutterText) { + let text = clutterText.get_text(); + if (text.length == 0) + return; + if (this._getIsInvalid()) + return; + this._reset(); + this.params.entryActivateCallback(text); + }, + + _onTextChanged: function(clutterText) { + let text = clutterText.get_text(); + this.entry.set_secondary_icon(text.length ? this.clearIcon : null); + + if (text.length) + this._setError(this._getIsInvalid()); + }, + + _getIsInvalid: function() { + for (let i = 0; i < this.params.invalidStrings.length; i++) { + if (this.entry.text.indexOf(this.params.invalidStrings[i]) != -1) + return true; + } + + return false; + } +}); + + diff --git a/extension.js b/extension.js index 5e712e2..ef5d9ed 100644 --- a/extension.js +++ b/extension.js @@ -3,7 +3,7 @@ /* * Copyright 2019 Abakkk * - * This file is part of DrowOnYourScreen, a drawing extension for GNOME Shell. + * 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 @@ -21,6 +21,7 @@ */ const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; const Lang = imports.lang; const Meta = imports.gi.Meta; const Shell = imports.gi.Shell; @@ -31,10 +32,11 @@ const Main = imports.ui.main; const OsdWindow = imports.ui.osdWindow; const PanelMenu = imports.ui.panelMenu; -const Extension = imports.misc.extensionUtils.getCurrentExtension(); -const Convenience = Extension.imports.convenience; -const Draw = Extension.imports.draw; -const _ = imports.gettext.domain(Extension.metadata["gettext-domain"]).gettext; +const ExtensionUtils = imports.misc.extensionUtils; +const Me = ExtensionUtils.getCurrentExtension(); +const Convenience = ExtensionUtils.getSettings && ExtensionUtils.initTranslations ? ExtensionUtils : Me.imports.convenience; +const Draw = Me.imports.draw; +const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; const GS_VERSION = Config.PACKAGE_VERSION; @@ -92,16 +94,25 @@ var AreaManager = new Lang.Class({ 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)); - if (Extension.stylesheet) { - this.stylesheetMonitor = Extension.stylesheet.monitor(Gio.FileMonitorFlags.NONE, null); - this.stylesheetChangedHandler = this.stylesheetMonitor.connect('changed', (monitor, file, otherFile, eventType) => { - if ((eventType != 0 && eventType != 3) || !Extension.stylesheet.query_exists(null)) - return; - let theme = St.ThemeContext.get_for_stage(global.stage).get_theme(); - theme.unload_stylesheet(Extension.stylesheet); - theme.load_stylesheet(Extension.stylesheet); - }); + 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); + }); }, onDesktopSettingChanged: function() { @@ -113,7 +124,7 @@ var AreaManager = new Lang.Class({ onPersistentSettingChanged: function() { if (this.settings.get_boolean('persistent-drawing')) - this.areas[Main.layoutManager.primaryIndex].saveAsJson(); + this.areas[Main.layoutManager.primaryIndex].syncPersistent(); }, updateIndicator: function() { @@ -136,8 +147,8 @@ var AreaManager = new Lang.Class({ let monitor = this.monitors[i]; let container = new St.Widget({ name: 'drawOnYourSreenContainer' + i }); let helper = new Draw.DrawingHelper({ name: 'drawOnYourSreenHelper' + i }, monitor); - let load = i == Main.layoutManager.primaryIndex && this.settings.get_boolean('persistent-drawing'); - let area = new Draw.DrawingArea({ name: 'drawOnYourSreenArea' + i }, monitor, helper, load); + let loadPersistent = i == Main.layoutManager.primaryIndex && this.settings.get_boolean('persistent-drawing'); + let area = new Draw.DrawingArea({ name: 'drawOnYourSreenArea' + i }, monitor, helper, loadPersistent); container.add_child(area); container.add_child(helper); @@ -161,6 +172,9 @@ var AreaManager = new Lang.Class({ 'delete-last-element': this.activeArea.deleteLastElement.bind(this.activeArea), 'smooth-last-element': this.activeArea.smoothLastElement.bind(this.activeArea), 'save-as-svg': this.activeArea.saveAsSvg.bind(this.activeArea), + 'save-as-json': this.activeArea.saveAsJson.bind(this.activeArea), + '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-square-area': this.activeArea.toggleSquareArea.bind(this.activeArea), 'increment-line-width': () => this.activeArea.incrementLineWidth(1), @@ -181,7 +195,7 @@ var AreaManager = new Lang.Class({ 'toggle-font-style': this.activeArea.toggleFontStyle.bind(this.activeArea), 'toggle-panel-and-dock-visibility': this.togglePanelAndDockOpacity.bind(this), 'toggle-help': this.activeArea.toggleHelp.bind(this.activeArea), - 'open-stylesheet': this.openStylesheetFile.bind(this) + 'open-user-stylesheet': this.openUserStyleFile.bind(this) }; for (let key in this.internalKeybindings) { @@ -212,9 +226,19 @@ var AreaManager = new Lang.Class({ } }, - openStylesheetFile: function() { - if (Extension.stylesheet && Extension.stylesheet.query_exists(null)) - Gio.AppInfo.launch_default_for_uri(Extension.stylesheet.get_uri(), global.create_app_launch_context(0, -1)); + 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(); }, @@ -223,7 +247,7 @@ var AreaManager = new Lang.Class({ for (let i = 0; i < this.areas.length; i++) this.areas[i].erase(); if (this.settings.get_boolean('persistent-drawing')) - this.areas[Main.layoutManager.primaryIndex].saveAsJson(); + this.areas[Main.layoutManager.primaryIndex].savePersistent(); }, togglePanelAndDockOpacity: function() { @@ -315,11 +339,12 @@ var AreaManager = new Lang.Class({ }, // use level -1 to set no level (null) - showOsd: function(emitter, label, level, maxLevel) { + showOsd: function(emitter, iconName, label, level) { if (this.osdDisabled) return; let activeIndex = this.areas.indexOf(this.activeArea); if (activeIndex != -1) { + let maxLevel; if (level == -1) level = null; else if (level > 100) @@ -329,7 +354,9 @@ var AreaManager = new Lang.Class({ // GS 3.34+ : bar from 0 to 1 if (level && GS_VERSION > '3.33.0') level = level / 100; - Main.osdWindowManager.show(activeIndex, this.enterGicon, label, level, maxLevel); + + let icon = iconName && new Gio.ThemedIcon({ name: iconName }); + Main.osdWindowManager.show(activeIndex, icon || this.enterGicon, label, level, maxLevel); Main.osdWindowManager._osdWindows[activeIndex]._label.get_clutter_text().set_use_markup(true); if (level === 0) { @@ -357,9 +384,13 @@ var AreaManager = new Lang.Class({ }, disable: function() { - if (this.stylesheetChangedHandler) { - this.stylesheetMonitor.disconnect(this.stylesheetChangedHandler); - this.stylesheetChangedHandler = null; + 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); diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index e579156..2df55f0 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Draw On Your Screen VERSION\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-01-03 08:00+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -19,17 +19,17 @@ msgstr "" # Add your name here, for example: # (add "\n" as separator if there is many translators) -# msgid "Translators" +# msgid "translator-credits" # msgstr "Me" # or, with mail: -# msgid "Translators" +# msgid "translator-credits" # msgstr "Me" # or, with page: -# msgid "Translators" +# msgid "translator-credits" # msgstr "Me" # else keep it empty. # It will be displayed in about page -msgid "Translators" +msgid "translator-credits" msgstr "" #: extension.js @@ -108,6 +108,12 @@ msgstr "" msgid "Color" msgstr "" +msgid "Open drawing" +msgstr "" + +msgid "Save drawing" +msgstr "" + #: prefs.js msgid "Preferences" @@ -196,10 +202,20 @@ msgstr "" msgid "Square drawing area" msgstr "" +msgid "Open previous drawing" +msgstr "" + +msgid "Open next drawing" +msgstr "" + +# already in draw.js +#msgid "Save drawing" +#msgstr "" + msgid "Save drawing as a SVG file" msgstr "" -msgid "Open stylesheet.css" +msgid "Edit style" msgstr "" msgid "Show help" @@ -246,6 +262,12 @@ msgstr "" msgid "Shift key held" msgstr "" +msgid "Ignore pointer movement" +msgstr "" + +msgid "Space key held" +msgstr "" + msgid "Leave" msgstr "" @@ -269,18 +291,18 @@ msgstr "" msgid "Global" msgstr "" -msgid "Drawing on the desktop" -msgstr "" - -msgid "Draw On Your Screen becomes Draw On Your Desktop" -msgstr "" - msgid "Persistent" msgstr "" msgid "Persistent drawing through session restart" msgstr "" +msgid "Drawing on the desktop" +msgstr "" + +msgid "Draw On Your Screen becomes Draw On Your Desktop" +msgstr "" + msgid "Disable on-screen notifications" msgstr "" @@ -297,32 +319,24 @@ msgid "" "By pressing Ctrl key during the drawing process, you can:\n" " . rotate a rectangle or a text area\n" " . extend and rotate an ellipse\n" -" . curve a line (cubic Bezier curve)" -msgstr "" - -msgid "Smooth stroke during the drawing process" +" . curve a line (cubic Bezier curve)\n" +" . smooth a free drawing stroke (you may prefer to smooth the stroke afterward, see “%s”)" msgstr "" msgid "" -"You can also smooth the stroke afterward\n" -"See “%s”" -msgstr "" - -msgid "Change the style" -msgstr "" - -msgid "See stylesheet.css" +"Default drawing style attributes (color palette, font, line, dash) are defined in an editable css file.\n" +"See “%s”." msgstr "" msgid "" -"Note: When you save elements made with eraser in a SVG file,\n" +"Note: 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)" +"See “%s” or edit the SVG file afterwards." msgstr "" # The following words refer to SVG attributes. -# You have the choice to translate or not +# You are free to translate them or not. #msgid "Butt" #msgstr "" diff --git a/locale/it_IT/LC_MESSAGES/draw-on-your-screen.mo b/locale/it_IT/LC_MESSAGES/draw-on-your-screen.mo new file mode 100644 index 0000000..feda332 Binary files /dev/null and b/locale/it_IT/LC_MESSAGES/draw-on-your-screen.mo differ diff --git a/locale/it_IT/LC_MESSAGES/draw-on-your-screen.po b/locale/it_IT/LC_MESSAGES/draw-on-your-screen.po new file mode 100644 index 0000000..7fc7c82 --- /dev/null +++ b/locale/it_IT/LC_MESSAGES/draw-on-your-screen.po @@ -0,0 +1,325 @@ +# Copyright (C) 2019 Listed translators +# +# This file is distributed under the same license as Draw On Your Screen. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: Draw On Your Screen VERSION\n" +"Report-Msgid-Bugs-To: https://framagit.org/abakkk/DrawOnYourScreen/issues\n" +"POT-Creation-Date: 2019-06-15 08:47+0200\n" +"PO-Revision-Date: 2019-06-15 09:52+0200\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 1.8.7.1\n" +"Last-Translator: albano battistella \n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Language: it_IT\n" + +# add your name here, for example: +# "Albano Battistella\n" +# "Ali\n" +# "" +# It will be displayed in About page +msgid "Translators" +msgstr "Albano Battistella" + +#: extension.js +msgid "Leaving drawing mode" +msgstr "Lascia la modalità di disegno" + +msgid "Press Ctrl + F1 for help" +msgstr "Premi Ctrl + F1 per aiuto" + +msgid "Entering drawing mode" +msgstr "Entra in modalità disegno" + +#: draw.js +msgid "Free drawing" +msgstr "Disegno libero" + +msgid "Line" +msgstr "Linea" + +msgid "Ellipse" +msgstr "Ellisse" + +msgid "Rectangle" +msgstr "Rettangolo" + +msgid "Text" +msgstr "Testo" + +msgid "Fill" +msgstr "Riempi" + +msgid "Stroke" +msgstr "Tratto" + +msgid "Dashed line" +msgstr "Linea tratteggiata" + +msgid "Full line" +msgstr "Linea piena" + +msgid "" +"Type your text\n" +"and press Enter" +msgstr "" +"Digita il tuo testo\n" +"e premere Invio" + +msgid "Screenshot" +msgstr "Screenshot" + +msgid "Screenshot to clipboard" +msgstr "Screenshot negli appunti" + +msgid "Area screenshot" +msgstr "Area screenshot" + +msgid "Area screenshot to clipboard" +msgstr "Area screenshot negli appunti" + +msgid "System" +msgstr "Sistema" + +msgid "Undo" +msgstr "Annulla" + +msgid "Redo" +msgstr "Ripeti" + +msgid "Erase" +msgstr "Cancella" + +msgid "Smooth" +msgstr "Liscio" + +msgid "Dashed" +msgstr "tratteggiato" + +msgid "Color" +msgstr "Colore" + +#: prefs.js +msgid "Preferences" +msgstr "Preferenze" + +msgid "About" +msgstr "Informazioni su" + +# GLOBAL_KEYBINDINGS +msgid "Enter/leave drawing mode" +msgstr "Entra/esci dalla modalità disegno" + +msgid "Erase all drawings" +msgstr "Cancella tutti i disegni" + +# INTERNAL_KEYBINDINGS +msgid "Undo last brushstroke" +msgstr "Annulla l'ultima pennellata" + +msgid "Redo last brushstroke" +msgstr "Ripeti l'ultima pennellata" + +msgid "Erase last brushstroke" +msgstr "Cancella l'ultima pennellata" + +msgid "Smooth last brushstroke" +msgstr "Liscia l'ultima pennellata" + +msgid "Select line" +msgstr "Seleziona la linea" + +msgid "Select ellipse" +msgstr "Seleziona l'ellisse" + +msgid "Select rectangle" +msgstr "Seleziona il rettangolo" + +msgid "Select text" +msgstr "Seleziona il testo" + +msgid "Unselect shape (free drawing)" +msgstr "Deseleziona forma (disegno libero)" + +msgid "Toggle fill/stroke" +msgstr "Attiva / disattiva riempimento / tratto" + +msgid "Increment line width" +msgstr "Incrementa la larghezza della linea" + +msgid "Decrement line width" +msgstr "Decrementa la larghezza della linea" + +msgid "Increment line width even more" +msgstr "Aumentare la larghezza della linea ancora di più" + +msgid "Decrement line width even more" +msgstr "Decrementa la larghezza della linea ancora di più" + +msgid "Change linejoin" +msgstr "Cambia linejoin" + +msgid "Change linecap" +msgstr "Cambia il limite di riga" + +# already in draw.js +# msgid "Dashed line" +# msgstr "" +msgid "Change font family (generic name)" +msgstr "Cambia la famiglia del font (nome generico)" + +msgid "Change font weight" +msgstr "Cambiare spessore del font" + +msgid "Change font style" +msgstr "Cambia stile del font" + +msgid "Hide panel and dock" +msgstr "Nascondi pannello e dock" + +msgid "Add a drawing background" +msgstr "Aggiungi uno sfondo di disegno" + +msgid "Square drawing area" +msgstr "Area di disegno quadrata" + +msgid "Save drawing as a SVG file" +msgstr "Salva disegno come file SVG" + +msgid "Open stylesheet.css" +msgstr "Apri stylesheet.css" + +msgid "Show help" +msgstr "Mostra aiuto" + +# OTHER_SHORTCUTS +msgid "Draw" +msgstr "Disegna" + +msgid "Left click" +msgstr "Clic sinistro" + +msgid "Menu" +msgstr "Menu" + +msgid "Right click" +msgstr "Clic destro" + +msgid "Center click" +msgstr "clic centro" + +msgid "Transform shape (when drawing)" +msgstr "Trasforma la forma (quando si disegna)" + +msgid "Ctrl key" +msgstr "Tasto Ctrl" + +msgid "Increment/decrement line width" +msgstr "Incrementa/decrementa larghezza della linea" + +msgid "Scroll" +msgstr "Scorri" + +msgid "Select color" +msgstr "Seleziona colore" + +msgid "Ctrl+1...9" +msgstr "Ctrl+1...9" + +msgid "Select eraser" +msgstr "Seleziona gomma" + +msgid "Shift key held" +msgstr "Tasto MAIUSC premuto" + +msgid "Leave" +msgstr "Lascia" + +msgid "Escape key" +msgstr "tasto Esc" + +# About page +# you are free to translate the extension name +# msgid "Draw On You Screen" +# msgstr "" +msgid "Version %d" +msgstr "Versione %d" + +msgid "" +"Start drawing with Super+Alt+D and save your beautiful work by taking a " +"screenshot" +msgstr "" +"Inizia a disegnare con Super + Alt + D e salva il tuo bellissimo lavoro " +"scattando uno screenshot" + +# Prefs page +msgid "Global" +msgstr "Globale" + +msgid "Drawing on the desktop" +msgstr "Disegno sul desktop" + +msgid "Draw On Your Screen becomes Draw On Your Desktop" +msgstr "Draw On Your Screen diventa Draw On Your Desktop" + +msgid "Persistent" +msgstr "Persistente" + +msgid "Persistent drawing through session restart" +msgstr "Disegno persistente attraverso il riavvio della sessione" + +msgid "Disable on-screen notifications" +msgstr "Disabilita le notifiche sullo schermo" + +msgid "Disable panel indicator" +msgstr "Disabilita indicatore del pannello" + +msgid "Internal" +msgstr "Interno" + +msgid "(in drawing mode)" +msgstr "(in modalità disegno)" + +msgid "" +"By pressing Ctrl key during the drawing process, you can:\n" +" . rotate a rectangle or a text area\n" +" . extend and rotate an ellipse\n" +" . curve a line (cubic Bezier curve)" +msgstr "" +"Premendo il tasto Ctrl durante del processo di disegno, " +"puoi:\n" +" . ruotare un rettangolo o un'area di testo\n" +" . estendere e ruotare un'ellisse\n" +" . curvare una linea (curva Bezier cubica)" + +msgid "Smooth stroke during the drawing process" +msgstr "Colpo liscio durante il processo di disegno" + +msgid "" +"You can also smooth the stroke afterward\n" +"See “%s”" +msgstr "" +"Puoi anche lisciare il tratto in seguito\n" +"Vedi \"% s\"" + +msgid "Change the style" +msgstr "Cambia lo stile" + +msgid "See stylesheet.css" +msgstr "Vedi stylesheet.css" + +msgid "" +"Note: When you save elements made with eraser in a SVG " +"file,\n" +"they are colored with background color, transparent if it is disabled.\n" +"(See “%s” or edit the SVG file afterwards)" +msgstr "" +" Nota : quando salvi gli elementi creati con gomma da " +"cancellare in un file SVG ,\n" +"questi sono colorati con il colore di sfondo, trasparente se disabilitati.\n" +"(Vedi \"% s\" o modifica il file SVG in seguito)" diff --git a/metadata.json b/metadata.json index b612103..fe0a0cd 100644 --- a/metadata.json +++ b/metadata.json @@ -5,6 +5,9 @@ "url": "https://framagit.org/abakkk/DrawOnYourScreen", "settings-schema": "org.gnome.shell.extensions.draw-on-your-screen", "gettext-domain": "draw-on-your-screen", + "data-dir": "drawOnYourScreen", + "persistent-file-name": "persistent", + "svg-file-name": "DrawOnYourScreen", "shell-version": [ "3.24", "3.26", @@ -13,5 +16,5 @@ "3.32", "3.34" ], - "version": 5 + "version": 5.1 } diff --git a/prefs.js b/prefs.js index e35e294..3971165 100644 --- a/prefs.js +++ b/prefs.js @@ -3,7 +3,7 @@ /* * Copyright 2019 Abakkk * - * This file is part of DrowOnYourScreen, a drawing extension for GNOME Shell. + * 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 @@ -26,10 +26,9 @@ const Lang = imports.lang; const Mainloop = imports.mainloop; const ExtensionUtils = imports.misc.extensionUtils; -const Extension = ExtensionUtils.getCurrentExtension(); -const Convenience = Extension.imports.convenience; -const Metadata = Extension.metadata; -const _ = imports.gettext.domain(Extension.metadata["gettext-domain"]).gettext; +const Me = ExtensionUtils.getCurrentExtension(); +const Convenience = ExtensionUtils.getSettings && ExtensionUtils.initTranslations ? ExtensionUtils : Me.imports.convenience; +const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; const _GTK = imports.gettext.domain('gtk30').gettext; const MARGIN = 10; @@ -68,8 +67,11 @@ var INTERNAL_KEYBINDINGS = { 'toggle-background': "Add a drawing background", 'toggle-square-area': "Square drawing area", '-separator-5': '', + '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-stylesheet': "Open stylesheet.css", + 'open-user-stylesheet': "Edit style", 'toggle-help': "Show help" }; @@ -81,6 +83,7 @@ var OTHER_SHORTCUTS = [ { desc: "Increment/decrement line width", shortcut: "Scroll" }, { desc: "Select color", shortcut: "Ctrl+1...9" }, { desc: "Select eraser", shortcut: "Shift key held" }, + { desc: "Ignore pointer movement", shortcut: "Space key held" }, { desc: "Leave", shortcut: "Escape key" } ]; @@ -103,7 +106,7 @@ function buildPrefsWidget() { return topStack; } -var TopStack = new GObject.Class({ +const TopStack = new GObject.Class({ Name: 'DrawOnYourScreenTopStack', GTypeName: 'DrawOnYourScreenTopStack', Extends: Gtk.Stack, @@ -117,7 +120,7 @@ var TopStack = new GObject.Class({ } }); -var AboutPage = new GObject.Class({ +const AboutPage = new GObject.Class({ Name: 'DrawOnYourScreenAboutPage', GTypeName: 'DrawOnYourScreenAboutPage', Extends: Gtk.ScrolledWindow, @@ -128,10 +131,10 @@ var AboutPage = new GObject.Class({ let vbox= new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin: MARGIN*3 }); this.add(vbox); - let name = " " + _(Metadata.name) + ""; - let version = _("Version %d").format(Metadata.version); - let description = _(Metadata.description); - let link = "" + Metadata.url + ""; + let name = " " + _(Me.metadata.name) + ""; + let version = _("Version %d").format(Me.metadata.version); + let description = _(Me.metadata.description); + 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"; let licence = "" + _GTK("This program comes with absolutely no warranty.\nSee the %s for details.").format(licenceLink, licenceName) + ""; @@ -152,11 +155,11 @@ var AboutPage = new GObject.Class({ creditBox.pack_start(rightBox, true, true, 5); vbox.add(creditBox); - if (_("Translators") != "Translators" && _("Translators") != "") { + if (_("translator-credits") != "translator-credits" && _("translator-credits") != "") { leftBox.pack_start(new Gtk.Label(), false, false, 0); rightBox.pack_start(new Gtk.Label(), false, false, 0); leftLabel = new Gtk.Label({ wrap: true, valign: 1, halign: 2, justify: 1, use_markup: true, label: "" + _GTK("Translated by") + "" }); - rightLabel = new Gtk.Label({ wrap: true, valign: 1, halign: 1, justify: 0, use_markup: true, label: "" + _("Translators") + "" }); + rightLabel = new Gtk.Label({ wrap: true, valign: 1, halign: 1, justify: 0, use_markup: true, label: "" + _("translator-credits") + "" }); leftBox.pack_start(leftLabel, false, false, 0); rightBox.pack_start(rightLabel, false, false, 0); } @@ -164,7 +167,7 @@ var AboutPage = new GObject.Class({ }); -var PrefsPage = new GObject.Class({ +const PrefsPage = new GObject.Class({ Name: 'DrawOnYourScreenPrefsPage', GTypeName: 'DrawOnYourScreenPrefsPage', Extends: Gtk.ScrolledWindow, @@ -174,83 +177,88 @@ var PrefsPage = new GObject.Class({ this.settings = Convenience.getSettings(); - let box = new Gtk.Box({orientation: Gtk.Orientation.VERTICAL, margin: MARGIN*3 }); + let box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin: MARGIN*3 }); this.add(box); - let listBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true }); - box.add(listBox); + 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") + "" })); + box.add(globalFrame); + + let listBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: MARGIN/2, margin_bottom: MARGIN/2 }); + globalFrame.add(listBox); let styleContext = listBox.get_style_context(); styleContext.add_class('background'); - let globalTitleBox = new Gtk.Box({ margin: MARGIN }); - let globalTitleLabel = new Gtk.Label({ use_markup: true, label: "" + _("Global") + " :" }); - globalTitleLabel.set_halign(1); - globalTitleBox.pack_start(globalTitleLabel, true, true, 4); - listBox.add(globalTitleBox); - let globalKeybindingsWidget = new KeybindingsWidget(GLOBAL_KEYBINDINGS, this.settings); globalKeybindingsWidget.margin = MARGIN; listBox.add(globalKeybindingsWidget); - let desktopBox = new Gtk.Box({ margin: 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, 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 persistentBox = new Gtk.Box({ margin: MARGIN }); + 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, label: "" + _("Persistent drawing through session restart") + "" }); + 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"); + 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); + 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 osdBox = new Gtk.Box({ margin: MARGIN }); + 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 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); + this.settings.bind('osd-disabled', osdSwitch, 'active', 0); osdBox.pack_start(osdLabelBox, true, true, 4); osdBox.pack_start(osdSwitch, false, false, 4); listBox.add(osdBox); - let indicatorBox = new Gtk.Box({ margin: MARGIN }); + 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); + 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); - this.addSeparator(listBox); - let internalTitleBox = new Gtk.Box({ margin: MARGIN }); - let internalTitleLabel = new Gtk.Label({ use_markup: true, label: "" + _("Internal") + " " + _("(in drawing mode)") + " :" }); - internalTitleLabel.set_halign(1); - internalTitleBox.pack_start(internalTitleLabel, true, true, 4); - listBox.add(internalTitleBox); + let children = listBox.get_children(); + for (let i = 0; i < children.length; i++) { + if (children[i].activatable) + children[i].set_activatable(false); + } - listBox.add(new Gtk.Box({ margin_top: MARGIN/2, margin_left: MARGIN, margin_right: MARGIN })); + 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)") })); + box.add(internalFrame); + + listBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: MARGIN }); + internalFrame.add(listBox); + + styleContext = listBox.get_style_context(); + styleContext.add_class('background'); for (let i = 0; i < OTHER_SHORTCUTS.length; i++) { if (OTHER_SHORTCUTS[i].desc.indexOf('-separator-') != -1) { @@ -266,70 +274,63 @@ var PrefsPage = new GObject.Class({ listBox.add(otherBox); } - listBox.add(new Gtk.Box({ margin_top: MARGIN, margin_left: MARGIN, margin_right: MARGIN })); - - let controlBox = new Gtk.Box({ margin_top: MARGIN, margin_left: MARGIN, margin_right: MARGIN, margin_bottom:MARGIN }); + let controlBox = new Gtk.Box({ margin: MARGIN, margin_top: 2*MARGIN }); let controlLabel = new Gtk.Label({ + wrap: true, + xalign: 0, use_markup: true, - label: _("By pressing Ctrl key during the drawing process, you can:\n . rotate a rectangle or a text area\n . extend and rotate an ellipse\n . curve a line (cubic Bezier curve)") + label: _("By pressing Ctrl key during the drawing process, you can:\n" + + " . rotate a rectangle or a text area\n" + + " . extend and rotate an ellipse\n" + + " . curve a line (cubic Bezier curve)\n" + + " . smooth a free drawing stroke (you may prefer to smooth the stroke afterward, see “%s”)").format(_("Smooth last brushstroke")) }); controlLabel.set_halign(1); - controlLabel.get_style_context().add_class("dim-label"); + controlLabel.get_style_context().add_class('dim-label'); controlBox.pack_start(controlLabel, true, true, 4); listBox.add(controlBox); - let smoothBox = new Gtk.Box({ margin: MARGIN }); - let smoothLabelBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); - let smoothLabel1 = new Gtk.Label({label: _("Smooth stroke during the drawing process")}); - let smoothLabel2 = new Gtk.Label({ use_markup: true, halign: 1, label: "" + _("You can also smooth the stroke afterward\nSee “%s”").format(_("Smooth last brushstroke")) + "" }); - smoothLabel1.set_halign(1); - smoothLabel2.get_style_context().add_class("dim-label"); - smoothLabelBox.pack_start(smoothLabel1, true, true, 0); - smoothLabelBox.pack_start(smoothLabel2, true, true, 0); - let smoothSwitch = new Gtk.Switch({valign: 3}); - this.settings.bind("smoothed-stroke", smoothSwitch, "active", 0); - smoothBox.pack_start(smoothLabelBox, true, true, 4); - smoothBox.pack_start(smoothSwitch, false, false, 4); - listBox.add(smoothBox); - let internalKeybindingsWidget = new KeybindingsWidget(INTERNAL_KEYBINDINGS, this.settings); internalKeybindingsWidget.margin = MARGIN; listBox.add(internalKeybindingsWidget); - let styleBox = new Gtk.Box({ margin_top: MARGIN, margin_left: MARGIN, margin_right: MARGIN, margin_bottom:MARGIN }); - let styleLabel = new Gtk.Label({ label: _("Change the style") }); + 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); - let styleLabel2 = new Gtk.Label({ label: _("See stylesheet.css") }); + styleLabel.get_style_context().add_class('dim-label'); styleBox.pack_start(styleLabel, true, true, 4); - styleBox.pack_start(styleLabel2, false, false, 4); listBox.add(styleBox); - let noteBox = new Gtk.Box({ margin_top: MARGIN, margin_left: MARGIN, margin_right: MARGIN, margin_bottom:MARGIN }); + let noteBox = new Gtk.Box({ margin: MARGIN }); let noteLabel = new Gtk.Label({ + wrap: true, + xalign: 0, use_markup: true, - label: _("Note: When you save elements made with eraser in a SVG file,\nthey are colored with background color, transparent if it is disabled.\n(See “%s” or edit the SVG file afterwards)").format(_("Add a drawing background")) + label: _("Note: 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"); + noteLabel.get_style_context().add_class('dim-label'); noteBox.pack_start(noteLabel, true, true, 4); listBox.add(noteBox); - let children = listBox.get_children(); + children = listBox.get_children(); for (let i = 0; i < children.length; i++) { if (children[i].activatable) children[i].set_activatable(false); } - }, - - addSeparator: function(container) { - let separatorRow = new Gtk.ListBoxRow({sensitive: false}); - separatorRow.add(new Gtk.Separator({ margin: MARGIN })); - container.add(separatorRow); } }); // this code comes from Sticky Notes View by Sam Bull, https://extensions.gnome.org/extension/568/notes/ -var KeybindingsWidget = new GObject.Class({ +const KeybindingsWidget = new GObject.Class({ Name: 'DrawOnYourScreenKeybindings.Widget', GTypeName: 'DrawOnYourScreenKeybindingsWidget', Extends: Gtk.Box, diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled index 329554d..eebc890 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 9864222..8890675 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,11 +1,6 @@ - - false - smoothed stroke - smoothed stroke - false move drawing on desktop @@ -107,12 +102,12 @@ decrement the line width - ["<Primary>Page_Up"] + ["<Primary><Shift>KP_Add"] increment the line width even more increment the line width even more - ["<Primary>Page_Down"] + ["<Primary><Shift>KP_Subtract"] decrement the line width even more decrement the line width even more @@ -196,16 +191,31 @@ toggle font style toggle font style - + ["<Primary>o"] - open stylesheet - open stylesheet + open user stylesheet to edit style + open user stylesheet to edit style - ["<Primary>s"] + ["<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>Page_Up"] + Open next json file + Open next json file + ["<Primary>F1"] toggle help diff --git a/stylesheet.css b/stylesheet.css index 862879a..bd02a07 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -1,50 +1,6 @@ -/* - * Except for the font, - * you don't need to restart the extension. - * Just save this file and the changes will be applied for your next brushstroke. - * - * line-join (no string): - * 0 : miter, 1 : round, 2 : bevel - * line-cap (no string): - * 0 : butt, 1 : round, 2 : square - * - * dash: - * dash-array-on is the length of dashes (no dashes if 0, you can put 0.1 to get dots or square according to line-cap) - * dash-array-off is the length of gaps (no dashes if 0) - * - * font: - * only one family : no comma separated list of families like "font1, font2, ..., Sans-Serif" - * font family can be any font installed, or a generic family name (Serif, Sans-Serif, Monospace, Cursive, Fantasy) - * font weight and font style : no upper case when string - * weight <= 500 (or lighter, normal, medium) is rendered as normal - * weight > 500 (or bolder, bold) is rendered as bold - * oblique and italic style support depends on the font family and seem to be rendered identically - * - */ +@import "./data/default.css"; -.draw-on-your-screen { - -drawing-line-width: 5px; - -drawing-line-join: 1; - -drawing-line-cap: 1; - -drawing-dash-array-on: 5px; - -drawing-dash-array-off: 15px; - -drawing-dash-offset: 0px; - -drawing-color1: HotPink; - -drawing-color2: Cyan; - -drawing-color3: yellow; - -drawing-color4: Orangered; - -drawing-color5: Chartreuse; - -drawing-color6: DarkViolet; - -drawing-color7: #ffffff; - -drawing-color8: rgba(130, 130, 130, 0.3); - -drawing-color9: rgb(0, 0, 0); - -drawing-background-color: #2e3436; /* GS osd_bg_color: #2e3436, GTK Adwaita-dark theme_base_color: #2d2c2e */ - font-family: Cantarell; - font-weight: normal; - font-style: normal; -} - -/*********************************************/ +/* The following styles don't affect the drawing */ /* square area */ @@ -56,8 +12,7 @@ outline: none; } -/* The following styles don't affect the drawing, - * but the "Ctrl + F1" on-screen-display */ + /* "Ctrl + F1" on-screen-display */ .draw-on-your-screen-helper { margin: 0; @@ -73,36 +28,71 @@ font-weight: normal; } -.draw-on-your-screen-separator { +.draw-on-your-screen-helper-separator { min-height: 0.6em; } /* context menu */ .draw-on-your-screen-menu { - font-size: 0.98em; /* default: 1em */ + font-size: 0.97em; /* default: 1em */ } .draw-on-your-screen-menu .popup-menu-item { - padding-top: .37em; /* default: .4em */ - padding-bottom: .37em; + padding-top: .3em; /* default: .4em */ + padding-bottom: .3em; } .draw-on-your-screen-menu .popup-menu-icon { - icon-size: 1.04em; /* default: 1.09 */ + icon-size: 1em; /* default: 1.09 */ + padding-top: 0.03em; } .draw-on-your-screen-menu .toggle-switch { - height: 20px; /* default: 22px */ -} + height: 1.35em; /* default: 22px */ +} -.draw-on-your-screen-menu-separator StLabel { +.draw-on-your-screen-menu-separator-item { + padding-top: 0.14em; + padding-bottom: 0.14em; +} + +.draw-on-your-screen-menu-separator-item StLabel { font-size: 0; } +.draw-on-your-screen-menu-separator-item .popup-separator-menu-item { + margin-top: 0.2em; /* default 6px */ + margin-bottom: 0.2em; /* default 6px */ +} + .draw-on-your-screen-menu-slider-label { min-width: 3em; text-align: right; } + +.draw-on-your-screen-menu-entry-item { + padding-right: 1.15em; /* default 1.75em */ + spacing: 0; /* default 12px */ +} + +.draw-on-your-screen-menu-entry { + border: none; + border-radius: 3px; + padding: 0.35em 0.57em; + width: 10em; +} + +.draw-on-your-screen-menu-entry-error { + color: #f57900; /* upstream warning_color */ +} + +.draw-on-your-screen-menu-entry:focus { + padding: 0.35em 0.57em; +} + +.draw-on-your-screen-menu-delete-button:hover { + color: #f57900; +}