add context menu

This commit is contained in:
abakkk 2019-03-26 22:28:24 +01:00
parent 9f74d2bd57
commit 27ea6a8be9
8 changed files with 250 additions and 5 deletions

191
draw.js
View File

@ -22,21 +22,35 @@
const Cairo = imports.cairo; const Cairo = imports.cairo;
const Clutter = imports.gi.Clutter; const Clutter = imports.gi.Clutter;
const GdkPixbuf = imports.gi.GdkPixbuf;
const GLib = imports.gi.GLib; const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk; const Gtk = imports.gi.Gtk;
const Lang = imports.lang; const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
const Shell = imports.gi.Shell;
const Signals = imports.signals; const Signals = imports.signals;
const St = imports.gi.St; 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 Screenshot = imports.ui.screenshot;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const ExtensionUtils = imports.misc.extensionUtils; const ExtensionUtils = imports.misc.extensionUtils;
const Extension = ExtensionUtils.getCurrentExtension(); const Extension = ExtensionUtils.getCurrentExtension();
const Convenience = Extension.imports.convenience; const Convenience = Extension.imports.convenience;
const ExtensionJs = Extension.imports.extension;
const Prefs = Extension.imports.prefs; const Prefs = Extension.imports.prefs;
const _ = imports.gettext.domain(Extension.metadata["gettext-domain"]).gettext; 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 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 ShapeNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text" };
var LineCapNames = { 0: 'Butt', 1: 'Round', 2: 'Square' }; var LineCapNames = { 0: 'Butt', 1: 'Round', 2: 'Square' };
@ -60,6 +74,7 @@ var DrawingArea = new Lang.Class({
this.settings = Convenience.getSettings(); this.settings = Convenience.getSettings();
this.emitter = new DrawingAreaEmitter(); this.emitter = new DrawingAreaEmitter();
this.monitor = monitor; this.monitor = monitor;
this.menu = new DrawingMenu(this);
this.helper = helper; this.helper = helper;
this.elements = []; this.elements = [];
@ -70,6 +85,7 @@ var DrawingArea = new Lang.Class({
this.hasBackground = false; this.hasBackground = false;
this.textHasCursor = false; this.textHasCursor = false;
this.dashedLine = false; this.dashedLine = false;
this.fill = false;
this.colors = [Clutter.Color.new(0, 0, 0, 255)]; this.colors = [Clutter.Color.new(0, 0, 0, 255)];
if (loadJson) if (loadJson)
@ -159,7 +175,9 @@ var DrawingArea = new Lang.Class({
} else if (button == 2) { } else if (button == 2) {
this.toggleShape(); this.toggleShape();
} else if (button == 3) { } else if (button == 3) {
this._startDrawing(x, y, true, shiftPressed); /*this._startDrawing(x, y, true, shiftPressed);
return Clutter.EVENT_STOP;*/
this.menu.open(x, y);
return Clutter.EVENT_STOP; return Clutter.EVENT_STOP;
} }
@ -941,3 +959,174 @@ 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));
},
_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;
},
open: function(x, y) {
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();
},
_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._addSubMenuItem(null, null, ShapeNames, this.area, 'currentShape');
this._addColorSubMenuItem();
let fillIcon = GdkPixbuf.Pixbuf.new_from_file_at_size(FILL_ICON_PATH, 24, 24);
this._addSwitchItem(_("Fill"), fillIcon, this.area, 'fill');
this._addSeparator();
this._addSliderItem(this.area, 'currentLineWidth');
let linejoinIcon = GdkPixbuf.Pixbuf.new_from_file_at_size(LINEJOIN_ICON_PATH, 24, 24);
this._addSubMenuItem(null, linejoinIcon, LineJoinNames, this.area, 'currentLineJoin');
let linecapIcon = GdkPixbuf.Pixbuf.new_from_file_at_size(LINECAP_ICON_PATH, 24, 24);
this._addSubMenuItem(null, linecapIcon, LineCapNames, this.area, 'currentLineCap');
let dashedLineIcon = GdkPixbuf.Pixbuf.new_from_file_at_size(DASHED_LINE_ICON_PATH, 24, 24);
this._addSwitchItem(_("Dashed"), dashedLineIcon, this.area, 'dashedLine');
this._addSeparator();
let FontFamilyNamesCopy = Object.create(FontFamilyNames);
FontFamilyNamesCopy[0] = this.area.fontFamily;
this._addSubMenuItem(null, 'font-x-generic-symbolic', FontFamilyNamesCopy, this.area, 'currentFontFamilyId');
this._addSubMenuItem(null, 'format-text-bold-symbolic', FontWeightNames, this.area, 'currentFontWeight');
this._addSubMenuItem(null, 'format-text-italic-symbolic', FontStyleNames, this.area, 'currentFontStyle');
this._addSeparator();
let manager = ExtensionJs.manager;
this._addSwitchItemWithCallback(_("Hide panel and dock"), manager.hiddenList ? true : false, manager.togglePanelAndDockOpacity.bind(manager));
this._addSwitchItemWithCallback(_("Add a drawing background"), this.area.hasBackground, this.area.toggleBackground.bind(this.area));
this._addSwitchItemWithCallback(_("Square drawing area"), this.area.isSquareArea, this.area.toggleSquareArea.bind(this.area));
this._addSeparator();
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');
},
_addSwitchItem: function(label, icon, target, targetProperty) {
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;
});
this.menu.addMenuItem(item);
},
_addSwitchItemWithCallback: function(label, active, onToggled) {
let item = new PopupMenu.PopupSwitchMenuItem(label, active);
item.connect('toggled', onToggled);
this.menu.addMenuItem(item);
},
_addSliderItem: function(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));
this.menu.addMenuItem(item);
},
_addSubMenuItem: function(label, icon, obj, target, targetProperty) {
let text = label ? label + _(obj[target[targetProperty]]).toLowerCase() : _(obj[target[targetProperty]]);
let item = new PopupMenu.PopupSubMenuMenuItem(text, 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]), () => {
let text = label ? label + _(obj[i]).toLowerCase() : _(obj[i]);
item.label.set_text(text);
target[targetProperty] = i;
});
}
return GLib.SOURCE_REMOVE;
});
this.menu.addMenuItem(item);
},
_addColorSubMenuItem: function() {
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;
});
this.menu.addMenuItem(item);
},
_addSeparator: function() {
let separator = new PopupMenu.PopupSeparatorMenuItem();
this.menu.addMenuItem(separator);
}
});

View File

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

@ -69,6 +69,24 @@ msgstr ""
msgid "System" msgid "System"
msgstr "" msgstr ""
msgid "Undo"
msgstr ""
msgid "Redo"
msgstr ""
msgid "Erase"
msgstr ""
msgid "Smooth"
msgstr ""
msgid "Fill"
msgstr ""
msgid "Dashed"
msgstr ""
#: prefs.js #: prefs.js
# GLOBAL_KEYBINDINGS # GLOBAL_KEYBINDINGS

View File

@ -77,4 +77,25 @@
min-height: 0.6em; min-height: 0.6em;
} }
/* context menu */
.draw-on-your-screen-menu {
font-size: 95%;
}
.draw-on-your-screen-menu .popup-menu-item {
padding-top: .35em;
padding-bottom: .35em;
}
.draw-on-your-screen-menu .popup-separator-menu-item {
margin-top: 0;
margin-bottom: 0;
}
.draw-on-your-screen-menu-slider-label {
min-width: 3em;
text-align: right;
}