Merge branch 'dev-context-menu' into 'master'

Dev context menu

See merge request abakkk/DrawOnYourScreen!1
This commit is contained in:
abakkk 2019-03-28 00:24:43 +01:00
commit a89e1d9d70
11 changed files with 354 additions and 30 deletions

264
draw.js
View File

@ -22,21 +22,35 @@
const Cairo = imports.cairo;
const Clutter = imports.gi.Clutter;
const GdkPixbuf = imports.gi.GdkPixbuf;
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 Signals = imports.signals;
const St = imports.gi.St;
const BoxPointer = imports.ui.boxpointer;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
const Slider = imports.ui.slider;
const Screenshot = imports.ui.screenshot;
const Tweener = imports.ui.tweener;
const ExtensionUtils = imports.misc.extensionUtils;
const Extension = ExtensionUtils.getCurrentExtension();
const Convenience = Extension.imports.convenience;
const ExtensionJs = Extension.imports.extension;
const Prefs = Extension.imports.prefs;
const _ = imports.gettext.domain(Extension.metadata["gettext-domain"]).gettext;
const FILL_ICON_PATH = Extension.dir.get_child('icons').get_child('fill-symbolic.svg').get_path();
const LINEJOIN_ICON_PATH = Extension.dir.get_child('icons').get_child('linejoin-symbolic.svg').get_path();
const LINECAP_ICON_PATH = Extension.dir.get_child('icons').get_child('linecap-symbolic.svg').get_path();
const DASHED_LINE_ICON_PATH = Extension.dir.get_child('icons').get_child('dashed-line-symbolic.svg').get_path();
var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4 };
var ShapeNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text" };
var LineCapNames = { 0: 'Butt', 1: 'Round', 2: 'Square' };
@ -60,6 +74,7 @@ var DrawingArea = new Lang.Class({
this.settings = Convenience.getSettings();
this.emitter = new DrawingAreaEmitter();
this.monitor = monitor;
this.menu = new DrawingMenu(this);
this.helper = helper;
this.elements = [];
@ -70,6 +85,7 @@ var DrawingArea = new Lang.Class({
this.hasBackground = false;
this.textHasCursor = false;
this.dashedLine = false;
this.fill = false;
this.colors = [Clutter.Color.new(0, 0, 0, 255)];
if (loadJson)
@ -154,18 +170,27 @@ var DrawingArea = new Lang.Class({
}
if (button == 1) {
this._startDrawing(x, y, false, shiftPressed);
this._startDrawing(x, y, shiftPressed);
return Clutter.EVENT_STOP;
} else if (button == 2) {
this.toggleShape();
} else if (button == 3) {
this._startDrawing(x, y, true, shiftPressed);
this._stopDrawing();
this.menu.open(x, y);
return Clutter.EVENT_STOP;
}
return Clutter.EVENT_PROPAGATE;
},
_onKeyboardPopupMenu: function() {
this._stopDrawing();
if (this.helper.visible)
this.helper.hideHelp();
this.menu.popup();
return Clutter.EVENT_STOP;
},
_onKeyPressed: function(actor, event) {
if (event.get_key_symbol() == Clutter.Escape) {
this.emitter.emit('stop-drawing');
@ -213,7 +238,7 @@ var DrawingArea = new Lang.Class({
return Clutter.EVENT_STOP;
},
_startDrawing: function(stageX, stageY, fill, eraser) {
_startDrawing: function(stageX, stageY, eraser) {
let [success, startX, startY] = this.transform_stage_point(stageX, stageY);
if (!success)
@ -230,7 +255,7 @@ var DrawingArea = new Lang.Class({
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 },
fill: fill,
fill: this.fill,
eraser: eraser,
transform: { active: false, center: [0, 0], angle: 0, startAngle: 0, ratio: 1 },
text: '',
@ -406,6 +431,11 @@ var DrawingArea = new Lang.Class({
this.selectShape((this.currentShape == Object.keys(Shapes).length - 1) ? 0 : this.currentShape + 1);
},
toggleFill: function() {
this.fill = !this.fill;
this.emitter.emit('show-osd', this.fill ? _("Fill") : _("Stroke"), null);
},
toggleDash: function() {
this.dashedLine = !this.dashedLine;
this.emitter.emit('show-osd', this.dashedLine ? _("Dashed line") : _("Full line"), null);
@ -464,6 +494,7 @@ var DrawingArea = new Lang.Class({
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._onKeyboardPopupMenuHandler = this.connect('popup-menu', this._onKeyboardPopupMenu.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);
@ -479,6 +510,10 @@ var DrawingArea = new Lang.Class({
this.disconnect(this.buttonPressedHandler);
this.buttonPressedHandler = null;
}
if (this._onKeyboardPopupMenuHandler) {
this.disconnect(this._onKeyboardPopupMenuHandler);
this._onKeyboardPopupMenuHandler = null;
}
if (this.motionHandler) {
this.disconnect(this.motionHandler);
this.motionHandler = null;
@ -498,7 +533,9 @@ var DrawingArea = new Lang.Class({
this.currentElement = null;
this._stopCursorTimeout();
this.dashedLine = false;
this.fill = false;
this._redisplay();
this.menu.close();
this.get_parent().set_background_color(null);
if (save)
this.saveAsJson();
@ -584,6 +621,7 @@ var DrawingArea = new Lang.Class({
disable: function() {
this.erase();
this.menu.disable();
}
});
@ -941,3 +979,221 @@ var DrawingHelper = new Lang.Class({
},
});
var DrawingMenu = new Lang.Class({
Name: 'DrawOnYourScreenDrawingMenu',
_init: function(area) {
this.area = area;
let side = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL ? St.Side.RIGHT : St.Side.LEFT;
this.menu = new PopupMenu.PopupMenu(Main.layoutManager.dummyCursor, 0.25, side);
this.menuManager = new PopupMenu.PopupMenuManager({ actor: this.area });
this.menuManager.addMenu(this.menu);
Main.layoutManager.uiGroup.add_actor(this.menu.actor);
this.menu.actor.add_style_class_name('background-menu draw-on-your-screen-menu');
this.menu.actor.hide();
// do not close the menu on item activated
this.menu.itemActivated = () => {};
this.menu.connect('open-state-changed', this._onMenuOpenStateChanged.bind(this));
},
disable: function() {
this.menuManager.removeMenu(this.menu);
Main.layoutManager.uiGroup.remove_actor(this.menu.actor);
this.menu.actor.destroy();
},
_onMenuOpenStateChanged: function(menu, open) {
if (!open)
// actionMode has changed, set previous actionMode in order to keep internal shortcuts working
Main.actionMode = ExtensionJs.DRAWING_ACTION_MODE | Shell.ActionMode.NORMAL;
},
popup: function() {
if (this.menu.isOpen) {
this.close();
} else {
this.open();
this.menu.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
}
},
open: function(x, y) {
if (this.menu.isOpen)
return;
if (x === undefined || y === undefined)
[x, y] = [this.area.monitor.x + this.area.monitor.width / 2, this.area.monitor.y + this.area.monitor.height / 2];
this._redisplay();
Main.layoutManager.setDummyCursorGeometry(x, y, 0, 0);
let monitor = this.area.monitor;
this.menu._arrowAlignment = (y - monitor.y) / monitor.height;
this.menu.open(BoxPointer.PopupAnimation.NONE);
this.menuManager.ignoreRelease();
},
close: function() {
if (this.menu.isOpen)
this.menu.close();
},
_redisplay: function() {
this.menu.removeAll();
this.menu.addAction(_("Undo"), this.area.undo.bind(this.area), 'edit-undo-symbolic');
this.menu.addAction(_("Redo"), this.area.redo.bind(this.area), 'edit-redo-symbolic');
this.menu.addAction(_("Erase"), this.area.deleteLastElement.bind(this.area), 'edit-clear-all-symbolic');
this.menu.addAction(_("Smooth"), this.area.smoothLastElement.bind(this.area), 'format-text-strikethrough-symbolic');
this._addSeparator(this.menu);
this._addSubMenuItem(this.menu, null, ShapeNames, this.area, 'currentShape', this.updateSectionVisibility.bind(this));
this._addColorSubMenuItem(this.menu);
let fillIcon = GdkPixbuf.Pixbuf.new_from_file_at_size(FILL_ICON_PATH, 24, 24);
this._addSwitchItem(this.menu, _("Fill"), fillIcon, this.area, 'fill', this.updateSectionVisibility.bind(this));
this._addSeparator(this.menu);
let lineSection = new PopupMenu.PopupMenuSection();
this._addSliderItem(lineSection, this.area, 'currentLineWidth');
let linejoinIcon = GdkPixbuf.Pixbuf.new_from_file_at_size(LINEJOIN_ICON_PATH, 24, 24);
this._addSubMenuItem(lineSection, linejoinIcon, LineJoinNames, this.area, 'currentLineJoin');
let linecapIcon = GdkPixbuf.Pixbuf.new_from_file_at_size(LINECAP_ICON_PATH, 24, 24);
this._addSubMenuItem(lineSection, linecapIcon, LineCapNames, this.area, 'currentLineCap');
let dashedLineIcon = GdkPixbuf.Pixbuf.new_from_file_at_size(DASHED_LINE_ICON_PATH, 24, 24);
this._addSwitchItem(lineSection, _("Dashed"), dashedLineIcon, this.area, 'dashedLine');
this._addSeparator(lineSection);
this.menu.addMenuItem(lineSection);
this.lineSection = lineSection;
let fontSection = new PopupMenu.PopupMenuSection();
let FontFamilyNamesCopy = Object.create(FontFamilyNames);
FontFamilyNamesCopy[0] = this.area.fontFamily;
this._addSubMenuItem(fontSection, 'font-x-generic-symbolic', FontFamilyNamesCopy, this.area, 'currentFontFamilyId');
this._addSubMenuItem(fontSection, 'format-text-bold-symbolic', FontWeightNames, this.area, 'currentFontWeight');
this._addSubMenuItem(fontSection, 'format-text-italic-symbolic', FontStyleNames, this.area, 'currentFontStyle');
this._addSeparator(fontSection);
this.menu.addMenuItem(fontSection);
this.fontSection = fontSection;
let manager = ExtensionJs.manager;
this._addSwitchItemWithCallback(this.menu, _("Hide panel and dock"), manager.hiddenList ? true : false, manager.togglePanelAndDockOpacity.bind(manager));
this._addSwitchItemWithCallback(this.menu, _("Add a drawing background"), this.area.hasBackground, this.area.toggleBackground.bind(this.area));
this._addSwitchItemWithCallback(this.menu, _("Square drawing area"), this.area.isSquareArea, this.area.toggleSquareArea.bind(this.area));
this._addSeparator(this.menu);
this.menu.addAction(_("Save drawing as a SVG file"), this.area.saveAsSvg.bind(this.area), 'document-save-symbolic');
this.menu.addAction(_("Open stylesheet.css"), manager.openStylesheetFile.bind(manager), 'document-open-symbolic');
this.menu.addAction(_("Show help"), this.area.toggleHelp.bind(this.area), 'preferences-desktop-keyboard-shortcuts-symbolic');
this.updateSectionVisibility();
},
updateSectionVisibility: function() {
if (this.area.fill || this.area.currentShape == Shapes.TEXT)
this.lineSection.actor.hide();
else
this.lineSection.actor.show();
if (this.area.currentShape != Shapes.TEXT)
this.fontSection.actor.hide();
else
this.fontSection.actor.show();
},
_addSwitchItem: function(menu, label, icon, target, targetProperty, callback) {
let item = new PopupMenu.PopupSwitchMenuItem(label, target[targetProperty]);
if (icon) {
item.icon = new St.Icon({ style_class: 'popup-menu-icon' });
item.actor.insert_child_at_index(item.icon, 1);
if (icon instanceof GObject.Object && GObject.type_is_a(icon, GdkPixbuf.Pixbuf))
item.icon.set_gicon(icon);
else
item.icon.set_icon_name(icon);
}
item.connect('toggled', (item, state) => {
target[targetProperty] = state;
if (callback)
callback();
});
menu.addMenuItem(item);
},
_addSwitchItemWithCallback: function(menu, label, active, onToggled) {
let item = new PopupMenu.PopupSwitchMenuItem(label, active);
item.connect('toggled', onToggled);
menu.addMenuItem(item);
},
_addSliderItem: function(menu, target, targetProperty) {
let item = new PopupMenu.PopupBaseMenuItem({ activate: false });
let label = new St.Label({ text: target[targetProperty] + " px", style_class: 'draw-on-your-screen-menu-slider-label' });
let slider = new Slider.Slider(target[targetProperty] / 50);
slider.connect('value-changed', (slider, value, property) => {
target[targetProperty] = Math.max(Math.round(value * 50), 1);
label.set_text(target[targetProperty] + " px");
});
item.actor.add(slider.actor, { expand: true });
item.actor.add(label);
item.actor.connect('key-press-event', slider.onKeyPressEvent.bind(slider));
menu.addMenuItem(item);
},
_addSubMenuItem: function(menu, icon, obj, target, targetProperty, callback) {
let item = new PopupMenu.PopupSubMenuMenuItem(_(obj[target[targetProperty]]), icon ? true : false);
if (icon && icon instanceof GObject.Object && GObject.type_is_a(icon, GdkPixbuf.Pixbuf))
item.icon.set_gicon(icon);
else if (icon)
item.icon.set_icon_name(icon);
item.menu.itemActivated = () => {
item.menu.close();
};
Mainloop.timeout_add(0, () => {
for (let i in obj) {
item.menu.addAction(_(obj[i]), () => {
item.label.set_text(_(obj[i]));
target[targetProperty] = i;
if (callback)
callback();
});
}
return GLib.SOURCE_REMOVE;
});
menu.addMenuItem(item);
},
_addColorSubMenuItem: function(menu) {
let item = new PopupMenu.PopupSubMenuMenuItem(_("Color"), true);
item.icon.set_icon_name('document-edit-symbolic');
item.icon.set_style(`color:${this.area.currentColor.to_string().slice(0, 7)};`);
item.menu.itemActivated = () => {
item.menu.close();
};
Mainloop.timeout_add(0, () => {
for (let i = 1; i < this.area.colors.length; i++) {
let text = `<span foreground="${this.area.colors[i].to_string()}">${this.area.colors[i].to_string()}</span>`;
let colorItem = item.menu.addAction(text, () => {
this.area.currentColor = this.area.colors[i];
item.icon.set_style(`color:${this.area.currentColor.to_string().slice(0, 7)};`);
});
colorItem.label.get_clutter_text().set_use_markup(true);
}
return GLib.SOURCE_REMOVE;
});
menu.addMenuItem(item);
},
_addSeparator: function(menu) {
let separator = new PopupMenu.PopupSeparatorMenuItem(' ');
separator.actor.add_style_class_name('draw-on-your-screen-menu-separator');
menu.addMenuItem(separator);
}
});

View File

@ -32,6 +32,9 @@ const Convenience = Extension.imports.convenience;
const Draw = Extension.imports.draw;
const _ = imports.gettext.domain(Extension.metadata["gettext-domain"]).gettext;
// DRAWING_ACTION_MODE is a custom Shell.ActionMode
var DRAWING_ACTION_MODE = Math.pow(2,14);
let manager;
function init() {
@ -147,6 +150,7 @@ var AreaManager = new Lang.Class({
'toggle-linejoin': this.activeArea.toggleLineJoin.bind(this.activeArea),
'toggle-linecap': this.activeArea.toggleLineCap.bind(this.activeArea),
'toggle-dash' : this.activeArea.toggleDash.bind(this.activeArea),
'toggle-fill' : this.activeArea.toggleFill.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),
@ -164,7 +168,7 @@ var AreaManager = new Lang.Class({
Main.wm.addKeybinding(key,
this.settings,
Meta.KeyBindingFlags.NONE,
256,
DRAWING_ACTION_MODE,
this.internalKeybindings[key]);
}
@ -172,7 +176,7 @@ var AreaManager = new Lang.Class({
Main.wm.addKeybinding('select-color' + i,
this.settings,
Meta.KeyBindingFlags.NONE,
256,
DRAWING_ACTION_MODE,
() => this.activeArea.selectColor(i));
}
},
@ -270,8 +274,8 @@ var AreaManager = new Lang.Class({
activeContainer.get_parent().remove_actor(activeContainer);
Main.uiGroup.add_child(activeContainer);
// 256 is a custom Shell.ActionMode
if (!Main.pushModal(this.areas[currentIndex], { actionMode: 256 | 1 }))
// add Shell.ActionMode.NORMAL to keep system keybindings enabled (e.g. Alt + F2 ...)
if (!Main.pushModal(this.areas[currentIndex], { actionMode: DRAWING_ACTION_MODE | Shell.ActionMode.NORMAL }))
return;
this.activeArea = this.areas[currentIndex];
this.addInternalKeybindings();

View File

@ -0,0 +1,3 @@
<svg viewBox="0 0 576 576" xmlns="http://www.w3.org/2000/svg">
<path fill="transparent" stroke="#eeeeec" stroke-width="110" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="25 150.5" stroke-dashoffset="0" d="M100 288 L 476 288"/>
</svg>

After

Width:  |  Height:  |  Size: 255 B

3
icons/fill-symbolic.svg Normal file
View File

@ -0,0 +1,3 @@
<svg viewBox="0 0 576 576" xmlns="http://www.w3.org/2000/svg">
<ellipse fill="#eeeeec" stroke="transparent" cx="288" cy="288" rx="260" ry="180"/>
</svg>

After

Width:  |  Height:  |  Size: 155 B

View File

@ -0,0 +1,4 @@
<svg viewBox="0 0 576 576" xmlns="http://www.w3.org/2000/svg">
<path fill="transparent" stroke="#eeeeec" stroke-width="250" stroke-linecap="butt" stroke-linejoin="round" d="M50 288 L 350 288"/>
<path fill="transparent" stroke="#eeeeec" stroke-width="250" stroke-linecap="round" stroke-linejoin="round" d="M300 288 L 400 288"/>
</svg>

After

Width:  |  Height:  |  Size: 338 B

View File

@ -0,0 +1,4 @@
<svg viewBox="0 0 576 576" xmlns="http://www.w3.org/2000/svg">
<path fill="transparent" stroke="#eeeeec" stroke-width="110" stroke-linecap="round" stroke-linejoin="round" d="M288 90 L 55 512"/>
<path fill="transparent" stroke="#eeeeec" stroke-width="110" stroke-linecap="round" stroke-linejoin="round" d="M288 90 L 514 512"/>
</svg>

After

Width:  |  Height:  |  Size: 337 B

View File

@ -43,6 +43,12 @@ msgstr ""
msgid "Text"
msgstr ""
msgid "Fill"
msgstr ""
msgid "Stroke"
msgstr ""
msgid "Dashed line"
msgstr ""
@ -69,6 +75,21 @@ msgstr ""
msgid "System"
msgstr ""
msgid "Undo"
msgstr ""
msgid "Redo"
msgstr ""
msgid "Erase"
msgstr ""
msgid "Smooth"
msgstr ""
msgid "Dashed"
msgstr ""
#: prefs.js
# GLOBAL_KEYBINDINGS
@ -93,6 +114,24 @@ msgstr ""
msgid "Smooth last brushstroke"
msgstr ""
msgid "Select line"
msgstr ""
msgid "Select ellipse"
msgstr ""
msgid "Select rectangle"
msgstr ""
msgid "Select text"
msgstr ""
msgid "Unselect shape (free drawing)"
msgstr ""
msgid "Select fill/stroke"
msgstr ""
msgid "Increment line width"
msgstr ""
@ -115,21 +154,6 @@ msgstr ""
#msgid "Dashed line"
#msgstr ""
msgid "Select line"
msgstr ""
msgid "Select ellipse"
msgstr ""
msgid "Select rectangle"
msgstr ""
msgid "Select text"
msgstr ""
msgid "Unselect shape (free drawing)"
msgstr ""
msgid "Change font family (generic name)"
msgstr ""

View File

@ -43,6 +43,13 @@ var INTERNAL_KEYBINDINGS = {
'delete-last-element' : "Erase last brushstroke",
'smooth-last-element': "Smooth last brushstroke",
'-separator-1': '',
'select-line-shape': "Select line",
'select-ellipse-shape': "Select ellipse",
'select-rectangle-shape': "Select rectangle",
'select-text-shape': "Select text",
'select-none-shape': "Unselect shape (free drawing)",
'toggle-fill': "Select fill/stroke",
'-separator-2': '',
'increment-line-width': "Increment line width",
'decrement-line-width': "Decrement line width",
'increment-line-width-more': "Increment line width even more",
@ -50,12 +57,6 @@ var INTERNAL_KEYBINDINGS = {
'toggle-linejoin': "Change linejoin",
'toggle-linecap': "Change linecap",
'toggle-dash': "Dashed line",
'-separator-2': '',
'select-line-shape': "Select line",
'select-ellipse-shape': "Select ellipse",
'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",

Binary file not shown.

View File

@ -117,10 +117,15 @@
<description>toggle linecap</description>
</key>
<key type="as" name="toggle-dash">
<default>["&lt;Primary&gt;a"]</default>
<default>["&lt;Primary&gt;period"]</default>
<summary>toggle dash</summary>
<description>toggle dash</description>
</key>
<key type="as" name="toggle-fill">
<default>["&lt;Primary&gt;a"]</default>
<summary>toggle fill</summary>
<description>toggle fill</description>
</key>
<key type="as" name="select-color1">
<default><![CDATA[['<Primary>KP_1','<Primary>1']]]></default>
<summary>select color1</summary>

View File

@ -77,4 +77,24 @@
min-height: 0.6em;
}
/* context menu */
.draw-on-your-screen-menu {
font-size: 0.98em; /* default: 1em */
}
.draw-on-your-screen-menu .popup-menu-item {
padding-top: .37em; /* default: .4em */
padding-bottom: .37em;
}
.draw-on-your-screen-menu-separator StLabel {
font-size: 0;
}
.draw-on-your-screen-menu-slider-label {
min-width: 3em;
text-align: right;
}