This commit is contained in:
abakkk 2019-03-05 12:36:59 +01:00
parent 1f67bc15af
commit 7f9a3195b4
9 changed files with 1863 additions and 0 deletions

92
convenience.js Normal file
View File

@ -0,0 +1,92 @@
/*
Copyright (c) 2011-2012, Giovanni Campagna <scampa.giovanni@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the GNOME nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
const Gettext = imports.gettext;
const Gio = imports.gi.Gio;
const Config = imports.misc.config;
const ExtensionUtils = imports.misc.extensionUtils;
/**
* initTranslations:
* @domain: (optional): the gettext domain to use
*
* Initialize Gettext to load translations from extensionsdir/locale.
* If @domain is not provided, it will be taken from metadata['gettext-domain']
*/
function initTranslations(domain) {
let extension = ExtensionUtils.getCurrentExtension();
domain = domain || extension.metadata['gettext-domain'];
// check if this extension was built with "make zip-file", and thus
// has the locale files in a subfolder
// otherwise assume that extension has been installed in the
// same prefix as gnome-shell
let localeDir = extension.dir.get_child('locale');
if (localeDir.query_exists(null))
Gettext.bindtextdomain(domain, localeDir.get_path());
else
Gettext.bindtextdomain(domain, Config.LOCALEDIR);
}
/**
* getSettings:
* @schema: (optional): the GSettings schema id
*
* Builds and return a GSettings schema for @schema, using schema files
* in extensionsdir/schemas. If @schema is not provided, it is taken from
* metadata['settings-schema'].
*/
function getSettings(schema) {
let extension = ExtensionUtils.getCurrentExtension();
schema = schema || extension.metadata['settings-schema'];
const GioSSS = Gio.SettingsSchemaSource;
// check if this extension was built with "make zip-file", and thus
// has the schema files in a subfolder
// otherwise assume that extension has been installed in the
// same prefix as gnome-shell (and therefore schemas are available
// in the standard folders)
let schemaDir = extension.dir.get_child('schemas');
let schemaSource;
if (schemaDir.query_exists(null))
schemaSource = GioSSS.new_from_directory(schemaDir.get_path(),
GioSSS.get_default(),
false);
else
schemaSource = GioSSS.get_default();
let schemaObj = schemaSource.lookup(schema, true);
if (!schemaObj)
throw new Error('Schema ' + schema + ' could not be found for extension '
+ extension.metadata.uuid + '. Please check your installation.');
return new Gio.Settings({ settings_schema: schemaObj });
}

690
draw.js Normal file
View File

