diff --git a/NEWS b/NEWS
index bab0ca3..fd428fd 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,24 @@
+v6.1 - June 2020
+=================
+
+* Fix empty media-keys settings case #28
+* Fix label color in OSD and menu #31
+* Fix OSD width
+* Fix "Hide panel and dock"
+* Small menu on small monitor
+* Start a new text line with the `Enter` key #30
+* IBus inputs #29 #34
+* Non-latin and emoji characters rendered
+* GS keyboard is now available to type text
+* Grid overlay
+* Polygon and polyline shapes
+* Move, resize and mirror tools
+* Fill rule and text alignment style attributes
+* An optional fourth control point for Bézier curves
+* Keybinding to toggle modeless/modal #6 #9 #20 #24 #33
+ (ungrabs keyboard and mouse while drawing remains at the top)
+* Attributes are now persistent through drawing mode toggling #27
+
v6 - March 2020
=================
diff --git a/README.md b/README.md
index c88b33f..f086e4c 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,7 @@ Then save your beautiful work by taking a screenshot.
## Features
* Basic shapes (rectangle, circle, ellipse, line, curve, text, free)
+* Basic transformations (move, rotate, resize, stretch, mirror, inverse)
* Smooth stroke
* Draw over applications
* Keep drawings on desktop background with persistence (notes, children's art ...)
@@ -33,7 +34,13 @@ Then save your beautiful work by taking a screenshot.

+* Duplicate an element:
+
+ Hold the `Shift` key while starting moving.
+
+ 
+
* 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.
+ [Screenshot Tool](https://extensions.gnome.org/extension/1112/screenshot-tool/) is a convenient extension to “create, copy, store and upload screenshots”. In order to select a screenshoot area with your pointer while keeping the drawing in place, you need first to tell DrawOnYourScreen to ungrab the pointer (`Ctrl + Super + Alt + D`).
diff --git a/data/default.css b/data/default.css
index b5ab3ef..4a03e08 100644
--- a/data/default.css
+++ b/data/default.css
@@ -1,18 +1,24 @@
/*
- * 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.
+ * WARNING : user.css may be obsolete after an extension update.
*
* ~/.local/share/drawOnYourScreen/user.css file is automatically generated by activating "Edit style".
- * Delete ~/.local/share/drawOnYourScreen/user.css file to retrieve default drawing style.
+ * Delete ~/.local/share/drawOnYourScreen/user.css file to retrieve the default drawing style.
+ *
+ * Except for the font, you don't need to restart the extension.
+ * Just save this file as ~/.local/share/drawOnYourScreen/user.css and the changes will be applied for your next brushstroke.
+ * Some attributes are modifiable in the user interface.
*
* line-join (no string):
* 0 : miter, 1 : round, 2 : bevel
* line-cap (no string):
* 0 : butt, 1 : round, 2 : square
+ * fill-rule (no string):
+ * 0 : nonzero (winding in Cairo), 1 : evenodd
*
* dash:
- * 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).
+ * By default, it is computed from the line width.
+ * dash-array-on is the length of dashes (put 0.1 to get dots or squares according to line-cap).
+ * dash-array-off is the length of gaps.
*
* square area:
* Drawing in a square area is convenient when using the extension as a vector graphics editor. By default,
@@ -23,9 +29,8 @@
* 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.
+ *
+ * text-align: left or right.
*
*/
@@ -33,22 +38,114 @@
-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-fill-rule: 0;
+ /*-drawing-dash-array-on: 5px;*/
+ /*-drawing-dash-array-off: 15px;*/
+ /*-drawing-dash-offset: 0px;*/
+ -drawing-background-color: #2e2e2e;
+ -grid-overlay-gap: 10px;
+ -grid-overlay-line-width: 0.4px;
+ -grid-overlay-interline-width: 0.2px;
+ -grid-overlay-color: Gray;
+ /*-drawing-square-area-width: 512px;*/
+ /*-drawing-square-area-height: 512px;*/
+ font-family: Cantarell;
+ font-weight: normal;
+ font-style: normal;
+ text-align: left;
+}
+
+/* Palette */
+.draw-on-your-screen {
-drawing-color1: HotPink;
-drawing-color2: Cyan;
-drawing-color3: yellow;
-drawing-color4: Orangered;
-drawing-color5: Chartreuse;
-drawing-color6: DarkViolet;
- -drawing-color7: #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;
+ -drawing-color7: White;
+ -drawing-color8: Gray;
+ -drawing-color9: Black;
}
+
+/*
+Example of alternative palettes from GNOME HIG Colors.
+https://developer.gnome.org/hig/stable/icon-design.html
+
+The last uncommented palette wins.
+*/
+
+/* lighter */
+/*
+.draw-on-your-screen {
+ -drawing-color1: rgb(153, 193, 241);
+ -drawing-color2: rgb(143, 240, 164);
+ -drawing-color3: rgb(249, 240, 107);
+ -drawing-color4: rgb(255, 190, 111);
+ -drawing-color5: rgb(246, 97, 81);
+ -drawing-color6: rgb(220, 138, 221);
+ -drawing-color7: rgb(205, 171, 143);
+ -drawing-color8: rgb(255, 255, 255);
+ -drawing-color9: rgb(119, 118, 123);
+}
+*/
+
+/* light */
+/*
+.draw-on-your-screen {
+ -drawing-color1: rgb( 98, 160, 241);
+ -drawing-color2: rgb( 87, 227, 137);
+ -drawing-color3: rgb(248, 228, 92);
+ -drawing-color4: rgb(255, 163, 72);
+ -drawing-color5: rgb(237, 51, 59);
+ -drawing-color6: rgb(192, 97, 203);
+ -drawing-color7: rgb(181, 131, 90);
+ -drawing-color8: rgb(246, 245, 244);
+ -drawing-color9: rgb( 94, 92, 100);
+}
+*/
+
+/* normal */
+/*
+.draw-on-your-screen {
+ -drawing-color1: rgb( 53, 132, 228);
+ -drawing-color2: rgb( 51, 209, 122);
+ -drawing-color3: rgb(246, 211, 45);
+ -drawing-color4: rgb(255, 120, 0);
+ -drawing-color5: rgb(224, 27, 36);
+ -drawing-color6: rgb(145, 65, 172);
+ -drawing-color7: rgb(152, 106, 68);
+ -drawing-color8: rgb(222, 221, 218);
+ -drawing-color9: rgb( 61, 56, 70);
+}
+*/
+
+/* dark */
+/*
+.draw-on-your-screen {
+ -drawing-color1: rgb( 28, 113, 216);
+ -drawing-color2: rgb( 46, 194, 126);
+ -drawing-color3: rgb(245, 194, 17);
+ -drawing-color4: rgb(230, 97, 0);
+ -drawing-color5: rgb(192, 28, 40);
+ -drawing-color6: rgb(129, 61, 156);
+ -drawing-color7: rgb(134, 94, 60);
+ -drawing-color8: rgb(192, 191, 188);
+ -drawing-color9: rgb( 36, 31, 49);
+}
+*/
+
+/* darker */
+/*
+.draw-on-your-screen {
+ -drawing-color1: rgb( 26, 095, 180);
+ -drawing-color2: rgb( 38, 162, 105);
+ -drawing-color3: rgb(229, 165, 10);
+ -drawing-color4: rgb(198, 70, 0);
+ -drawing-color5: rgb(165, 29, 45);
+ -drawing-color6: rgb( 97, 53, 131);
+ -drawing-color7: rgb( 99, 69, 44);
+ -drawing-color8: rgb(154, 153, 150);
+ -drawing-color9: rgb( 0, 0, 0);
+}
+*/
diff --git a/data/icons/color-symbolic.svg b/data/icons/color-symbolic.svg
new file mode 100644
index 0000000..39ae95d
--- /dev/null
+++ b/data/icons/color-symbolic.svg
@@ -0,0 +1,18 @@
+
+
+
diff --git a/data/icons/fillrule-evenodd-symbolic.svg b/data/icons/fillrule-evenodd-symbolic.svg
new file mode 100644
index 0000000..a74de4c
--- /dev/null
+++ b/data/icons/fillrule-evenodd-symbolic.svg
@@ -0,0 +1,3 @@
+
diff --git a/data/icons/fillrule-nonzero-symbolic.svg b/data/icons/fillrule-nonzero-symbolic.svg
new file mode 100644
index 0000000..a3b9b2b
--- /dev/null
+++ b/data/icons/fillrule-nonzero-symbolic.svg
@@ -0,0 +1,3 @@
+
diff --git a/draw.js b/draw.js
index cb542ac..087f504 100644
--- a/draw.js
+++ b/draw.js
@@ -28,8 +28,8 @@ const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
-const Mainloop = imports.mainloop;
-const Shell = imports.gi.Shell;
+const Pango = imports.gi.Pango;
+const PangoCairo = imports.gi.PangoCairo;
const St = imports.gi.St;
const BoxPointer = imports.ui.boxpointer;
@@ -48,22 +48,44 @@ const Prefs = Me.imports.prefs;
const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext;
const GS_VERSION = Config.PACKAGE_VERSION;
+const CAIRO_DEBUG_EXTENDS = false;
+const SVG_DEBUG_EXTENDS = false;
+const SVG_DEBUG_SUPERPOSES_CAIRO = false;
+const TEXT_CURSOR_TIME = 600; // ms
-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();
+const ICON_DIR = Me.dir.get_child('data').get_child('icons');
+const COLOR_ICON_PATH = ICON_DIR.get_child('color-symbolic.svg').get_path();
+const FILL_ICON_PATH = ICON_DIR.get_child('fill-symbolic.svg').get_path();
+const STROKE_ICON_PATH = ICON_DIR.get_child('stroke-symbolic.svg').get_path();
+const LINEJOIN_ICON_PATH = ICON_DIR.get_child('linejoin-symbolic.svg').get_path();
+const LINECAP_ICON_PATH = ICON_DIR.get_child('linecap-symbolic.svg').get_path();
+const FILLRULE_NONZERO_ICON_PATH = ICON_DIR.get_child('fillrule-nonzero-symbolic.svg').get_path();
+const FILLRULE_EVENODD_ICON_PATH = ICON_DIR.get_child('fillrule-evenodd-symbolic.svg').get_path();
+const DASHED_LINE_ICON_PATH = ICON_DIR.get_child('dashed-line-symbolic.svg').get_path();
+const FULL_LINE_ICON_PATH = ICON_DIR.get_child('full-line-symbolic.svg').get_path();
-var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4 };
-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 reverseEnumeration = function(obj) {
+ let reversed = {};
+ Object.keys(obj).forEach(key => {
+ reversed[obj[key]] = key.slice(0,1) + key.slice(1).toLowerCase().replace('_', '-');
+ });
+ return reversed;
+};
+
+const Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6 };
+const Manipulations = { MOVE: 100, RESIZE: 101, MIRROR: 102 };
+var Tools = Object.assign({}, Shapes, Manipulations);
+const Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, STRETCH: 3, REFLECTION: 4, INVERSION: 5 };
+const ToolNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline", 100: "Move", 101: "Resize", 102: "Mirror" };
+const LineCapNames = Object.assign(reverseEnumeration(Cairo.LineCap), { 2: 'Square' });
+const LineJoinNames = reverseEnumeration(Cairo.LineJoin);
+const FillRuleNames = { 0: 'Nonzero', 1: 'Evenodd' };
+const FontGenericNames = { 0: 'Theme', 1: 'Sans-Serif', 2: 'Serif', 3: 'Monospace', 4: 'Cursive', 5: 'Fantasy' };
+const FontWeightNames = Object.assign(reverseEnumeration(Pango.Weight), { 200: "Ultra-light", 350: "Semi-light", 600: "Semi-bold", 800: "Ultra-bold" });
+delete FontWeightNames[Pango.Weight.ULTRAHEAVY];
+const FontStyleNames = reverseEnumeration(Pango.Style);
+const FontStretchNames = reverseEnumeration(Pango.Stretch);
+const FontVariantNames = reverseEnumeration(Pango.Variant);
const getDateString = function() {
let date = GLib.DateTime.new_now_local();
@@ -108,12 +130,16 @@ const getJsonFiles = function() {
var DrawingArea = new Lang.Class({
Name: 'DrawOnYourScreenDrawingArea',
Extends: St.DrawingArea,
- Signals: { 'show-osd': { param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE] },
- 'stop-drawing': {} },
+ Signals: { 'show-osd': { param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE, GObject.TYPE_BOOLEAN] },
+ 'update-action-mode': {},
+ 'leave-drawing-mode': {} },
_init: function(params, monitor, helper, loadPersistent) {
this.parent({ style_class: 'draw-on-your-screen', name: params.name});
+ this.connect('destroy', this._onDestroy.bind(this));
+ this.reactiveHandler = this.connect('notify::reactive', this._onReactiveChanged.bind(this));
+
this.settings = Convenience.getSettings();
this.monitor = monitor;
this.helper = helper;
@@ -121,13 +147,17 @@ var DrawingArea = new Lang.Class({
this.elements = [];
this.undoneElements = [];
this.currentElement = null;
- this.currentShape = Shapes.NONE;
+ this.currentTool = Shapes.NONE;
+ this.currentFontGeneric = 0;
this.isSquareArea = false;
+ this.hasGrid = false;
this.hasBackground = false;
this.textHasCursor = false;
this.dashedLine = false;
this.fill = false;
this.colors = [Clutter.Color.new(0, 0, 0, 255)];
+ this.newThemeAttributes = {};
+ this.oldThemeAttributes = {};
if (loadPersistent)
this._loadPersistent();
@@ -139,6 +169,53 @@ var DrawingArea = new Lang.Class({
return this._menu;
},
+ closeMenu: function() {
+ if (this._menu)
+ this._menu.close();
+ },
+
+ get isWriting() {
+ return this.textEntry ? true : false;
+ },
+
+ get currentTool() {
+ return this._currentTool;
+ },
+
+ set currentTool(tool) {
+ this._currentTool = tool;
+ if (this.hasManipulationTool)
+ this._startElementGrabber();
+ else
+ this._stopElementGrabber();
+ },
+
+ get hasManipulationTool() {
+ // No Object.values method in GS 3.24.
+ return Object.keys(Manipulations).map(key => Manipulations[key]).indexOf(this.currentTool) != -1;
+ },
+
+ // Boolean wrapper for switch menu item.
+ get currentEvenodd() {
+ return this.currentFillRule == Cairo.FillRule.EVEN_ODD;
+ },
+
+ set currentEvenodd(evenodd) {
+ this.currentFillRule = evenodd ? Cairo.FillRule.EVEN_ODD : Cairo.FillRule.WINDING;
+ },
+
+ vfunc_repaint: function() {
+ let cr = this.get_context();
+
+ try {
+ this._repaint(cr);
+ } catch(e) {
+ logError(e, "An error occured while painting");
+ }
+
+ cr.$dispose();
+ },
+
_redisplay: function() {
// force area to emit 'repaint'
this.queue_repaint();
@@ -150,18 +227,26 @@ var DrawingArea = new Lang.Class({
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();
+ this.newThemeAttributes.ThemeFontFamily = font.get_family();
+ try { this.newThemeAttributes.FontWeight = font.get_weight(); } catch(e) { this.newThemeAttributes.FontWeight = Pango.Weight.NORMAL; }
+ this.newThemeAttributes.FontStyle = font.get_style();
+ this.newThemeAttributes.FontStretch = font.get_stretch();
+ this.newThemeAttributes.FontVariant = font.get_variant();
+ this.newThemeAttributes.TextRightAligned = themeNode.get_text_align() == St.TextAlign.RIGHT;
+ this.newThemeAttributes.LineWidth = themeNode.get_length('-drawing-line-width');
+ this.newThemeAttributes.LineJoin = themeNode.get_double('-drawing-line-join');
+ this.newThemeAttributes.LineCap = themeNode.get_double('-drawing-line-cap');
+ this.newThemeAttributes.FillRule = themeNode.get_double('-drawing-fill-rule');
+ this.dashArray = [Math.abs(themeNode.get_length('-drawing-dash-array-on')), Math.abs(themeNode.get_length('-drawing-dash-array-off'))];
+ this.dashOffset = themeNode.get_length('-drawing-dash-offset');
+ this.gridGap = themeNode.get_length('-grid-overlay-gap');
+ this.gridLineWidth = themeNode.get_length('-grid-overlay-line-width');
+ this.gridInterlineWidth = themeNode.get_length('-grid-overlay-interline-width');
+ this.gridColor = themeNode.get_color('-grid-overlay-color');
this.squareAreaWidth = themeNode.get_length('-drawing-square-area-width');
this.squareAreaHeight = themeNode.get_length('-drawing-square-area-height');
+ this.activeBackgroundColor = themeNode.get_color('-drawing-background-color');
} catch(e) {
logError(e);
}
@@ -169,45 +254,81 @@ var DrawingArea = new Lang.Class({
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);
+ this.currentColor = this.currentColor || this.colors[1];
+ // SVG does not support 'Ultra-heavy' weight (1000)
+ this.newThemeAttributes.FontWeight = Math.min(this.newThemeAttributes.FontWeight, 900);
+ this.newThemeAttributes.LineWidth = (this.newThemeAttributes.LineWidth > 0) ? this.newThemeAttributes.LineWidth : 3;
+ this.newThemeAttributes.LineJoin = ([0, 1, 2].indexOf(this.newThemeAttributes.LineJoin) != -1) ? this.newThemeAttributes.LineJoin : Cairo.LineJoin.ROUND;
+ this.newThemeAttributes.LineCap = ([0, 1, 2].indexOf(this.newThemeAttributes.LineCap) != -1) ? this.newThemeAttributes.LineCap : Cairo.LineCap.ROUND;
+ this.newThemeAttributes.FillRule = ([0, 1].indexOf(this.newThemeAttributes.FillRule) != -1) ? this.newThemeAttributes.FillRule : Cairo.FillRule.WINDING;
+ for (let attributeName in this.newThemeAttributes) {
+ if (this.newThemeAttributes[attributeName] != this.oldThemeAttributes[attributeName]) {
+ this.oldThemeAttributes[attributeName] = this.newThemeAttributes[attributeName];
+ this[`current${attributeName}`] = this.newThemeAttributes[attributeName];
+ }
+ }
+ this.gridGap = this.gridGap && this.gridGap >= 1 ? this.gridGap : 10;
+ this.gridLineWidth = this.gridLineWidth || 0.4;
+ this.gridInterlineWidth = this.gridInterlineWidth || 0.2;
+ this.gridColor = this.gridColor && this.gridColor.alpha ? this.gridColor : Clutter.Color.new(127, 127, 127, 255);
},
- vfunc_repaint: function() {
- let cr = this.get_context();
+ _repaint: function(cr) {
+ if (CAIRO_DEBUG_EXTENDS) {
+ cr.scale(0.5, 0.5);
+ cr.translate(this.monitor.width, this.monitor.height);
+ }
for (let i = 0; i < this.elements.length; i++) {
- let isStraightLine = this.elements[i].shape == Shapes.LINE &&
- (this.elements[i].points.length < 3 || this.elements[i].points[2] == this.elements[i].points[1] || this.elements[i].points[2] == this.elements[i].points[0]);
+ cr.save();
- if (this.elements[i].fill && !isStraightLine) {
- // first paint stroke
- this.elements[i].buildCairo(cr, false);
+ this.elements[i].buildCairo(cr, { showTextRectangle: this.grabbedElement && this.grabbedElement == this.elements[i],
+ drawTextRectangle: this.grabPoint ? true : false });
+
+ if (this.grabPoint)
+ this._searchElementToGrab(cr, this.elements[i]);
+
+ if (this.elements[i].fill && !this.elements[i].isStraightLine) {
+ cr.fillPreserve();
if (this.elements[i].shape == Shapes.NONE || this.elements[i].shape == Shapes.LINE)
cr.closePath();
- cr.stroke();
- // secondly paint fill
- this.elements[i].buildCairo(cr, false);
- cr.fill();
- } else {
- this.elements[i].buildCairo(cr, false);
- cr.stroke();
- }
+ }
+
+ cr.stroke();
+ cr.restore();
}
if (this.currentElement) {
- this.currentElement.buildCairo(cr, this.textHasCursor);
+ cr.save();
+ this.currentElement.buildCairo(cr, { showTextCursor: this.textHasCursor,
+ showTextRectangle: this.currentElement.shape == Shapes.TEXT && !this.isWriting,
+ dummyStroke: this.currentElement.fill && this.currentElement.line.lineWidth == 0 });
+
cr.stroke();
+ cr.restore();
}
- cr.$dispose();
+ if (this.reactive && this.hasGrid && this.gridGap && this.gridGap >= 1) {
+ cr.save();
+ Clutter.cairo_set_source_color(cr, this.gridColor);
+
+ let [gridX, gridY] = [this.gridGap, this.gridGap];
+ while (gridX < this.monitor.width) {
+ cr.setLineWidth((gridX / this.gridGap) % 5 ? this.gridInterlineWidth : this.gridLineWidth);
+ cr.moveTo(gridX, 0);
+ cr.lineTo(gridX, this.monitor.height);
+ gridX += this.gridGap;
+ cr.stroke();
+ }
+ while (gridY < this.monitor.height) {
+ cr.setLineWidth((gridY / this.gridGap) % 5 ? this.gridInterlineWidth : this.gridLineWidth);
+ cr.moveTo(0, gridY);
+ cr.lineTo(this.monitor.width, gridY);
+ gridY += this.gridGap;
+ cr.stroke();
+ }
+ cr.restore();
+ }
},
_onButtonPressed: function(actor, event) {
@@ -216,21 +337,26 @@ var DrawingArea = new Lang.Class({
let button = event.get_button();
let [x, y] = event.get_coords();
+ let controlPressed = event.has_control_modifier();
let shiftPressed = event.has_shift_modifier();
- // stop writing
- if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.WRITING ) {
+ if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.isWriting)
+ // finish writing
this._stopWriting();
- }
- // hide helper
if (this.helper.visible) {
- this.helper.hideHelp();
+ // hide helper
+ this.toggleHelp();
return Clutter.EVENT_STOP;
}
if (button == 1) {
- this._startDrawing(x, y, shiftPressed);
+ if (this.hasManipulationTool) {
+ if (this.grabbedElement)
+ this._startTransforming(x, y, controlPressed, shiftPressed);
+ } else {
+ this._startDrawing(x, y, shiftPressed);
+ }
return Clutter.EVENT_STOP;
} else if (button == 2) {
this.toggleFill();
@@ -246,7 +372,7 @@ var DrawingArea = new Lang.Class({
_onKeyboardPopupMenu: function() {
this._stopDrawing();
if (this.helper.visible)
- this.helper.hideHelp();
+ this.toggleHelp();
this.menu.popup();
return Clutter.EVENT_STOP;
},
@@ -266,35 +392,32 @@ var DrawingArea = new Lang.Class({
},
_onKeyPressed: function(actor, event) {
- if (event.get_key_symbol() == Clutter.KEY_Escape) {
- this.emit('stop-drawing');
+ if (this.currentElement && this.currentElement.shape == Shapes.LINE) {
+ if (event.get_key_symbol() == Clutter.KEY_Return ||
+ event.get_key_symbol() == Clutter.KEY_KP_Enter ||
+ event.get_key_symbol() == Clutter.KEY_Control_L) {
+ if (this.currentElement.points.length == 2)
+ this.emit('show-osd', null, _("Press %s to get\na fourth control point")
+ .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true);
+ this.currentElement.addPoint();
+ this.updatePointerCursor(true);
+ this._redisplay();
+ return Clutter.EVENT_STOP;
+ } else {
+ return Clutter.EVENT_PROPAGATE;
+ }
+
+ } else if (this.currentElement &&
+ (this.currentElement.shape == Shapes.POLYGON || this.currentElement.shape == Shapes.POLYLINE) &&
+ (event.get_key_symbol() == Clutter.KEY_Return || event.get_key_symbol() == Clutter.KEY_KP_Enter)) {
+ this.currentElement.addPoint();
return Clutter.EVENT_STOP;
- } 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._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._updateTextCursorTimeout();
- 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 if (event.has_control_modifier()){
- // it's a shortcut, do not write text
- return Clutter.EVENT_PROPAGATE;
- } else {
- let unicode = event.get_key_unicode();
- this.currentElement.text += unicode;
- this._updateTextCursorTimeout();
- }
- this._redisplay();
+ } else if (event.get_key_symbol() == Clutter.KEY_Escape) {
+ if (this.helper.visible)
+ this.toggleHelp();
+ else
+ this.emit('leave-drawing-mode');
return Clutter.EVENT_STOP;
} else {
@@ -315,6 +438,148 @@ var DrawingArea = new Lang.Class({
return Clutter.EVENT_STOP;
},
+ _searchElementToGrab: function(cr, element) {
+ if (element.getContainsPoint(cr, this.grabPoint[0], this.grabPoint[1]))
+ this.grabbedElement = element;
+ else if (this.grabbedElement == element)
+ this.grabbedElement = null;
+
+ if (element == this.elements[this.elements.length - 1])
+ // All elements have been tested, the winner is the last.
+ this.updatePointerCursor();
+ },
+
+ _startElementGrabber: function() {
+ if (this.elementGrabberHandler)
+ return;
+
+ this.elementGrabberHandler = this.connect('motion-event', (actor, event) => {
+ if (this.motionHandler || this.grabbedElementLocked) {
+ this.grabPoint = null;
+ return;
+ }
+
+ // Reduce computing without notable effect.
+ if (Math.random() <= 0.75)
+ return;
+
+ let coords = event.get_coords();
+ let [s, x, y] = this.transform_stage_point(coords[0], coords[1]);
+ if (!s)
+ return;
+
+ this.grabPoint = [x, y];
+ this.grabbedElement = null;
+ // this._redisplay calls this._searchElementToGrab.
+ this._redisplay();
+ });
+ },
+
+ _stopElementGrabber: function() {
+ if (this.elementGrabberHandler) {
+ this.disconnect(this.elementGrabberHandler);
+ this.grabPoint = null;
+ this.elementGrabberHandler = null;
+ }
+ },
+
+ _startTransforming: function(stageX, stageY, controlPressed, duplicate) {
+ let [success, startX, startY] = this.transform_stage_point(stageX, stageY);
+
+ if (!success)
+ return;
+
+ if (this.currentTool == Manipulations.MIRROR) {
+ this.grabbedElementLocked = !this.grabbedElementLocked;
+ if (this.grabbedElementLocked) {
+ this.updatePointerCursor();
+ let label = controlPressed ? _("Mark a point of symmetry") : _("Draw a line of symmetry");
+ this.emit('show-osd', null, label, "", -1, true);
+ return;
+ }
+ }
+
+ this.grabPoint = null;
+
+ this.buttonReleasedHandler = this.connect('button-release-event', (actor, event) => {
+ this._stopTransforming();
+ });
+
+ if (duplicate) {
+ // deep cloning
+ let copy = new DrawingElement(JSON.parse(JSON.stringify(this.grabbedElement)));
+ this.elements.push(copy);
+ this.grabbedElement = copy;
+ }
+
+ if (this.currentTool == Manipulations.MOVE)
+ this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.ROTATION : Transformations.TRANSLATION);
+ else if (this.currentTool == Manipulations.RESIZE)
+ this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.STRETCH : Transformations.SCALE_PRESERVE);
+ else if (this.currentTool == Manipulations.MIRROR) {
+ this.grabbedElement.startTransformation(startX, startY, controlPressed ? Transformations.INVERSION : Transformations.REFLECTION);
+ this._redisplay();
+ }
+
+
+ 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)
+ return;
+ let controlPressed = event.has_control_modifier();
+ this._updateTransforming(x, y, controlPressed);
+ });
+ },
+
+ _updateTransforming: function(x, y, controlPressed) {
+ if (controlPressed && this.grabbedElement.lastTransformation.type == Transformations.TRANSLATION) {
+ this.grabbedElement.stopTransformation();
+ this.grabbedElement.startTransformation(x, y, Transformations.ROTATION);
+ } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.ROTATION) {
+ this.grabbedElement.stopTransformation();
+ this.grabbedElement.startTransformation(x, y, Transformations.TRANSLATION);
+ }
+
+ if (controlPressed && this.grabbedElement.lastTransformation.type == Transformations.SCALE_PRESERVE) {
+ this.grabbedElement.stopTransformation();
+ this.grabbedElement.startTransformation(x, y, Transformations.STRETCH);
+ } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.STRETCH) {
+ this.grabbedElement.stopTransformation();
+ this.grabbedElement.startTransformation(x, y, Transformations.SCALE_PRESERVE);
+ }
+
+ if (controlPressed && this.grabbedElement.lastTransformation.type == Transformations.REFLECTION) {
+ this.grabbedElement.transformations.pop();
+ this.grabbedElement.startTransformation(x, y, Transformations.INVERSION);
+ } else if (!controlPressed && this.grabbedElement.lastTransformation.type == Transformations.INVERSION) {
+ this.grabbedElement.transformations.pop();
+ this.grabbedElement.startTransformation(x, y, Transformations.REFLECTION);
+ }
+
+ this.grabbedElement.updateTransformation(x, y);
+ this._redisplay();
+ },
+
+ _stopTransforming: function() {
+ if (this.motionHandler) {
+ this.disconnect(this.motionHandler);
+ this.motionHandler = null;
+ }
+ if (this.buttonReleasedHandler) {
+ this.disconnect(this.buttonReleasedHandler);
+ this.buttonReleasedHandler = null;
+ }
+
+ this.grabbedElement.stopTransformation();
+ this.grabbedElement = null;
+ this.grabbedElementLocked = false;
+ this._redisplay();
+ },
+
_startDrawing: function(stageX, stageY, eraser) {
let [success, startX, startY] = this.transform_stage_point(stageX, stageY);
@@ -326,26 +591,35 @@ var DrawingArea = new Lang.Class({
});
this.currentElement = new DrawingElement ({
- shape: this.currentShape,
+ shape: this.currentTool,
color: this.currentColor.to_string(),
line: { lineWidth: this.currentLineWidth, lineJoin: this.currentLineJoin, lineCap: this.currentLineCap },
- dash: { array: this.dashedLine ? this.dashArray : [0, 0] , offset: this.dashedLine ? this.dashOffset : 0 },
+ dash: { active: this.dashedLine, array: this.dashedLine ? [this.dashArray[0] || this.currentLineWidth, this.dashArray[1] || this.currentLineWidth * 3] : [0, 0] , offset: this.dashOffset },
fill: this.fill,
+ fillRule: this.currentFillRule,
eraser: eraser,
transform: { active: false, center: [0, 0], angle: 0, startAngle: 0, ratio: 1 },
- text: '',
- font: { family: (this.currentFontFamilyId == 0 ? this.fontFamily : FontFamilyNames[this.currentFontFamilyId]), weight: this.currentFontWeight, style: this.currentFontStyle },
- points: [[startX, startY]]
+ points: []
});
- if (this.currentShape == Shapes.TEXT) {
- this.currentElement.line = { lineWidth: 1, lineJoin: 0, lineCap: 0 };
- this.currentElement.dash = { array: [1, 1] , offset: 0 };
+ if (this.currentTool == Shapes.TEXT) {
this.currentElement.fill = false;
+ this.currentElement.font = {
+ family: (this.currentFontGeneric == 0 ? this.currentThemeFontFamily : FontGenericNames[this.currentFontGeneric]),
+ weight: this.currentFontWeight,
+ style: this.currentFontStyle,
+ stretch: this.currentFontStretch,
+ variant: this.currentFontVariant };
this.currentElement.text = _("Text");
- this.currentElement.state = TextState.DRAWING;
+ this.currentElement.textRightAligned = this.currentTextRightAligned;
}
+ this.currentElement.startDrawing(startX, startY);
+
+ if (this.currentTool == Shapes.POLYGON || this.currentTool == Shapes.POLYLINE)
+ this.emit('show-osd', null, _("Press %s to mark vertices")
+ .format(Gtk.accelerator_get_label(Clutter.KEY_Return, 0)), "", -1, true);
+
this.motionHandler = this.connect('motion-event', (actor, event) => {
if (this.spaceKeyPressed)
return;
@@ -359,6 +633,16 @@ var DrawingArea = new Lang.Class({
});
},
+ _updateDrawing: function(x, y, controlPressed) {
+ if (!this.currentElement)
+ return;
+
+ this.currentElement.updateDrawing(x, y, controlPressed);
+
+ this._redisplay();
+ this.updatePointerCursor(controlPressed);
+ },
+
_stopDrawing: function() {
if (this.motionHandler) {
this.disconnect(this.motionHandler);
@@ -369,20 +653,16 @@ var DrawingArea = new Lang.Class({
this.buttonReleasedHandler = null;
}
- // skip when the size is too small to be visible (3px) (except for free drawing)
- if (this.currentElement && this.currentElement.points.length >= 2 &&
- (this.currentShape == Shapes.NONE ||
- Math.hypot(this.currentElement.points[1][0] - this.currentElement.points[0][0], this.currentElement.points[1][1] - this.currentElement.points[0][1]) > 3)) {
-
- // start writing
- if (this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.DRAWING) {
- this.currentElement.state = TextState.WRITING;
- this.currentElement.text = '';
- this.emit('show-osd', null, _("Type your text\nand press Enter"), -1);
- this._updateTextCursorTimeout();
- this.textHasCursor = true;
- this._redisplay();
- this.updatePointerCursor();
+ // skip when a polygon has not at least 3 points
+ if (this.currentElement && this.currentElement.shape == Shapes.POLYGON && this.currentElement.points.length < 3)
+ this.currentElement = null;
+
+ if (this.currentElement)
+ this.currentElement.stopDrawing();
+
+ if (this.currentElement && this.currentElement.points.length >= 2) {
+ if (this.currentElement.shape == Shapes.TEXT && !this.isWriting) {
+ this._startWriting();
return;
}
@@ -394,29 +674,84 @@ var DrawingArea = new Lang.Class({
this.updatePointerCursor();
},
- _updateDrawing: function(x, y, controlPressed) {
- if (!this.currentElement)
- return;
- if (this.currentElement.shape == Shapes.NONE)
- 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))
- this.currentElement.transformEllipse(x, y);
- else if (this.currentElement.shape == Shapes.LINE && (controlPressed || this.currentElement.transform.active))
- this.currentElement.transformLine(x, y);
- else
- this.currentElement.points[1] = [x, y];
-
+ _startWriting: function() {
+ this.currentElement.text = '';
+ this.currentElement.cursorPosition = 0;
+ this.emit('show-osd', null, _("Type your text and press %s")
+ .format(Gtk.accelerator_get_label(Clutter.KEY_Escape, 0)), "", -1, true);
+ this._updateTextCursorTimeout();
+ this.textHasCursor = true;
this._redisplay();
- this.updatePointerCursor(controlPressed);
+
+ this.textEntry = new St.Entry({ visible: false });
+ this.get_parent().add_child(this.textEntry);
+ this.textEntry.grab_key_focus();
+ this.updateActionMode();
+ this.updatePointerCursor();
+
+ this.textEntry.clutterText.connect('activate', (clutterText) => {
+ let startNewLine = true;
+ this._stopWriting(startNewLine);
+ clutterText.text = "";
+ });
+
+ this.textEntry.clutterText.connect('text-changed', (clutterText) => {
+ GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
+ this.currentElement.text = clutterText.text;
+ this.currentElement.cursorPosition = clutterText.cursorPosition;
+ this._updateTextCursorTimeout();
+ this._redisplay();
+ });
+ });
+
+ this.textEntry.clutterText.connect('key-press-event', (clutterText, event) => {
+ if (event.get_key_symbol() == Clutter.KEY_Escape) {
+ this._stopWriting();
+ return Clutter.EVENT_STOP;
+ }
+
+ // 'cursor-changed' signal is not emitted if the text entry is not visible.
+ // So key events related to the cursor must be listened.
+ if (event.get_key_symbol() == Clutter.KEY_Left || event.get_key_symbol() == Clutter.KEY_Right ||
+ event.get_key_symbol() == Clutter.KEY_Home || event.get_key_symbol() == Clutter.KEY_End) {
+ GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
+ this.currentElement.cursorPosition = clutterText.cursorPosition;
+ this._updateTextCursorTimeout();
+ this.textHasCursor = true;
+ this._redisplay();
+ });
+ }
+
+ return Clutter.EVENT_PROPAGATE;
+ });
},
- _stopWriting: function() {
+ _stopWriting: function(startNewLine) {
if (this.currentElement.text.length > 0)
this.elements.push(this.currentElement);
- this.currentElement = null;
- this._stopTextCursorTimeout();
+
+ if (startNewLine && this.currentElement.points.length == 2) {
+ this.currentElement.lineIndex = this.currentElement.lineIndex || 0;
+ // copy object, the original keep existing in this.elements
+ this.currentElement = Object.create(this.currentElement);
+ this.currentElement.lineIndex ++;
+ let height = Math.abs(this.currentElement.points[1][1] - this.currentElement.points[0][1]);
+ // define a new 'points' array, the original keep existing in this.elements
+ this.currentElement.points = [
+ [this.currentElement.points[0][0], this.currentElement.points[0][1] + height],
+ [this.currentElement.points[1][0], this.currentElement.points[1][1] + height]
+ ];
+ this.currentElement.text = "";
+ } else {
+ this.currentElement = null;
+ this._stopTextCursorTimeout();
+ this.textEntry.destroy();
+ delete this.textEntry;
+ this.grab_key_focus();
+ this.updateActionMode();
+ this.updatePointerCursor();
+ }
+
this._redisplay();
},
@@ -428,15 +763,26 @@ var DrawingArea = new Lang.Class({
},
updatePointerCursor: function(controlPressed) {
- if (!this.currentElement || (this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.WRITING))
- this.setPointerCursor(this.currentShape == Shapes.NONE ? 'POINTING_HAND' : 'CROSSHAIR');
+ if (this.currentTool == Manipulations.MIRROR && this.grabbedElementLocked)
+ this.setPointerCursor('CROSSHAIR');
+ else if (this.hasManipulationTool)
+ this.setPointerCursor(this.grabbedElement ? 'MOVE_OR_RESIZE_WINDOW' : 'DEFAULT');
+ else if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.isWriting)
+ this.setPointerCursor('IBEAM');
+ else if (!this.currentElement)
+ this.setPointerCursor(this.currentTool == Shapes.NONE ? 'POINTING_HAND' : 'CROSSHAIR');
else if (this.currentElement.shape != Shapes.NONE && controlPressed)
this.setPointerCursor('MOVE_OR_RESIZE_WINDOW');
},
+ initPointerCursor: function() {
+ this.currentPointerCursorName = null;
+ this.updatePointerCursor();
+ },
+
_stopTextCursorTimeout: function() {
if (this.textCursorTimeoutId) {
- Mainloop.source_remove(this.textCursorTimeoutId);
+ GLib.source_remove(this.textCursorTimeoutId);
this.textCursorTimeoutId = null;
}
this.textHasCursor = false;
@@ -444,7 +790,7 @@ var DrawingArea = new Lang.Class({
_updateTextCursorTimeout: function() {
this._stopTextCursorTimeout();
- this.textCursorTimeoutId = Mainloop.timeout_add(600, () => {
+ this.textCursorTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, TEXT_CURSOR_TIME, () => {
this.textHasCursor = !this.textHasCursor;
this._redisplay();
return GLib.SOURCE_CONTINUE;
@@ -452,9 +798,9 @@ var DrawingArea = new Lang.Class({
},
erase: function() {
+ this.deleteLastElement();
this.elements = [];
this.undoneElements = [];
- this.currentElement = null;
this._redisplay();
},
@@ -468,8 +814,9 @@ var DrawingArea = new Lang.Class({
this.disconnect(this.buttonReleasedHandler);
this.buttonReleasedHandler = null;
}
+ if (this.isWriting)
+ this._stopWriting();
this.currentElement = null;
- this._stopTextCursorTimeout();
} else {
this.elements.pop();
}
@@ -500,6 +847,11 @@ var DrawingArea = new Lang.Class({
this.get_parent().set_background_color(this.hasBackground ? this.activeBackgroundColor : null);
},
+ toggleGrid: function() {
+ this.hasGrid = !this.hasGrid;
+ this._redisplay();
+ },
+
toggleSquareArea: function() {
this.isSquareArea = !this.isSquareArea;
if (this.isSquareArea) {
@@ -525,73 +877,118 @@ var DrawingArea = new Lang.Class({
this.currentElement.color = this.currentColor.to_string();
this._redisplay();
}
- this.emit('show-osd', null, `${this.currentColor.to_string()}`, -1);
+ // Foreground color markup is not displayed since 3.36, use style instead but the transparency is lost.
+ this.emit('show-osd', null, this.currentColor.to_string(), this.currentColor.to_string().slice(0, 7), -1, false);
},
- selectShape: function(shape) {
- this.currentShape = shape;
- this.emit('show-osd', null, _(ShapeNames[shape]), -1);
+ selectTool: function(tool) {
+ this.currentTool = tool;
+ this.emit('show-osd', null, _(ToolNames[tool]), "", -1, false);
this.updatePointerCursor();
},
toggleFill: function() {
this.fill = !this.fill;
- this.emit('show-osd', null, this.fill ? _("Fill") : _("Stroke"), -1);
+ this.emit('show-osd', null, this.fill ? _("Fill") : _("Outline"), "", -1, false);
},
toggleDash: function() {
this.dashedLine = !this.dashedLine;
- this.emit('show-osd', null, this.dashedLine ? _("Dashed line") : _("Full line"), -1);
+ this.emit('show-osd', null, this.dashedLine ? _("Dashed line") : _("Full line"), "", -1, false);
},
incrementLineWidth: function(increment) {
this.currentLineWidth = Math.max(this.currentLineWidth + increment, 0);
- this.emit('show-osd', null, this.currentLineWidth + " " + _("px"), 2 * this.currentLineWidth);
+ this.emit('show-osd', null, _("%d px").format(this.currentLineWidth), "", 2 * this.currentLineWidth, false);
},
toggleLineJoin: function() {
this.currentLineJoin = this.currentLineJoin == 2 ? 0 : this.currentLineJoin + 1;
- this.emit('show-osd', null, _(LineJoinNames[this.currentLineJoin]), -1);
+ this.emit('show-osd', null, _(LineJoinNames[this.currentLineJoin]), "", -1, false);
},
toggleLineCap: function() {
this.currentLineCap = this.currentLineCap == 2 ? 0 : this.currentLineCap + 1;
- this.emit('show-osd', null, _(LineCapNames[this.currentLineCap]), -1);
+ this.emit('show-osd', null, _(LineCapNames[this.currentLineCap]), "", -1, false);
+ },
+
+ toggleFillRule: function() {
+ this.currentFillRule = this.currentFillRule == 1 ? 0 : this.currentFillRule + 1;
+ this.emit('show-osd', null, _(FillRuleNames[this.currentFillRule]), "", -1, false);
},
toggleFontWeight: function() {
- this.currentFontWeight = this.currentFontWeight == 1 ? 0 : this.currentFontWeight + 1;
- if (this.currentElement) {
+ let fontWeights = Object.keys(FontWeightNames).map(key => Number(key));
+ let index = fontWeights.indexOf(this.currentFontWeight);
+ this.currentFontWeight = index == fontWeights.length - 1 ? fontWeights[0] : fontWeights[index + 1];
+ if (this.currentElement && this.currentElement.font) {
this.currentElement.font.weight = this.currentFontWeight;
this._redisplay();
}
- this.emit('show-osd', null, `${_(FontWeightNames[this.currentFontWeight])}`, -1);
+ this.emit('show-osd', null, `` +
+ `${_(FontWeightNames[this.currentFontWeight])}`, "", -1, false);
},
toggleFontStyle: function() {
this.currentFontStyle = this.currentFontStyle == 2 ? 0 : this.currentFontStyle + 1;
- if (this.currentElement) {
+ if (this.currentElement && this.currentElement.font) {
this.currentElement.font.style = this.currentFontStyle;
this._redisplay();
}
- this.emit('show-osd', null, `${_(FontStyleNames[this.currentFontStyle])}`, -1);
+ this.emit('show-osd', null, `` +
+ `${_(FontStyleNames[this.currentFontStyle])}`, "", -1, false);
},
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.currentFontGeneric = this.currentFontGeneric == 5 ? 0 : this.currentFontGeneric + 1;
+ let currentFontFamily = this.currentFontGeneric == 0 ? this.currentThemeFontFamily : FontGenericNames[this.currentFontGeneric];
+ if (this.currentElement && this.currentElement.font) {
this.currentElement.font.family = currentFontFamily;
this._redisplay();
}
- this.emit('show-osd', null, `${_(currentFontFamily)}`, -1);
+ this.emit('show-osd', null, `${_(currentFontFamily)}`, "", -1, false);
+ },
+
+ toggleTextAlignment: function() {
+ this.currentTextRightAligned = !this.currentTextRightAligned;
+ if (this.currentElement && this.currentElement.textRightAligned !== undefined) {
+ this.currentElement.textRightAligned = this.currentTextRightAligned;
+ this._redisplay();
+ }
+ this.emit('show-osd', null, this.currentTextRightAligned ? _("Right aligned") : _("Left aligned"), "", -1, false);
},
toggleHelp: function() {
- if (this.helper.visible)
+ if (this.helper.visible) {
this.helper.hideHelp();
- else
+ if (this.textEntry)
+ this.textEntry.grab_key_focus();
+ } else {
this.helper.showHelp();
+ this.grab_key_focus();
+ }
+
+ },
+
+ // The area is reactive when it is modal.
+ _onReactiveChanged: function() {
+ if (this.hasGrid)
+ this._redisplay();
+ if (this.helper.visible)
+ this.toggleHelp();
+ if (this.textEntry && this.reactive)
+ this.textEntry.grab_key_focus();
+ },
+
+ _onDestroy: function() {
+ this.disconnect(this.reactiveHandler);
+ this.erase();
+ if (this._menu)
+ this._menu.disable();
+ },
+
+ updateActionMode: function() {
+ this.emit('update-action-mode');
},
enterDrawingMode: function() {
@@ -601,7 +998,7 @@ var DrawingArea = new Lang.Class({
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));
- this.get_parent().set_background_color(this.hasBackground ? this.activeBackgroundColor : null);
+ this.get_parent().set_background_color(this.reactive && this.hasBackground ? this.activeBackgroundColor : null);
this._updateStyle();
},
@@ -639,17 +1036,10 @@ var DrawingArea = new Lang.Class({
this.scrollHandler = null;
}
- if (this.helper.visible)
- this.helper.hideHelp();
-
this.currentElement = null;
this._stopTextCursorTimeout();
- this.currentShape = Shapes.NONE;
- this.dashedLine = false;
- this.fill = false;
this._redisplay();
- if (this._menu)
- this._menu.close();
+ this.closeMenu();
this.get_parent().set_background_color(null);
if (save)
this.savePersistent();
@@ -657,17 +1047,23 @@ var DrawingArea = new Lang.Class({
saveAsSvg: function() {
// stop drawing or writing
- if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.WRITING) {
+ if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.isWriting) {
this._stopWriting();
} else if (this.currentElement && this.currentElement.shape != Shapes.TEXT) {
this._stopDrawing();
}
let content = `