diff --git a/draw.js b/draw.js
index 85e3603..e44414c 100644
--- a/draw.js
+++ b/draw.js
@@ -48,6 +48,7 @@ const _ = imports.gettext.domain(Extension.metadata["gettext-domain"]).gettext;
const GS_VERSION = Config.PACKAGE_VERSION;
const DEFAULT_FILE_NAME = 'DrawOnYourScreen';
+const DATA_SUB_DIR = 'drawOnYourScreen'
const FILL_ICON_PATH = Extension.dir.get_child('icons').get_child('fill-symbolic.svg').get_path();
const STROKE_ICON_PATH = Extension.dir.get_child('icons').get_child('stroke-symbolic.svg').get_path();
@@ -70,6 +71,40 @@ function getDateString() {
return `${date.format("%F")} ${date.format("%X")}`;
}
+function getJsonFiles() {
+ let directory = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), DATA_SUB_DIR]));
+ if (!directory.query_exists(null))
+ return [];
+
+ let jsonFiles = [];
+ let enumerator;
+ try {
+ enumerator = directory.enumerate_children('standard::name,standard::display-name,standard::content-type,time::modified', Gio.FileQueryInfoFlags.NONE, null);
+ } catch(e) {
+ return [];
+ }
+
+ let i = 0;
+ let fileInfo = enumerator.next_file(null);
+ while (fileInfo) {
+ if (fileInfo.get_content_type().indexOf('json') != -1 && fileInfo.get_name() != `${DEFAULT_FILE_NAME}.json`) {
+ let file = enumerator.get_child(fileInfo);
+ jsonFiles.push({ name: fileInfo.get_name().slice(0, -5),
+ displayName: fileInfo.get_display_name().slice(0, -5),
+ modificationDateTime: fileInfo.get_modification_date_time(),
+ delete: () => file.delete(null) });
+ }
+ fileInfo = enumerator.next_file(null);
+ }
+ enumerator.close(null);
+
+ jsonFiles.sort((a, b) => {
+ return a.modificationDateTime.difference(b.modificationDateTime);
+ });
+
+ return jsonFiles;
+}
+
// 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.
@@ -79,7 +114,7 @@ var DrawingArea = new Lang.Class({
Signals: { 'show-osd': { param_types: [GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_DOUBLE] },
'stop-drawing': {} },
- _init: function(params, monitor, helper, loadJson) {
+ _init: function(params, monitor, helper, loadPersistent) {
this.parent({ style_class: 'draw-on-your-screen', name: params && params.name ? params.name : ""});
this.connect('repaint', this._repaint.bind(this));
@@ -99,8 +134,8 @@ var DrawingArea = new Lang.Class({
this.fill = false;
this.colors = [Clutter.Color.new(0, 0, 0, 255)];
- if (loadJson)
- this._loadJson();
+ if (loadPersistent)
+ this._loadPersistent();
},
get menu() {
@@ -591,7 +626,7 @@ var DrawingArea = new Lang.Class({
this._menu.close();
this.get_parent().set_background_color(null);
if (save)
- this.saveAsJson();
+ this.savePersistent();
},
saveAsSvg: function() {
@@ -632,10 +667,18 @@ var DrawingArea = new Lang.Class({
}
},
- saveAsJson: function() {
- let filename = `${DEFAULT_FILE_NAME}.json`;
- let dir = GLib.get_user_data_dir();
- let path = GLib.build_filenamev([dir, filename]);
+ _saveAsJson: function(name, notify) {
+ // stop drawing or writing
+ if (this.currentElement && this.currentElement.shape == Shapes.TEXT && this.currentElement.state == TextState.WRITING) {
+ this._stopWriting();
+ } else if (this.currentElement && this.currentElement.shape != Shapes.TEXT) {
+ this._stopDrawing();
+ }
+
+ let dir = GLib.build_filenamev([GLib.get_user_data_dir(), DATA_SUB_DIR]);
+ if (!GLib.file_test(dir, GLib.FileTest.EXISTS))
+ GLib.mkdir_with_parents(dir, 0o700);
+ let path = GLib.build_filenamev([dir, `${name}.json`]);
let oldContents;
if (GLib.file_test(path, GLib.FileTest.EXISTS)) {
@@ -652,14 +695,30 @@ var DrawingArea = new Lang.Class({
// because of compromise between disk usage and human readability
let contents = `[\n ` + new Array(...this.elements.map(element => JSON.stringify(element))).join(`,\n\n `) + `\n]`;
- if (contents != oldContents)
+ if (contents != oldContents) {
GLib.file_set_contents(path, contents);
+ if (notify)
+ this.emit('show-osd', 'document-save-symbolic', name, -1);
+ if (name != DEFAULT_FILE_NAME)
+ this.jsonName = name;
+ }
},
- _loadJson: function() {
- let filename = `${DEFAULT_FILE_NAME}.json`;
+ saveAsJsonWithName: function(name) {
+ this._saveAsJson(name);
+ },
+
+ saveAsJson: function() {
+ this._saveAsJson(getDateString(), true);
+ },
+
+ savePersistent: function() {
+ this._saveAsJson(DEFAULT_FILE_NAME);
+ },
+
+ _loadJson: function(name, notify) {
let dir = GLib.get_user_data_dir();
- let path = GLib.build_filenamev([dir, filename]);
+ let path = GLib.build_filenamev([dir, DATA_SUB_DIR, `${name}.json`]);
if (!GLib.file_test(path, GLib.FileTest.EXISTS))
return;
@@ -669,6 +728,43 @@ var DrawingArea = new Lang.Class({
if (contents instanceof Uint8Array)
contents = imports.byteArray.toString(contents);
this.elements.push(...JSON.parse(contents).map(object => new DrawingElement(object)));
+
+ if (notify)
+ this.emit('show-osd', 'document-open-symbolic', name, -1);
+ if (name != DEFAULT_FILE_NAME)
+ this.jsonName = name;
+ },
+
+ _loadPersistent: function() {
+ this._loadJson(DEFAULT_FILE_NAME);
+ },
+
+ loadJson: function(name, notify) {
+ this.elements = [];
+ this.currentElement = null;
+ this._stopCursorTimeout();
+ this._loadJson(name, notify);
+ this._redisplay();
+ },
+
+ loadNextJson: function() {
+ let names = getJsonFiles().map(file => file.name);
+
+ if (!names.length)
+ return;
+
+ let nextName = names[this.jsonName && names.indexOf(this.jsonName) != names.length - 1 ? names.indexOf(this.jsonName) + 1 : 0];
+ this.loadJson(nextName, true);
+ },
+
+ loadPreviousJson: function() {
+ let names = getJsonFiles().map(file => file.name);
+
+ if (!names.length)
+ return;
+
+ let previousName = names[this.jsonName && names.indexOf(this.jsonName) > 0 ? names.indexOf(this.jsonName) - 1 : names.length - 1];
+ this.loadJson(previousName, true);
},
disable: function() {
@@ -1144,8 +1240,12 @@ var DrawingMenu = new Lang.Class({
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._addDrawingNameItem(this.menu);
+ this._addOpenDrawingSubMenuItem(this.menu);
+ this._addSaveDrawingSubMenuItem(this.menu);
+
+ this.menu.addAction(_("Save drawing as a SVG file"), this.area.saveAsSvg.bind(this.area), 'image-x-generic-symbolic');
+ this.menu.addAction(_("Open stylesheet.css"), manager.openStylesheetFile.bind(manager), 'document-page-setup-symbolic');
this.menu.addAction(_("Show help"), this.area.toggleHelp.bind(this.area), 'preferences-desktop-keyboard-shortcuts-symbolic');
this.updateSectionVisibility();
@@ -1277,6 +1377,105 @@ var DrawingMenu = new Lang.Class({
menu.addMenuItem(item);
},
+ _addDrawingNameItem: function(menu) {
+ this.drawingNameMenuItem = new PopupMenu.PopupMenuItem('', { reactive: false, activate: false });
+ this.drawingNameMenuItem.setSensitive(false);
+ menu.addMenuItem(this.drawingNameMenuItem);
+ this._updateDrawingNameMenuItem();
+ },
+
+ _updateDrawingNameMenuItem: function() {
+ getActor(this.drawingNameMenuItem).visible = this.area.jsonName ? true : false;
+ if (this.area.jsonName) {
+ this.drawingNameMenuItem.label.set_text(`${this.area.jsonName}`);
+ this.drawingNameMenuItem.label.get_clutter_text().set_use_markup(true);
+ }
+ },
+
+ _addOpenDrawingSubMenuItem: function(menu) {
+ let item = new PopupMenu.PopupSubMenuMenuItem(_("Open drawing"), true);
+ this.openDrawingSubMenuItem = item;
+ this.openDrawingSubMenu = item.menu;
+ item.icon.set_icon_name('document-open-symbolic');
+
+ item.menu.itemActivated = () => {
+ item.menu.close();
+ };
+
+ Mainloop.timeout_add(0, () => {
+ this._populateOpenDrawingSubMenu();
+ return GLib.SOURCE_REMOVE;
+ });
+ menu.addMenuItem(item);
+ },
+
+ _populateOpenDrawingSubMenu: function() {
+ this.openDrawingSubMenu.removeAll();
+ let jsonFiles = getJsonFiles();
+ jsonFiles.forEach(file => {
+ let item = this.openDrawingSubMenu.addAction(`${file.displayName}`, () => {
+ this.area.loadJson(file.name);
+ this._updateDrawingNameMenuItem();
+ this._updateSaveDrawingSubMenuItemSensitivity();
+ });
+ item.label.get_clutter_text().set_use_markup(true);
+
+ let expander = new St.Bin({
+ style_class: 'popup-menu-item-expander',
+ x_expand: true,
+ });
+ getActor(item).add_child(expander);
+
+ let deleteButton = new St.Button({ style_class: 'draw-on-your-screen-menu-delete-button',
+ child: new St.Icon({ icon_name: 'edit-delete-symbolic',
+ style_class: 'popup-menu-icon',
+ x_align: Clutter.ActorAlign.END }) });
+ getActor(item).add_child(deleteButton);
+
+ deleteButton.connect('clicked', () => {
+ file.delete();
+ this._populateOpenDrawingSubMenu();
+ });
+ });
+
+ this.openDrawingSubMenuItem.setSensitive(!this.openDrawingSubMenu.isEmpty());
+ },
+
+ _addSaveDrawingSubMenuItem: function(menu) {
+ let item = new PopupMenu.PopupSubMenuMenuItem(_("Save drawing"), true);
+ this.saveDrawingSubMenuItem = item;
+ this._updateSaveDrawingSubMenuItemSensitivity();
+ this.saveDrawingSubMenu = item.menu;
+ item.icon.set_icon_name('document-save-symbolic');
+
+ item.menu.itemActivated = () => {
+ item.menu.close();
+ };
+
+ Mainloop.timeout_add(0, () => {
+ this._populateSaveDrawingSubMenu();
+ return GLib.SOURCE_REMOVE;
+ });
+ menu.addMenuItem(item);
+ },
+
+ _updateSaveDrawingSubMenuItemSensitivity: function() {
+ this.saveDrawingSubMenuItem.setSensitive(this.area.elements.length > 0);
+ },
+
+ _populateSaveDrawingSubMenu: function() {
+ this.saveEntry = new DrawingMenuEntry({ initialTextGetter: getDateString,
+ entryActivateCallback: (text) => {
+ this.area.saveAsJsonWithName(text);
+ this.saveDrawingSubMenu.toggle();
+ this._updateDrawingNameMenuItem();
+ this._populateOpenDrawingSubMenu();
+ },
+ invalidStrings: [DEFAULT_FILE_NAME, '/'],
+ primaryIconName: 'insert-text' });
+ this.saveDrawingSubMenu.addMenuItem(this.saveEntry.item);
+ },
+
_addSeparator: function(menu) {
let separatorItem = new PopupMenu.PopupSeparatorMenuItem(' ');
getActor(separatorItem).add_style_class_name('draw-on-your-screen-menu-separator-item');
@@ -1284,3 +1483,87 @@ var DrawingMenu = new Lang.Class({
}
});
+// based on searchItem.js, https://github.com/leonardo-bartoli/gnome-shell-extension-Recents
+var DrawingMenuEntry = new Lang.Class({
+ Name: 'DrawOnYourScreenDrawingMenuEntry',
+
+ _init: function (params) {
+ this.params = params;
+ this.item = new PopupMenu.PopupBaseMenuItem({ style_class: 'draw-on-your-screen-menu-entry-item',
+ activate: false,
+ reactive: true,
+ can_focus: false });
+
+ this.itemActor = GS_VERSION < '3.33.0' ? this.item.actor : this.item;
+
+ this.entry = new St.Entry({
+ style_class: 'search-entry draw-on-your-screen-menu-entry',
+ track_hover: true,
+ reactive: true,
+ can_focus: true
+ });
+
+ this.entry.set_primary_icon(new St.Icon({ style_class: 'search-entry-icon',
+ icon_name: this.params.primaryIconName }));
+
+ this.entry.clutter_text.connect('text-changed', this._onTextChanged.bind(this));
+ this.entry.clutter_text.connect('activate', this._onTextActivated.bind(this));
+
+ this.clearIcon = new St.Icon({
+ style_class: 'search-entry-icon',
+ icon_name: 'edit-clear-symbolic'
+ });
+ this.entry.connect('secondary-icon-clicked', this._reset.bind(this));
+
+ getActor(this.item).add(this.entry, { expand: true });
+ getActor(this.item).connect('notify::mapped', (actor) => {
+ if (actor.mapped) {
+ this.entry.set_text(this.params.initialTextGetter());
+ this.entry.clutter_text.grab_key_focus();
+ }
+ });
+ },
+
+ _setError: function(hasError) {
+ if (hasError)
+ this.entry.add_style_class_name('draw-on-your-screen-menu-entry-error');
+ else
+ this.entry.remove_style_class_name('draw-on-your-screen-menu-entry-error');
+ },
+
+ _reset: function() {
+ this.entry.text = '';
+ this.entry.clutter_text.set_cursor_visible(true);
+ this.entry.clutter_text.set_selection(0, 0);
+ this._setError(false);
+ },
+
+ _onTextActivated: function(clutterText) {
+ let text = clutterText.get_text();
+ if (text.length == 0)
+ return;
+ if (this._getIsInvalid())
+ return;
+ this._reset();
+ this.params.entryActivateCallback(text);
+ },
+
+ _onTextChanged: function(clutterText) {
+ let text = clutterText.get_text();
+ this.entry.set_secondary_icon(text.length ? this.clearIcon : null);
+
+ if (text.length)
+ this._setError(this._getIsInvalid());
+ },
+
+ _getIsInvalid: function() {
+ for (let i = 0; i < this.params.invalidStrings.length; i++) {
+ if (this.entry.text.indexOf(this.params.invalidStrings[i]) != -1)
+ return true;
+ }
+
+ return false;
+ }
+});
+
+
diff --git a/extension.js b/extension.js
index 635c99a..7e83e57 100644
--- a/extension.js
+++ b/extension.js
@@ -113,7 +113,7 @@ var AreaManager = new Lang.Class({
onPersistentSettingChanged: function() {
if (this.settings.get_boolean('persistent-drawing'))
- this.areas[Main.layoutManager.primaryIndex].saveAsJson();
+ this.areas[Main.layoutManager.primaryIndex].savePersistent();
},
updateIndicator: function() {
@@ -136,8 +136,8 @@ var AreaManager = new Lang.Class({
let monitor = this.monitors[i];
let container = new St.Widget({ name: 'drawOnYourSreenContainer' + i });
let helper = new Draw.DrawingHelper({ name: 'drawOnYourSreenHelper' + i }, monitor);
- let load = i == Main.layoutManager.primaryIndex && this.settings.get_boolean('persistent-drawing');
- let area = new Draw.DrawingArea({ name: 'drawOnYourSreenArea' + i }, monitor, helper, load);
+ let loadPersistent = i == Main.layoutManager.primaryIndex && this.settings.get_boolean('persistent-drawing');
+ let area = new Draw.DrawingArea({ name: 'drawOnYourSreenArea' + i }, monitor, helper, loadPersistent);
container.add_child(area);
container.add_child(helper);
@@ -161,6 +161,9 @@ var AreaManager = new Lang.Class({
'delete-last-element': this.activeArea.deleteLastElement.bind(this.activeArea),
'smooth-last-element': this.activeArea.smoothLastElement.bind(this.activeArea),
'save-as-svg': this.activeArea.saveAsSvg.bind(this.activeArea),
+ 'save-as-json': this.activeArea.saveAsJson.bind(this.activeArea),
+ 'open-previous-json': this.activeArea.loadPreviousJson.bind(this.activeArea),
+ 'open-next-json': this.activeArea.loadNextJson.bind(this.activeArea),
'toggle-background': this.activeArea.toggleBackground.bind(this.activeArea),
'toggle-square-area': this.activeArea.toggleSquareArea.bind(this.activeArea),
'increment-line-width': () => this.activeArea.incrementLineWidth(1),
@@ -223,7 +226,7 @@ var AreaManager = new Lang.Class({
for (let i = 0; i < this.areas.length; i++)
this.areas[i].erase();
if (this.settings.get_boolean('persistent-drawing'))
- this.areas[Main.layoutManager.primaryIndex].saveAsJson();
+ this.areas[Main.layoutManager.primaryIndex].savePersistent();
},
togglePanelAndDockOpacity: function() {
diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot
index e579156..d19a5a8 100644
--- a/locale/draw-on-your-screen.pot
+++ b/locale/draw-on-your-screen.pot
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Draw On Your Screen VERSION\n"
"Report-Msgid-Bugs-To: https://framagit.org/abakkk/DrawOnYourScreen/issues\n"
-"POT-Creation-Date: 2019-03-04 16:40+0100\n"
+"POT-Creation-Date: 2020-01-03 08:00+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -108,6 +108,12 @@ msgstr ""
msgid "Color"
msgstr ""
+msgid "Open drawing"
+msgstr ""
+
+msgid "Save drawing"
+msgstr ""
+
#: prefs.js
msgid "Preferences"
@@ -196,6 +202,16 @@ msgstr ""
msgid "Square drawing area"
msgstr ""
+msgid "Open previous drawing"
+msgstr ""
+
+msgid "Open next drawing"
+msgstr ""
+
+# already in draw.js
+#msgid "Save drawing"
+#msgstr ""
+
msgid "Save drawing as a SVG file"
msgstr ""
diff --git a/prefs.js b/prefs.js
index 2488cf3..6ec03a4 100644
--- a/prefs.js
+++ b/prefs.js
@@ -68,6 +68,9 @@ var INTERNAL_KEYBINDINGS = {
'toggle-background': "Add a drawing background",
'toggle-square-area': "Square drawing area",
'-separator-5': '',
+ 'open-previous-json': "Open previous drawing",
+ 'open-next-json': "Open next drawing",
+ 'save-as-json': "Save drawing",
'save-as-svg': "Save drawing as a SVG file",
'open-stylesheet': "Open stylesheet.css",
'toggle-help': "Show help"
diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled
index 329554d..563aa40 100644
Binary files a/schemas/gschemas.compiled and b/schemas/gschemas.compiled differ
diff --git a/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml b/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml
index 9864222..58fa1ad 100644
--- a/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml
+++ b/schemas/org.gnome.shell.extensions.draw-on-your-screen.gschema.xml
@@ -206,6 +206,21 @@
Save drawing as a svg file
Save drawing as a svg file
+
+ ["<Primary><Shift>s"]
+ Save drawing as a json file
+ Save drawing as a json file
+
+
+ ["<Primary>Left"]
+ Open previous json file
+ Open previous json file
+
+
+ ["<Primary>Right"]
+ Open next json file
+ Open next json file
+
["<Primary>F1"]
toggle help
diff --git a/stylesheet.css b/stylesheet.css
index 3e96dd6..6437e87 100644
--- a/stylesheet.css
+++ b/stylesheet.css
@@ -115,5 +115,29 @@
min-width: 3em;
text-align: right;
}
+
+.draw-on-your-screen-menu-entry-item {
+ padding-right: 1.15em; /* default 1.75em */
+ spacing: 0; /* default 12px */
+}
+
+.draw-on-your-screen-menu-entry {
+ border: none;
+ border-radius: 3px;
+ padding: 0.35em 0.57em;
+ width: 10em;
+}
+
+.draw-on-your-screen-menu-entry-error {
+ color: #f57900; /* upstream warning_color */
+}
+
+.draw-on-your-screen-menu-entry:focus {
+ padding: 0.35em 0.57em;
+}
+
+.draw-on-your-screen-menu-delete-button:hover {
+ color: #f57900;
+}