@ -0,0 +1,690 @@
/* jslint esversion: 6 */
/*
* Copyright 2019 Abakkk
*
* This file is part of DrowOnYourScreen, a drawing extension for GNOME Shell.
* https://framagit.org/abakkk/DrawOnYourScreen
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
const Cairo = imports.cairo;
const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Signals = imports.signals;
const St = imports.gi.St;
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 Prefs = Extension.imports.prefs;
const _ = imports.gettext.domain(Extension.metadata["gettext-domain"]).gettext;
var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4 };
var ShapeNames = { 0: "Free drawing", 1: "Line", 2: "Circle", 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' };
// DrawingArea is the widget in which we draw, thanks to Cairo.
// It creates and manages a DrawingElement for each "brushstroke".
// It handles pointer/mouse/(touch?) events and some keyboard events.
var DrawingArea = new Lang.Class({
Name: 'DrawingArea',
Extends: St.DrawingArea,
_init: function(params, helper) {
this.parent({ style_class: 'draw-on-your-screen', name: params && params.name ? params.name : ""});
// 'style-changed' is emitted when 'this' is added to an actor
// ('this' needs to be in the stage to query theme_node)
this.connect('style-changed', this._onStyleChanged.bind(this));
this.connect('repaint', this._repaint.bind(this));
this.emitter = new DrawingAreaEmitter();
this.helper = helper;
this.elements = [];
this.undoneElements = [];
this.currentElement = null;
this.currentShape = Shapes.NONE;
this.hasBackground = false;
this.textHasCursor = false;
this.dashedLine = false;
this.colors = [Clutter.Color.new(0, 0, 0, 255)];
},
_redisplay: function() {
// force area to emit 'repaint'
this.queue_repaint();
},
_onStyleChanged: function() {
try {
let themeNode = this.get_theme_node();
for (let i = 1; i < 10; i++) {
this.colors[i] = themeNode.get_color('-drawing-color' + i);
}
this.activeBackgroundColor = themeNode.get_color('-drawing-background-color');
this.currentLineWidth = themeNode.get_length('-drawing-line-width');
this.currentLineJoin = themeNode.get_double('-drawing-line-join');
this.currentLineCap = themeNode.get_double('-drawing-line-cap');
this.dashArray = [themeNode.get_length('-drawing-dash-array-on'), themeNode.get_length('-drawing-dash-array-off')];
this.dashOffset = themeNode.get_length('-drawing-dash-offset');
let font = themeNode.get_font();
this.fontFamily = font.get_family();
this.currentFontWeight = font.get_weight();
this.currentFontStyle = font.get_style();
} catch(e) {
logError(e);
}
for (let i = 1; i < 10; i++) {
this.colors[i] = this.colors[i].alpha ? this.colors[i] : this.colors[0];
}
this.currentColor = this.colors[1];
this.currentLineWidth = (this.currentLineWidth > 0) ? this.currentLineWidth : 3;
this.currentLineJoin = ([0, 1, 2].indexOf(this.currentLineJoin) != -1) ? this.currentLineJoin : Cairo.LineJoin.ROUND;
this.currentLineCap = ([0, 1, 2].indexOf(this.currentLineCap) != -1) ? this.currentLineCap : Cairo.LineCap.ROUND;
this.currentFontFamilyId = 0;
this.currentFontWeight = this.currentFontWeight > 500 ? 1 : 0 ;
// font style enum order of Cairo and Pango are different
this.currentFontStyle = this.currentFontStyle == 2 ? 1 : ( this.currentFontStyle == 1 ? 2 : 0);
},
_repaint: function(area) {
let cr = area.get_context();
for (let i = 0; i < this.elements.length; i++) {
this.elements[i].buildCairo(cr, false);
if (this.elements[i].fill && this.elements[i].shape != Shapes.LINE)
cr.fill();
else
cr.stroke();
}
if (this.currentElement) {
this.currentElement.buildCairo(cr, this.textHasCursor);
cr.stroke();
}
cr.$dispose();
},
_onButtonPressed: function(actor, event) {
let button = event.get_button();
let [x, y] = event.get_coords();
let shiftPressed = event.has_shift_modifier();
// stop writing
if (this.currentElement && this.currentElement.shape == Shapes.TEXT) {
this._stopWriting();
}
// hide helper
if (this.helper.visible && button != 2) {
this.helper.hideHelp();
return Clutter.EVENT_STOP;
}
if (button == 1) {
this._startDrawing(x, y, false, shiftPressed);
return Clutter.EVENT_STOP;
} else if (button == 2) {
this.toggleShape();
} else if (button == 3) {
this._startDrawing(x, y, true, shiftPressed);
return Clutter.EVENT_STOP;
}
return Clutter.EVENT_PROPAGATE;
},
_onKeyPressed: function(actor, event) {
if (event.get_key_symbol() == Clutter.Escape) {
this.emitter.emit('stop-drawing');
this.erase();
return Clutter.EVENT_STOP;
} else if (this.currentElement && this.currentElement.shape == Shapes.TEXT) {
if (event.get_key_symbol() == Clutter.KEY_BackSpace) {
this.currentElement.text = this.currentElement.text.slice(0, -1);
this._updateCursorTimeout();
} 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._redisplay();
});
return Clutter.EVENT_STOP;
} else if (event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == 65421) {
// stop writing
// Clutter.KEY_Return is "Enter" and 65421 is KP_Enter
this._stopWriting();
} else {
let unicode = event.get_key_unicode();
this.currentElement.text += unicode;
this._updateCursorTimeout();
}
this._redisplay();
return Clutter.EVENT_STOP;
} else {
return Clutter.EVENT_PROPAGATE;
}
},
_onScroll: function(actor, event) {
if (this.helper.visible)
return Clutter.EVENT_PROPAGATE;
let direction = event.get_scroll_direction();
if (direction == Clutter.ScrollDirection.UP)
this.incrementLineWidth(1);
else if (direction == Clutter.ScrollDirection.DOWN)
this.incrementLineWidth(-1);
else
return Clutter.EVENT_PROPAGATE;
return Clutter.EVENT_STOP;
},
_startDrawing: function(stageX, stageY, fill, eraser) {
let [success, startX, startY] = this.transform_stage_point(stageX, stageY);
if (!success)
return;
this.buttonReleasedHandler = this.connect('button-release-event', (actor, event) => {
this._stopDrawing();
});
this.currentElement = new DrawingElement ({
color: this.currentColor,
line: { lineWidth: this.currentLineWidth, lineJoin: this.currentLineJoin, lineCap: this.currentLineCap },
dash: { array: this.dashedLine ? this.dashArray : [0, 0] , offset: this.dashedLine ? this.dashOffset : 0 },
fill: fill,
eraser: eraser,
shape: this.currentShape == Shapes.TEXT ? Shapes.RECTANGLE : this.currentShape,
text: '',
font: { family: (this.currentFontFamilyId == 0 ? this.fontFamily : FontFamilyNames[this.currentFontFamilyId]), weight: this.currentFontWeight, style: this.currentFontStyle },
points: [[startX, startY]]
});
this.motionHandler = this.connect('motion-event', (actor, event) => {
let coords = event.get_coords();
let [s, x, y] = this.transform_stage_point(coords[0], coords[1]);
if (!s)
return;
this._updateDrawing(x, y);
});
},
_stopDrawing: function() {
if (this.motionHandler) {
this.disconnect(this.motionHandler);
this.motionHandler = null;
}
if (this.buttonReleasedHandler) {
this.disconnect(this.buttonReleasedHandler);
this.buttonReleasedHandler = null;
}
// start writing
if (this.currentShape == Shapes.TEXT && this.currentElement) {
this.currentElement.shape = Shapes.TEXT;
this.currentElement.text = '';
this.emitter.emit('show-osd', _("Type your text\nand press Enter"), null);
this._updateCursorTimeout();
this._redisplay();
return;
}
if (this.currentElement) {
this.elements.push(this.currentElement);
}
this.currentElement = null;
this._redisplay();
},
_updateDrawing: function(x, y) {
if (!this.currentElement)
return;
if (this.currentElement.shape == Shapes.NONE)
this.currentElement.points.push([x, y]);
else
this.currentElement.points[1] = [x, y];
this._redisplay();
},
_stopWriting: function() {
this.elements.push(this.currentElement);
this.currentElement = null;
this._stopCursorTimeout();
this._redisplay();
},
_stopCursorTimeout: function() {
if (this.cursorTimeoutId) {
Mainloop.source_remove(this.cursorTimeoutId);
this.cursorTimeoutId = null;
}
this.textHasCursor = false;
},
_updateCursorTimeout: function() {
this._stopCursorTimeout();
this.cursorTimeoutId = Mainloop.timeout_add(600, () => {
this.textHasCursor = !this.textHasCursor;
this._redisplay();
return true;
});
},
erase: function() {
this.elements = [];
this.undoneElements = [];
this.currentElement = null;
this._redisplay();
},
deleteLastElement: function() {
if (this.currentElement) {
if (this.motionHandler) {
this.disconnect(this.motionHandler);
this.motionHandler = null;
}
if (this.buttonReleasedHandler) {
this.disconnect(this.buttonReleasedHandler);
this.buttonReleasedHandler = null;
}
this.currentElement = null;
this._stopCursorTimeout();
} else {
this.elements.pop();
}
this._redisplay();
},
undo: function() {
if (this.elements.length > 0)
this.undoneElements.push(this.elements.pop());
this._redisplay();
},
redo: function() {
if (this.undoneElements.length > 0)
this.elements.push(this.undoneElements.pop());
this._redisplay();
},
toggleBackground: function() {
this.hasBackground = !this.hasBackground;
this.get_parent().set_background_color(this.hasBackground ? this.activeBackgroundColor : null);
},
toggleColor: function() {
this.selectColor((this.currentColor == this.colors[1]) ? 2 : 1);
},
selectColor: function(index) {
this.currentColor = this.colors[index];
if (this.currentElement) {
this.currentElement.color = this.currentColor;
this._redisplay();
}
this.emitter.emit('show-osd', this.currentColor.to_string(), null);
},
selectShape: function(shape) {
this.currentShape = shape;
this.emitter.emit('show-osd', _(ShapeNames[shape]), null);
},
toggleShape: function() {
this.selectShape((this.currentShape == Object.keys(Shapes).length - 1) ? 0 : this.currentShape + 1);
},
toggleDash: function() {
this.dashedLine = !this.dashedLine;
this.emitter.emit('show-osd', this.dashedLine ? _("Dashed line") : _("Full line"), null);
},
incrementLineWidth: function(increment) {
this.currentLineWidth = Math.max(this.currentLineWidth + increment, 1);
this.emitter.emit('show-osd', this.currentLineWidth + "px", this.currentLineWidth);
},
toggleLineJoin: function() {
this.currentLineJoin = this.currentLineJoin == 2 ? 0 : this.currentLineJoin + 1;
this.emitter.emit('show-osd', LineJoinNames[this.currentLineJoin], null);
},
toggleLineCap: function() {
this.currentLineCap = this.currentLineCap == 2 ? 0 : this.currentLineCap + 1;
this.emitter.emit('show-osd', LineCapNames[this.currentLineCap], null);
},
toggleFontWeight: function() {
this.currentFontWeight = this.currentFontWeight == 1 ? 0 : this.currentFontWeight + 1;
if (this.currentElement) {
this.currentElement.font.weight = this.currentFontWeight;
this._redisplay();
}
this.emitter.emit('show-osd', FontWeightNames[this.currentFontWeight], null);
},
toggleFontStyle: function() {
this.currentFontStyle = this.currentFontStyle == 2 ? 0 : this.currentFontStyle + 1;
if (this.currentElement) {
this.currentElement.font.style = this.currentFontStyle;
this._redisplay();
}
this.emitter.emit('show-osd', FontStyleNames[this.currentFontStyle], null);
},
toggleFontFamily: function() {
this.currentFontFamilyId = this.currentFontFamilyId == 5 ? 0 : this.currentFontFamilyId + 1;
let currentFontFamily = this.currentFontFamilyId == 0 ? this.fontFamily : FontFamilyNames[this.currentFontFamilyId];
if (this.currentElement) {
this.currentElement.font.family = currentFontFamily;
this._redisplay();
}
this.emitter.emit('show-osd',currentFontFamily , null);
},
toggleHelp: function() {
if (this.helper.visible)
this.helper.hideHelp();
else
this.helper.showHelp();
},
enterDrawingMode: function() {
this.keyPressedHandler = this.connect('key-press-event', this._onKeyPressed.bind(this));
this.buttonPressedHandler = this.connect('button-press-event', this._onButtonPressed.bind(this));
this.scrollHandler = this.connect('scroll-event', this._onScroll.bind(this));
this.selectShape(Shapes.NONE);
this.get_parent().set_background_color(this.hasBackground ? this.activeBackgroundColor : null);
},
leaveDrawingMode: function() {
if (this.keyPressedHandler) {
this.disconnect(this.keyPressedHandler);
this.keyPressedHandler = null;
}
if (this.buttonPressedHandler) {
this.disconnect(this.buttonPressedHandler);
this.buttonPressedHandler = null;
}
if (this.motionHandler) {
this.disconnect(this.motionHandler);
this.motionHandler = null;
}
if (this.buttonReleasedHandler) {
this.disconnect(this.buttonReleasedHandler);
this.buttonReleasedHandler = null;
}
if (this.scrollHandler) {
this.disconnect(this.scrollHandler);
this.scrollHandler = null;
}
if (this.helper.visible)
this.helper.hideHelp();
this.currentElement = null;
this._stopCursorTimeout();
this.dashedLine = false;
this._redisplay();
this.get_parent().set_background_color(null);
},
save: function() {
// stop drawing or writing
if (this.currentElement && this.currentElement.shape == Shapes.TEXT) {
this._stopWriting();
} else if (this.currentElement && this.currentShape != Shapes.TEXT) {
this._stopDrawing();
}
let content = `<svg viewBox="0 0 ${this.width} ${this.height}" xmlns="http://www.w3.org/2000/svg">`;
let backgroundColorString = this.hasBackground ? this.activeBackgroundColor.to_string() : 'transparent';
if (backgroundColorString != 'transparent') {
content += `\n <rect id="background" width="100%" height="100%" fill="${backgroundColorString}"/>`;
}
for (let i = 0; i < this.elements.length; i++) {
content += this.elements[i].buildSVG(backgroundColorString);
}
content += "\n</svg>";
let date = GLib.DateTime.new_now_local();
let filename = `DrawOnYourScreen ${date.format("%F")} ${date.format("%X")}.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))
return false;
let success = GLib.file_set_contents(path, content);
if (success) {
// pass the parent (bgContainer) to Flashspot because coords of this are relative
let flashspot = new Screenshot.Flashspot(this.get_parent());
flashspot.fire();
global.play_theme_sound(0, 'screen-capture', "Save as SVG", null);
}
},
disable: function() {
if (this.theme && this.customStylesheetsChangedHandler) {
this.theme.disconnect(this.customStylesheetsChangedHandler);
this.customStylesheetsChangedHandler = null;
}
this.erase();
}
});
var DrawingAreaEmitter = new Lang.Class({
Name: 'DrawingAreaEmitter',
_init: function() {
}
});
Signals.addSignalMethods(DrawingAreaEmitter.prototype);
// 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({
Name: 'DrawingElement',
_init: function(params) {
for (let key in params)
this[key] = params[key];
},
buildCairo: function(cr, showTextCursor) {
cr.setLineCap(this.line.lineCap);
cr.setLineJoin(this.line.lineJoin);
cr.setLineWidth(this.line.lineWidth);
if (this.dash.array[0] > 0 && this.dash.array[1] > 0)
cr.setDash(this.dash.array, this.dash.offset);
else
cr.setDash([1000000], 0);
if (this.eraser)
cr.setOperator(Cairo.Operator.CLEAR);
else
cr.setOperator(Cairo.Operator.OVER);
Clutter.cairo_set_source_color(cr, this.color);
let [points, shape] = [this.points, this.shape];
if (shape == Shapes.NONE || shape == Shapes.LINE) {
cr.moveTo(points[0][0], points[0][1]);
for (let j = 1; j < points.length; j++) {
cr.lineTo(points[j][0], points[j][1]);
}
} else if (shape == Shapes.ELLIPSE && points.length == 2) {
let r = Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]);
cr.arc(points[0][0], points[0][1], r, 0, 2 * Math.PI);
} else if (shape == Shapes.RECTANGLE && points.length == 2) {
cr.rectangle(points[0][0], points[0][1], points[1][0] - points[0][0], points[1][1] - points[0][1]);
} else if (shape == Shapes.TEXT && points.length == 2) {
cr.selectFontFace(this.font.family, this.font.style, this.font.weight);
cr.setFontSize(Math.abs(points[1][1] - points[0][1]));
cr.moveTo(Math.min(points[0][0], points[1][0]), Math.max(points[0][1], points[1][1]));
cr.showText((showTextCursor) ? (this.text + "_") : this.text);
}
},
buildSVG: function(bgColor) {
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.to_string();
let attributes = `fill="${this.fill ? color : 'transparent'}" ` +
`stroke="${this.fill ? 'transparent' : color}" ` +
`stroke-width="${this.line.lineWidth}" ` +
`stroke-linecap="${LineCapNames[this.line.lineCap].toLowerCase()}" ` +
`stroke-linejoin="${LineJoinNames[this.line.lineJoin].toLowerCase()}"`;
if (this.dash.array[0] > 0 && this.dash.array[1] > 0)
attributes += ` stroke-dasharray="${this.dash.array[0]} ${this.dash.array[1]}" stroke-dashoffset="${this.dash.offset}"`;
if (this.shape == Shapes.NONE || this.shape == Shapes.LINE) {
row += `<path ${attributes} d="M${points[0][0]} ${points[0][1]}`;
for (let i = 1; i < points.length; i++) {
row += ` L ${points[i][0]} ${points[i][1]}`;
}
row += `${this.fill ? 'z' : ''}"/>`;
} else if (this.shape == Shapes.ELLIPSE && points.length == 2) {
let r = Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]);
row += `<circle ${attributes} cx="${points[0][0]}" cy="${points[0][1]}" r="${r}"/>`;
} else if (this.shape == Shapes.RECTANGLE && points.length == 2) {
row += `<rect ${attributes} x="${Math.min(points[0][0], points[1][0])}" y="${Math.min(points[0][1], points[1][1])}" ` +
`width="${Math.abs(points[1][0] - points[0][0])}" height="${Math.abs(points[1][1] - points[0][1])}"/>`;
} else if (this.shape == Shapes.TEXT && points.length == 2) {
attributes = `fill="${color}" ` +
`stroke="transparent" ` +
`font-family="${this.font.family}" ` +
`font-size="${Math.abs(points[1][1] - points[0][1])}" ` +
`font-weight="${FontWeightNames[this.font.weight].toLowerCase()}" ` +
`font-style="${FontStyleNames[this.font.style].toLowerCase()}"`;
row += `<text ${attributes} x="${Math.min(points[0][0], points[1][0])}" y="${Math.max(points[0][1], points[1][1])}">${this.text}</text>`;
}
return row;
}
});
var HELPER_ANIMATION_TIME = 0.25;
// DrawingHelper provides the "help osd" (Ctrl + F1)
// It uses the same texts as in prefs
var DrawingHelper = new Lang.Class({
Name: 'DrawingHelper',
Extends: St.ScrollView,
_init: function(params, monitor) {
this.parent(params);
this.monitor = monitor;
this.hide();
this.vbox = new St.BoxLayout({ style_class: 'osd-window draw-on-your-screen-helper', vertical: true });
this.add_actor(this.vbox);
this.vbox.add(new St.Label({ text: _("Global") }));
let settings = Convenience.getSettings();
for (let settingKey in Prefs.GLOBAL_KEYBINDINGS) {
let hbox = new St.BoxLayout({ vertical: false });
if (settingKey.indexOf('-separator-') != -1) {
this.vbox.add(hbox);
continue;
}
let [keyval, mods] = Gtk.accelerator_parse(settings.get_strv(settingKey)[0]);
hbox.add(new St.Label({ text: _(Prefs.GLOBAL_KEYBINDINGS[settingKey]) }));
hbox.add(new St.Label({ text: Gtk.accelerator_get_label(keyval, mods) }), { expand: true });
this.vbox.add(hbox);
}
this.vbox.add(new St.Label({ text: _("Internal") }));
for (let desc in Prefs.OTHER_SHORTCUTS) {
if (desc.indexOf('-separator-') != -1) {
this.vbox.add(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-separator' }));
continue;
}
let hbox = new St.BoxLayout({ vertical: false });
hbox.add(new St.Label({ text: _(desc) }));
hbox.add(new St.Label({ text: _(Prefs.OTHER_SHORTCUTS[desc]) }), { expand: true });
this.vbox.add(hbox);
}
this.vbox.add(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-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' }));
continue;
}
let hbox = new St.BoxLayout({ vertical: false });
let [keyval, mods] = Gtk.accelerator_parse(settings.get_strv(settingKey)[0]);
hbox.add(new St.Label({ text: _(Prefs.INTERNAL_KEYBINDINGS[settingKey]) }));
hbox.add(new St.Label({ text: Gtk.accelerator_get_label(keyval, mods) }), { expand: true });
this.vbox.add(hbox);
}
},
showHelp: function() {
this.opacity = 0;
this.show();
let maxHeight = this.monitor.height*(3/4);
if (this.height > maxHeight)
this.vscrollbar_policy = Gtk.PolicyType.ALWAYS;
else
this.vscrollbar_policy = Gtk.PolicyType.NEVER;
this.set_height(Math.min(this.height, maxHeight));
this.set_position(this.monitor.x + Math.floor(this.monitor.width / 2 - this.width / 2),
this.monitor.y + Math.floor(this.monitor.height / 2 - this.height / 2));
Tweener.removeTweens(this);
Tweener.addTween(this, { opacity: 255,
time: HELPER_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: null });
},
hideHelp: function() {
Tweener.removeTweens(this);
Tweener.addTween(this, { opacity: 0,
time: HELPER_ANIMATION_TIME,
transition: 'easeOutQuad',
onComplete: this.hide.bind(this) });
},
});

278
extension.js Normal file
View File

@ -0,0 +1,278 @@
/* jslint esversion: 6 */
/*
* Copyright 2019 Abakkk
*
* This file is part of DrowOnYourScreen, a drawing extension for GNOME Shell.
* https://framagit.org/abakkk/DrawOnYourScreen
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
const Gio = imports.gi.Gio;
const Lang = imports.lang;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Main = imports.ui.main;
const OsdWindow = imports.ui.osdWindow;
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;
let manager;
function init() {
Convenience.initTranslations();
}
function enable() {
manager = new AreaManager();
}
function disable() {
manager.disable();
manager = null;
}
// 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({
Name: 'AreaManager',
_init: function() {
this.areas = [];
this.drawingHandlers = [];
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',
Convenience.getSettings(),
Meta.KeyBindingFlags.NONE,
Shell.ActionMode.ALL,
this.toggleDrawing.bind(this));
Main.wm.addKeybinding('erase-drawing',
Convenience.getSettings(),
Meta.KeyBindingFlags.NONE,
Shell.ActionMode.ALL,
this.eraseDrawing.bind(this));
this.updateAreas();
this.monitorChangedHandler = Main.layoutManager.connect('monitors-changed', this.updateAreas.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);
});
}
},
updateAreas: function() {
if (this.activeArea)
this.toggleDrawing();
this.removeAreas();
this.monitors = Main.layoutManager.monitors;
for (let i = 0; i < this.monitors.length; i++) {
let monitor = this.monitors[i];
let helper = new Draw.DrawingHelper({ name: 'drawOnYourSreenHelper' + i }, monitor);
let bgContainer = new St.Bin({ name: 'drawOnYourSreenContainer' + i });
let area = new Draw.DrawingArea({ name: 'drawOnYourSreenArea' + i }, helper);
bgContainer.set_child(area);
Main.uiGroup.add_actor(bgContainer);
Main.uiGroup.add_actor(helper);
bgContainer.set_position(monitor.x, monitor.y);
bgContainer.set_size(monitor.width, monitor.height);
area.set_position(monitor.x, monitor.y);
area.set_size(monitor.width, monitor.height);
this.drawingHandlers.push(area.emitter.connect('stop-drawing', this.toggleDrawing.bind(this)));
this.drawingHandlers.push(area.emitter.connect('show-osd', this.showOsd.bind(this)));
this.areas.push(area);
}
},
addInternalKeybindings: function() {
this.internalKeybindings = {
'undo': this.activeArea.undo.bind(this.activeArea),
'redo': this.activeArea.redo.bind(this.activeArea),
'delete-last-element': this.activeArea.deleteLastElement.bind(this.activeArea),
'save-as-svg': this.activeArea.save.bind(this.activeArea),
'toggle-background': this.activeArea.toggleBackground.bind(this.activeArea),
'increment-line-width': () => this.activeArea.incrementLineWidth(1),
'decrement-line-width': () => this.activeArea.incrementLineWidth(-1),
'increment-line-width-more': () => this.activeArea.incrementLineWidth(5),
'decrement-line-width-more': () => this.activeArea.incrementLineWidth(-5),
'toggle-linejoin': this.activeArea.toggleLineJoin.bind(this.activeArea),
'toggle-linecap': this.activeArea.toggleLineCap.bind(this.activeArea),
'toggle-dash' : this.activeArea.toggleDash.bind(this.activeArea),
'select-none-shape': () => this.activeArea.selectShape(Draw.Shapes.NONE),
'select-line-shape': () => this.activeArea.selectShape(Draw.Shapes.LINE),
'select-ellipse-shape': () => this.activeArea.selectShape(Draw.Shapes.ELLIPSE),
'select-rectangle-shape': () => this.activeArea.selectShape(Draw.Shapes.RECTANGLE),
'select-text-shape': () => this.activeArea.selectShape(Draw.Shapes.TEXT),
'toggle-font-family': this.activeArea.toggleFontFamily.bind(this.activeArea),
'toggle-font-weight': this.activeArea.toggleFontWeight.bind(this.activeArea),
'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)
};
for (let key in this.internalKeybindings) {
Main.wm.addKeybinding(key,
Convenience.getSettings(),
Meta.KeyBindingFlags.NONE,
256,
this.internalKeybindings[key]);
}
for (let i = 1; i < 10; i++) {
Main.wm.addKeybinding('select-color' + i,
Convenience.getSettings(),
Meta.KeyBindingFlags.NONE,
256,
() => this.activeArea.selectColor(i));
}
},
removeInternalKeybindings: function() {
for (let key in this.internalKeybindings) {
Main.wm.removeKeybinding(key);
}
for (let i = 1; i < 10; i++) {
Main.wm.removeKeybinding('select-color' + i);
}
},
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));
if (this.activeArea)
this.toggleDrawing();
},
eraseDrawing: function() {
for (let i = 0; i < this.areas.length; i++) {
this.areas[i].erase();
}
},
togglePanelAndDockOpacity: function() {
if (this.hiddenList) {
for (let i = 0; i < this.hiddenList.length; i++) {
this.hiddenList[i].actor.set_opacity(this.hiddenList[i].oldOpacity);
}
this.hiddenList = null;
} else {
let activeIndex = this.areas.indexOf(this.activeArea);
// dash-to-dock
let dtdContainers = Main.uiGroup.get_children().filter((actor) => {
return actor.name && actor.name == 'dashtodockContainer' &&
actor._delegate &&
actor._delegate._monitorIndex !== undefined &&
actor._delegate._monitorIndex == activeIndex;
});
// for simplicity, we assume that main dash-to-panel panel is displayed on primary monitor
// and we hide all secondary panels together if the active area is not on the primary
let name = activeIndex == Main.layoutManager.primaryIndex ? 'panelBox' : 'dashtopanelSecondaryPanelBox';
let panelBoxes = Main.uiGroup.get_children().filter((actor) => {
return actor.name && actor.name == name;
});
let actorToHide = dtdContainers.concat(panelBoxes);
this.hiddenList = [];
for (let i = 0; i < actorToHide.length; i++) {
this.hiddenList.push({ actor: actorToHide[i], oldOpacity: actorToHide[i].get_opacity() });
actorToHide[i].set_opacity(0);
}
}
},
toggleDrawing: function() {
if (this.activeArea) {
if (this.hiddenList)
this.togglePanelAndDockOpacity();
Main.popModal(this.activeArea);
let activeIndex = this.areas.indexOf(this.activeArea);
this.removeInternalKeybindings();
this.activeArea.reactive = false;
this.activeArea.leaveDrawingMode();
this.activeArea = null;
global.display.set_cursor(Meta.Cursor.DEFAULT);
Main.osdWindowManager.show(activeIndex, this.leaveGicon, _("Leaving drawing mode"), null);
} else {
// avoid to deal with Meta changes (global.display/global.screen)
let currentIndex = Main.layoutManager.monitors.indexOf(Main.layoutManager.currentMonitor);
// 256 is a custom Shell.ActionMode
if (!Main.pushModal(this.areas[currentIndex], { actionMode: 256 | 1 }))
return;
this.activeArea = this.areas[currentIndex];
this.addInternalKeybindings();
this.activeArea.reactive = true;
this.activeArea.enterDrawingMode();
global.display.set_cursor(Meta.Cursor.POINTING_HAND);
// increase OSD display time
let hideTimeoutSave = OsdWindow.HIDE_TIMEOUT;
OsdWindow.HIDE_TIMEOUT = 2000;
Main.osdWindowManager.show(currentIndex, this.enterGicon, _("Press Ctrl + F1 for help") + "\n\n" + _("Entering drawing mode"), null);
OsdWindow.HIDE_TIMEOUT = hideTimeoutSave;
}
},
showOsd: function(emitter, label, level, maxLevel) {
let activeIndex = this.areas.indexOf(this.activeArea);
if (activeIndex != -1)
Main.osdWindowManager.show(activeIndex, this.enterGicon, label, level, maxLevel);
},
removeAreas: function() {
for (let i = 0; i < this.areas.length; i++) {
let area = this.areas[i];
Main.uiGroup.remove_actor(area.get_parent());
area.emitter.disconnect(this.drawingHandlers[i]);
area.disable();
area.get_parent().destroy();
}
this.areas = [];
this.drawingHandlers = [];
},
disable: function() {
if (this.stylesheetChangedHandler) {
this.stylesheetMonitor.disconnect(this.stylesheetChangedHandler);
this.stylesheetChangedHandler = null;
}
if (this.activeArea)
this.toggleDrawing();
Main.wm.removeKeybinding('toggle-drawing');
Main.wm.removeKeybinding('erase-drawing');
this.removeAreas();
}
});

View File

@ -0,0 +1,208 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-03-04 16:40+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: extension.js
msgid "Leaving drawing mode"
msgstr ""
msgid "Press Ctrl + F1 for help"
msgstr ""
msgid "Entering drawing mode"
msgstr ""
#: draw.js
msgid "Free drawing"
msgstr ""
msgid "Line"
msgstr ""
msgid "Circle"
msgstr ""
msgid "Rectangle"
msgstr ""
msgid "Text"
msgstr ""
msgid "Dashed line"
msgstr ""
msgid "Full line"
msgstr ""
msgid ""
"Type your text\n"
"and press Enter"
msgstr ""
#: prefs.js
msgid "Enter/leave drawing mode"
msgstr ""
msgid "Erase all drawings"
msgstr ""
msgid "Undo last brushstroke"
msgstr ""
msgid "Redo last brushstroke"
msgstr ""
msgid "Erase last brushstroke"
msgstr ""
msgid "Increment line width"
msgstr ""
msgid "Decrement line width"
msgstr ""
msgid "Increment line width even more"
msgstr ""
msgid "Decrement line width even more"
msgstr ""
msgid "Change linejoin"
msgstr ""
msgid "Change linecap"
msgstr ""
msgid "Dashed line"
msgstr ""
msgid "Select line"
msgstr ""
msgid "Select circle"
msgstr ""
msgid "Select rectangle"
msgstr ""
msgid "Select text"
msgstr ""
msgid "Unselect shape (free drawing)"
msgstr ""
msgid "Change font family (generic name)"
msgstr ""
msgid "Change font weight"
msgstr ""
msgid "Change font style"
msgstr ""
msgid "Hide panel and dock"
msgstr ""
msgid "Add a drawing background"
msgstr ""
msgid "Save drawing as a SVG file"
msgstr ""
msgid "Open stylesheet.css"
msgstr ""
msgid "Show help"
msgstr ""
msgid "Draw"
msgstr ""
msgid "Left click"
msgstr ""
msgid "Draw by filling in"
msgstr ""
msgid "Right click"
msgstr ""
msgid "Toggle shape"
msgstr ""
msgid "Center click"
msgstr ""
msgid "Increment/decrement line width"
msgstr ""
msgid "Scroll"
msgstr ""
msgid "Select color"
msgstr ""
msgid "Ctrl+1...9"
msgstr ""
msgid "Select eraser"
msgstr ""
msgid "Shift key held"
msgstr ""
msgid "Leave and erase all drawings"
msgstr ""
msgid "Escape key"
msgstr ""
msgid ""
"Start drawing with Super+Alt+D\n"
"Then save your beautiful work by taking a screenshot"
msgstr ""
msgid "Global"
msgstr ""
msgid "Internal"
msgstr ""
msgid "(in drawing mode)"
msgstr ""
msgid "Change the style"
msgstr ""
msgid "See stylesheet.css"
msgstr ""
msgid ""
"<u>Note</u>: 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 \"Add a drawing background\" or edit the SVG file afterwards)"
msgstr ""
msgid ""
"<span size=\"small\">This program comes with ABSOLUTELY NO WARRANTY.\n"
"See the <a href=\"https://www.gnu.org/licenses/old-licenses/gpl-2.0.html"
"\">GNU General Public License, version 2 or later</a> for details.</span>"
msgstr ""
msgid "Credits"
msgstr ""

14
metadata.json Normal file
View File

@ -0,0 +1,14 @@
{
"name": "Draw On You Screen",
"description": "Start drawing with Super+Alt+D and save your beautiful work by taking a screenshot",
"version": 1,
"uuid": "drawOnYourScreen@abakkk.framagit.org",
"url": "https://framagit.org/abakkk/DrawOnYourScreen",
"settings-schema": "org.gnome.shell.extensions.draw-on-your-screen",
"gettext-domain": "draw-on-your-screen",
"shell-version": [
"3.26",
"3.28",
"3.30"
]
}

333
prefs.js Normal file
View File

@ -0,0 +1,333 @@
/* jslint esversion: 6 */
/*
* Copyright 2019 Abakkk
*
* This file is part of DrowOnYourScreen, a drawing extension for GNOME Shell.
* https://framagit.org/abakkk/DrawOnYourScreen
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
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 MARGIN = 10;
var GLOBAL_KEYBINDINGS = {
'toggle-drawing': "Enter/leave drawing mode",
'erase-drawing': "Erase all drawings"
};
var INTERNAL_KEYBINDINGS = {
'undo': "Undo last brushstroke",
'redo': "Redo last brushstroke",
'delete-last-element' : "Erase last brushstroke",
'-separator-1': '',
'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",
'toggle-linejoin': "Change linejoin",
'toggle-linecap': "Change linecap",
'toggle-dash': "Dashed line",
'-separator-2': '',
'select-line-shape': "Select line",
'select-ellipse-shape': "Select circle",
'select-rectangle-shape': "Select rectangle",
'select-text-shape': "Select text",
'select-none-shape': "Unselect shape (free drawing)",
'-separator-3': '',
'toggle-font-family': "Change font family (generic name)",
'toggle-font-weight': "Change font weight",
'toggle-font-style': "Change font style",
'-separator-4': '',
'toggle-panel-and-dock-visibility': "Hide panel and dock",
'toggle-background': "Add a drawing background",
'-separator-5': '',
'save-as-svg': "Save drawing as a SVG file",
'open-stylesheet': "Open stylesheet.css",
'toggle-help': "Show help"
};
var OTHER_SHORTCUTS = {
"Draw": "Left click",
"Draw by filling in": "Right click",
"Toggle shape": "Center click",
"Increment/decrement line width": "Scroll",
"Select color": "Ctrl+1...9",
"Select eraser": "Shift key held",
"Leave and erase all drawings": "Escape key"
};
function init() {
Convenience.initTranslations();
}
function buildPrefsWidget() {
let prefsPage = new PrefsPage();
prefsPage.show_all();
return prefsPage;
}
const PrefsPage = new GObject.Class({
Name: 'PrefsPage',
GTypeName: 'PrefsPage',
Extends: Gtk.ScrolledWindow,
_init: function(params) {
this.parent();
this.settings = Convenience.getSettings();
let box = new Gtk.Box({orientation: Gtk.Orientation.VERTICAL, margin: MARGIN*3 });
this.add(box);
let textBox1 = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, margin: MARGIN });
let text1 = new Gtk.Label({ wrap: true, justify: 2, use_markup: true,
label: _("Start drawing with Super+Alt+D\nThen save your beautiful work by taking a screenshot") });
textBox1.pack_start(text1, false, false, 0);
box.add(textBox1);
let listBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true, margin_top: 2*MARGIN, margin_bottom: 2*MARGIN });
box.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: "<b><big>" + _("Global") + " :</big></b>" });
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);
this.addSeparator(listBox);
let internalTitleBox = new Gtk.Box({ margin: MARGIN });
let internalTitleLabel = new Gtk.Label({ use_markup: true, label: "<b><big>" + _("Internal") + " </big></b>" + _("(in drawing mode)") + " <b><big>:</big></b>" });
internalTitleLabel.set_halign(1);
internalTitleBox.pack_start(internalTitleLabel, true, true, 4);
listBox.add(internalTitleBox);
listBox.add(new Gtk.Box({ margin_top: MARGIN/2, margin_left: MARGIN, margin_right: MARGIN }));
for (let desc in OTHER_SHORTCUTS) {
if (desc.indexOf('-separator-') != -1) {
listBox.add(new Gtk.Box({ margin_top: MARGIN, margin_left: MARGIN, margin_right: MARGIN }));
continue;
}
let otherBox = new Gtk.Box({ margin_left: MARGIN, margin_right: MARGIN });
let otherLabel = new Gtk.Label({ label: _(desc) });
otherLabel.set_halign(1);
let otherLabel2 = new Gtk.Label({ label: _(OTHER_SHORTCUTS[desc]) });
otherBox.pack_start(otherLabel, true, true, 4);
otherBox.pack_start(otherLabel2, false, false, 4);
listBox.add(otherBox);
}
listBox.add(new Gtk.Box({ margin_top: MARGIN, margin_left: MARGIN, margin_right: MARGIN }));
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") });
styleLabel.set_halign(1);
let styleLabel2 = new Gtk.Label({ label: _("See stylesheet.css") });
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 noteLabel = new Gtk.Label({
use_markup: true,
label: _("<u>Note</u>: When you save elements made with eraser in a SVG file,\nthey are colored with background color, transparent if it is disabled.\n(See \"Add a drawing background\" or edit the SVG file afterwards)")
});
noteLabel.set_halign(1);
//let noteLabel2 = new Gtk.Label({ label: _("See notesheet.css") });
noteBox.pack_start(noteLabel, true, true, 4);
//noteBox.pack_start(noteLabel2, false, false, 4);
listBox.add(noteBox);
this.addSeparator(listBox);
let licence = _("<span size=\"small\">This program comes with ABSOLUTELY NO WARRANTY.\nSee the <a href=\"https://www.gnu.org/licenses/old-licenses/gpl-2.0.html\">GNU General Public License, version 2 or later</a> for details.</span>");
let textBox2 = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
let text2 = new Gtk.Label({ wrap: true, justify: 2, use_markup: true,
label: "<small>Version" + " " + Metadata.version +"</small>\n\n" + "<span><a href=\"" + Metadata.url + "\">" + Metadata.url + "</a></span>" + "\n\n" + licence + "\n" });
textBox2.pack_start(text2, false, false, 0);
let creditBox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL });
let leftBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
let rightBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
let leftLabel = new Gtk.Label({ wrap: true, valign: 1, halign: 2, justify: 1, use_markup: true, label: "<small><u>" + _("Credits") + ":</u></small>" });
let rightLabel = new Gtk.Label({ wrap: true, valign: 1, halign: 1, justify: 0, use_markup: true, label: "<small>Abakkk</small>" });
leftBox.pack_start(leftLabel, true, true, 0);
rightBox.pack_start(rightLabel, true, true, 0);
creditBox.pack_start(leftBox, true, true, 5);
creditBox.pack_start(rightBox, true, true, 5);
textBox2.pack_start(creditBox, false, false, 0);
box.add(textBox2);
let 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/
const KeybindingsWidget = new GObject.Class({
Name: 'Keybindings.Widget',
GTypeName: 'KeybindingsWidget',
Extends: Gtk.Box,
_init: function(keybindings, settings) {
this.parent();
this.set_orientation(Gtk.Orientation.VERTICAL);
this._keybindings = keybindings;
this._settings = settings;
this._columns = {
NAME: 0,
ACCEL_NAME: 1,
MODS: 2,
KEY: 3
};
this._store = new Gtk.ListStore();
this._store.set_column_types([
GObject.TYPE_STRING,
GObject.TYPE_STRING,
GObject.TYPE_INT,
GObject.TYPE_INT
]);
this._tree_view = new Gtk.TreeView({
model: this._store,
hexpand: false,
vexpand: false
});
this._tree_view.set_activate_on_single_click(false);
this._tree_view.get_selection().set_mode(Gtk.SelectionMode.SINGLE);
let action_renderer = new Gtk.CellRendererText();
let action_column = new Gtk.TreeViewColumn({
title: "",
expand: true,
});
action_column.pack_start(action_renderer, true);
action_column.add_attribute(action_renderer, 'text', 1);
this._tree_view.append_column(action_column);
let keybinding_renderer = new Gtk.CellRendererAccel({
editable: true,
accel_mode: Gtk.CellRendererAccelMode.GTK,
xalign: 1
});
keybinding_renderer.connect('accel-edited',
Lang.bind(this, function(renderer, iter, key, mods) {
let value = Gtk.accelerator_name(key, mods);
let [success, iterator ] =
this._store.get_iter_from_string(iter);
if(!success) {
printerr("Can't change keybinding");
}
let name = this._store.get_value(iterator, 0);
this._store.set(
iterator,
[this._columns.MODS, this._columns.KEY],
[mods, key]
);
this._settings.set_strv(name, [value]);
})
);
let keybinding_column = new Gtk.TreeViewColumn({
title: "",
});
keybinding_column.pack_end(keybinding_renderer, false);
keybinding_column.add_attribute(
keybinding_renderer,
'accel-mods',
this._columns.MODS
);
keybinding_column.add_attribute(
keybinding_renderer,
'accel-key',
this._columns.KEY
);
this._tree_view.append_column(keybinding_column);
this._tree_view.columns_autosize();
this._tree_view.set_headers_visible(false);
this.add(this._tree_view);
this.keybinding_column = keybinding_column;
this.action_column = action_column;
this._refresh();
},
_refresh: function() {
this._store.clear();
for(let settings_key in this._keybindings) {
if (settings_key.indexOf('-separator-') != -1)
continue;
let [key, mods] = Gtk.accelerator_parse(
this._settings.get_strv(settings_key)[0]
);
let iter = this._store.append();
this._store.set(iter,
[
this._columns.NAME,
this._columns.ACCEL_NAME,
this._columns.MODS,
this._columns.KEY
],
[
settings_key,
_(this._keybindings[settings_key]),
mods,
key
]
);
}
}
});

BIN
schemas/gschemas.compiled Normal file

Binary file not shown.

View File

@ -0,0 +1,175 @@
<?xml version="1.0" encoding="UTF-8"?>
<schemalist gettext-domain="gnome-shell-extensions">
<schema path="/org/gnome/shell/extensions/draw-on-your-screen/" id="org.gnome.shell.extensions.draw-on-your-screen">
<key type="as" name="toggle-drawing">
<default>["&lt;Alt&gt;&lt;Super&gt;d"]</default>
<summary>toggle drawing</summary>
<description>enter or leave drawing mode</description>
</key>
<key type="as" name="erase-drawing">
<default>["&lt;Alt&gt;&lt;Super&gt;e"]</default>
<summary>erase drawing</summary>
<description>erase drawing</description>
</key>
<key type="as" name="undo">
<default>["&lt;Primary&gt;z"]</default>
<summary>undo</summary>
<description>undo</description>
</key>
<key type="as" name="redo">
<default>["&lt;Primary&gt;&lt;Shift&gt;z"]</default>
<summary>redo</summary>
<description>redo</description>
</key>
<key type="as" name="delete-last-element">
<default>["Delete"]</default>
<summary>delete last element</summary>
<description>delete last element</description>
</key>
<key type="as" name="toggle-background">
<default>["&lt;Primary&gt;b"]</default>
<summary>toggle background</summary>
<description>toggle background</description>
</key>
<key type="as" name="toggle-panel-and-dock-visibility">
<default>["&lt;Primary&gt;h"]</default>
<summary>hide or show panel and dock</summary>
<description>hide or show panel and dock</description>
</key>
<key type="as" name="select-ellipse-shape">
<default>["&lt;Primary&gt;e"]</default>
<summary>select cercle</summary>
<description>select a cercle</description>
</key>
<key type="as" name="select-rectangle-shape">
<default>["&lt;Primary&gt;r"]</default>
<summary>select rectangle</summary>
<description>select rectangle</description>
</key>
<key type="as" name="select-line-shape">
<default>["&lt;Primary&gt;l"]</default>
<summary>select line</summary>
<description>select a line</description>
</key>
<key type="as" name="select-text-shape">
<default>["&lt;Primary&gt;t"]</default>
<summary>select text</summary>
<description>select text</description>
</key>
<key type="as" name="select-none-shape">
<default>["&lt;Primary&gt;u"]</default>
<summary>unselect shape (free drawing)</summary>
<description>unselect shape (free drawing)</description>
</key>
<key type="as" name="increment-line-width">
<default>["&lt;Primary&gt;KP_Add"]</default>
<summary>increment the line width</summary>
<description>increment the line width</description>
</key>
<key type="as" name="decrement-line-width">
<default>["&lt;Primary&gt;KP_Subtract"]</default>
<summary>decrement the line width</summary>
<description>decrement the line width</description>
</key>
<key type="as" name="increment-line-width-more">
<default>["&lt;Primary&gt;Page_Up"]</default>
<summary>increment the line width even more</summary>
<description>increment the line width even more</description>
</key>
<key type="as" name="decrement-line-width-more">
<default>["&lt;Primary&gt;Page_Down"]</default>
<summary>decrement the line width even more</summary>
<description>decrement the line width even more</description>
</key>
<key type="as" name="toggle-linejoin">
<default>["&lt;Primary&gt;j"]</default>
<summary>toggle linejoin</summary>
<description>toggle linejoin</description>
</key>
<key type="as" name="toggle-linecap">
<default>["&lt;Primary&gt;k"]</default>
<summary>toggle linecap</summary>
<description>toggle linecap</description>
</key>
<key type="as" name="toggle-dash">
<default>["&lt;Primary&gt;a"]</default>
<summary>toggle dash</summary>
<description>toggle dash</description>
</key>
<key type="as" name="select-color1">
<default><![CDATA[['<Primary>KP_1','<Primary>1']]]></default>
<summary>select color1</summary>
<description>select color1</description>
</key>
<key type="as" name="select-color2">
<default><![CDATA[['<Primary>KP_2','<Primary>2']]]></default>
<summary>select color2</summary>
<description>select color2</description>
</key>
<key type="as" name="select-color3">
<default><![CDATA[['<Primary>KP_3','<Primary>3']]]></default>
<summary>select color3</summary>
<description>select color3</description>
</key>
<key type="as" name="select-color4">
<default><![CDATA[['<Primary>KP_4','<Primary>4']]]></default>
<summary>select color4</summary>
<description>select color4</description>
</key>
<key type="as" name="select-color5">
<default><![CDATA[['<Primary>KP_5','<Primary>5']]]></default>
<summary>select color5</summary>
<description>select color5</description>
</key>
<key type="as" name="select-color6">
<default><![CDATA[['<Primary>KP_6','<Primary>6']]]></default>
<summary>select color6</summary>
<description>select color6</description>
</key>
<key type="as" name="select-color7">
<default><![CDATA[['<Primary>KP_7','<Primary>7']]]></default>
<summary>select color7</summary>
<description>select color7</description>
</key>
<key type="as" name="select-color8">
<default><![CDATA[['<Primary>KP_8','<Primary>8']]]></default>
<summary>select color8</summary>
<description>select color8</description>
</key>
<key type="as" name="select-color9">
<default><![CDATA[['<Primary>KP_9','<Primary>9']]]></default>
<summary>select color9</summary>
<description>select color9</description>
</key>
<key type="as" name="toggle-font-family">
<default>["&lt;Primary&gt;f"]</default>
<summary>toggle font family</summary>
<description>toggle font family</description>
</key>
<key type="as" name="toggle-font-weight">
<default>["&lt;Primary&gt;w"]</default>
<summary>toggle font weight</summary>
<description>toggle font weight</description>
</key>
<key type="as" name="toggle-font-style">
<default>["&lt;Primary&gt;i"]</default>
<summary>toggle font style</summary>
<description>toggle font style</description>
</key>
<key type="as" name="open-stylesheet">
<default>["&lt;Primary&gt;o"]</default>
<summary>open stylesheet</summary>
<description>open stylesheet</description>
</key>
<key type="as" name="save-as-svg">
<default>["&lt;Primary&gt;s"]</default>
<summary>Save drawing as a svg file</summary>
<description>Save drawing as a svg file</description>
</key>
<key type="as" name="toggle-help">
<default>["&lt;Primary&gt;F1"]</default>
<summary>toggle help</summary>
<description>toggle help</description>
</key>
</schema>
</schemalist>

73
stylesheet.css Normal file
View File

@ -0,0 +1,73 @@
/*
* 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
*
*/
.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,
* but the "Ctrl + F1" on-screen-display
*
*/
.draw-on-your-screen-helper {
margin: 0;
spacing: 0.5em;
}
.draw-on-your-screen-helper StBoxLayout {
spacing: 5em;
}
.draw-on-your-screen-helper StBoxLayout StLabel {
text-align: right;
font-weight: normal;
}
.draw-on-your-screen-separator {
min-height: 0.6em;
}