From af694382c8b21ad63a678810f46f52d7ee1131b3 Mon Sep 17 00:00:00 2001 From: abakkk Date: Sat, 19 Sep 2020 15:43:30 +0200 Subject: [PATCH 1/8] add color picker It wraps the upstream color picker, that is designed for DBus access. There is a shortcut and a menu button. --- area.js | 75 ++++++++++++++++-- extension.js | 1 + files.js | 1 + locale/draw-on-your-screen.pot | 11 ++- menu.js | 11 +++ schemas/gschemas.compiled | Bin 7464 -> 7550 bytes ...extensions.draw-on-your-screen.gschema.xml | 4 + shortcuts.js | 11 ++- 8 files changed, 105 insertions(+), 9 deletions(-) diff --git a/area.js b/area.js index dadec04..8945954 100644 --- a/area.js +++ b/area.js @@ -29,6 +29,7 @@ const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Lang = imports.lang; const Pango = imports.gi.Pango; +const Shell = imports.gi.Shell; const St = imports.gi.St; const System = imports.system; @@ -62,7 +63,7 @@ var Tools = Object.assign({ }, Shapes, Manipulations); Object.defineProperty(Tools, 'getNameOf', { enumerable: false }); -const getClutterColorFromString = function(string, fallback) { +const getColorFromString = function(string, fallback) { let [success, color] = Clutter.Color.from_string(string); color.toString = () => string; if (success) @@ -70,10 +71,14 @@ const getClutterColorFromString = function(string, fallback) { log(`${Me.metadata.uuid}: "${string}" color cannot be parsed.`); color = Clutter.Color.get_static(Clutter.StaticColor[fallback.toUpperCase()]); - color.toString = () => fallback.slice(0, 1).toUpperCase() + fallback.slice(1); + color.toString = () => fallback; return color; }; +const getColorFromRGB = function(red, green, blue) { + return getColorFromString(`rgb(${red},${green},${blue})`, 'White'); +}; + // 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. @@ -152,7 +157,7 @@ var DrawingArea = new Lang.Class({ set currentPalette(palette) { this._currentPalette = palette; - this.colors = palette[1].map(colorString => getClutterColorFromString(colorString, 'white')); + this.colors = palette[1].map(colorString => getColorFromString(colorString, 'White')); if (!this.colors[0]) this.colors.push(Clutter.Color.get_static(Clutter.StaticColor.WHITE)); }, @@ -254,9 +259,9 @@ var DrawingArea = new Lang.Class({ this.squareAreaSize = Me.drawingSettings.get_uint('square-area-size'); } - this.areaBackgroundColor = getClutterColorFromString(Me.drawingSettings.get_string('background-color'), 'black'); + this.areaBackgroundColor = getColorFromString(Me.drawingSettings.get_string('background-color'), 'Black'); - this.gridColor = getClutterColorFromString(Me.drawingSettings.get_string('grid-color'), 'gray'); + this.gridColor = getColorFromString(Me.drawingSettings.get_string('grid-color'), 'Gray'); if (Me.drawingSettings.get_boolean('grid-line-auto')) { this.gridLineSpacing = Math.round(this.monitor.width / (5 * GRID_TILES_HORIZONTAL_NUMBER)); this.gridLineWidth = this.gridLineSpacing / 20; @@ -1032,6 +1037,62 @@ var DrawingArea = new Lang.Class({ }); }, + _onColorPicked: function(color) { + let { red, green, blue } = color; + this.currentColor = getColorFromRGB(red, green, blue); + if (this.currentElement) { + this.currentElement.color = this.currentColor; + this._redisplay(); + } + this.emit('show-osd', Files.Icons.COLOR, String(this.currentColor), this.currentColor.to_string().slice(0, 7), -1, false); + this.initPointerCursor(); + }, + + pickColor: function() { + if (!Screenshot.PickPixel) + // GS 3.28- + return; + + // Translators: It is displayed in an OSD notification to ask the user to start picking, so it should use the imperative mood. + this.emit('show-osd', Files.Icons.COLOR_PICKER, pgettext("osd-notification", "Pick a color"), "", -1, false); + + try { + let screenshot = new Shell.Screenshot(); + let pickPixel = new Screenshot.PickPixel(screenshot); + + if (pickPixel.pickAsync) { + pickPixel.pickAsync().then(result => { + if (result instanceof Clutter.Color) { + // GS 3.38+ + this._onColorPicked(result); + } else { + // GS 3.36 + let graphenePoint = result; + screenshot.pick_color(graphenePoint.x, graphenePoint.y, (o, res) => { + let [, color] = screenshot.pick_color_finish(res); + this._onColorPicked(color); + }); + } + }).catch(() => this.initPointerCursor()); + } else { + // GS 3.34- + pickPixel.show(); + pickPixel.connect('finished', (pickPixel, coords) => { + if (coords) + screenshot.pick_color(...coords, (o, res) => { + let [, color] = screenshot.pick_color_finish(res); + this._onColorPicked(color); + }); + else + this.initPointerCursor(); + }); + } + } catch(e) { + log(`${Me.metadata.uuid}: color picker failed: ${e.message}`); + this.initPointerCursor(); + } + }, + toggleHelp: function() { if (this.helper.visible) { this.helper.hideHelp(); @@ -1120,7 +1181,7 @@ var DrawingArea = new Lang.Class({ elements.push(...JSON.parse(json.contents).map(object => { if (object.color) - object.color = getClutterColorFromString(object.color, 'white'); + object.color = getColorFromString(object.color, 'White'); if (object.font && typeof object.font == 'string') object.font = Pango.FontDescription.from_string(object.font); if (object.image) @@ -1227,7 +1288,7 @@ var DrawingArea = new Lang.Class({ this.elements.push(...JSON.parse(json.contents).map(object => { if (object.color) - object.color = getClutterColorFromString(object.color, 'white'); + object.color = getColorFromString(object.color, 'White'); if (object.font && typeof object.font == 'string') object.font = Pango.FontDescription.from_string(object.font); if (object.image) diff --git a/extension.js b/extension.js index 1363f17..6a232fc 100644 --- a/extension.js +++ b/extension.js @@ -225,6 +225,7 @@ const AreaManager = new Lang.Class({ 'save-as-json': this.activeArea.saveAsJson.bind(this.activeArea, true, null), 'open-previous-json': this.activeArea.loadPreviousJson.bind(this.activeArea), 'open-next-json': this.activeArea.loadNextJson.bind(this.activeArea), + 'pick-color': this.activeArea.pickColor.bind(this.activeArea), 'toggle-background': this.activeArea.toggleBackground.bind(this.activeArea), 'toggle-grid': this.activeArea.toggleGrid.bind(this.activeArea), 'toggle-square-area': this.activeArea.toggleSquareArea.bind(this.activeArea), diff --git a/files.js b/files.js index c91b404..dfefc5d 100644 --- a/files.js +++ b/files.js @@ -41,6 +41,7 @@ const ICON_NAMES = [ 'tool-ellipse', 'tool-line', 'tool-mirror', 'tool-move', 'tool-none', 'tool-polygon', 'tool-polyline', 'tool-rectangle', 'tool-resize', ]; const ThemedIconNames = { + COLOR_PICKER: 'color-select-symbolic', ENTER: 'applications-graphics', LEAVE: 'application-exit', GRAB: 'input-touchpad', UNGRAB: 'touchpad-disabled', OPEN: 'document-open', SAVE: 'document-save', diff --git a/locale/draw-on-your-screen.pot b/locale/draw-on-your-screen.pot index 3b2aa41..3ad0099 100644 --- a/locale/draw-on-your-screen.pot +++ b/locale/draw-on-your-screen.pot @@ -10,7 +10,7 @@ msgid "" msgstr "" "Project-Id-Version: Draw On Your Screen\n" "Report-Msgid-Bugs-To: https://framagit.org/abakkk/DrawOnYourScreen/issues\n" -"POT-Creation-Date: 2020-09-18 11:37+0200\n" +"POT-Creation-Date: 2020-09-19 15:32+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -47,6 +47,11 @@ msgstr "" msgid "Type your text and press %s" msgstr "" +#. Translators: It is displayed in an OSD notification to ask the user to start picking, so it should use the imperative mood. +msgctxt "osd-notification" +msgid "Pick a color" +msgstr "" + #. Translators: "released" as the opposite of "grabbed" msgid "Keyboard and pointer released" msgstr "" @@ -284,6 +289,10 @@ msgstr "" msgid "Color" msgstr "" +#. Translators: It is displayed in a menu button tooltip or as a shortcut action description, so it should NOT use the imperative mood. +msgid "Pick a color" +msgstr "" + msgid "Add to images" msgstr "" diff --git a/menu.js b/menu.js index 53364bf..7d00347 100644 --- a/menu.js +++ b/menu.js @@ -483,6 +483,17 @@ var DrawingMenu = new Lang.Class({ item.icon.set_gicon(icon); item.icon.set_style(`color:${this.area.currentColor.to_string().slice(0, 7)};`); + if (GS_VERSION >= '3.30') { + let colorPickerCallback = () => { + this.close(); + this.area.pickColor(); + }; + // Translators: It is displayed in a menu button tooltip or as a shortcut action description, so it should NOT use the imperative mood. + let colorPickerButton = new ActionButton(_("Pick a color"), Files.Icons.COLOR_PICKER, colorPickerCallback, null, true); + let index = getActor(item).get_children().length - 1; + getActor(item).insert_child_at_index(colorPickerButton, index); + } + item.menu.itemActivated = item.menu.close; this._populateColorSubMenu(); diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled index 76b17efbbdb4dbb3ddec71fd0a4fff0ce6d9962b..ec56cc35f34e5c54a706cbe5bd5df3c8b1a77293 100644 GIT binary patch delta 2642 zcmZved2AF_0LEX-VK3Tk_w4Rqx24i8FtDYx9CB1BSH;*0B=v~2w9|IWc4yh6?SgjO zgeXQys?R7G2%ynaLqY{ZJWvV=Nn=9N1dpmQ0iuQ=B*j031OtBG?(`tK$*e~@Paaf#n5po;41+qN472R+Qtc;$P;3g=oCZH zAHWQ2T!;tw@j{3#ViY=vDfQGSzp(&&85SW-S3>uh)0>gIp(+@TyndQFeID{E zXgx4(PP@mPo_P@32ln4MeT#Wt92W|19E1J{>OJS)<_&swJO)j%3eoP}Sizh=8@UNu z2-bGr8evY)nR%hLV4acEGjD+Qg7Vk0zh-%Q4k&&Q=3`K8RG?>t&!MAW$xZWdR-n&C zJ_enM7k=W~0}kf&9H<+*1w>bEKgpb)8!Fr>8dbZySJ!TVe(X(&xFe~5!iwN%q zdJSwaD$ujS7}R7J;??8xMp>TTjob@;0vtU!*TbBi1FDDifsIBx^lYaeItY$6oM}lx z|A`BZbQs}JV3SdSo)xY`(;Qex7vKAe9nfbWcQ6B+jq>y?zX{p~V&}JBXL)+I-wS;Q z)EMRIXT@=20A?8E*`ilifu0qvKyL$o-HtWP>G_0A_|{bLPt92`bNU?QRnR(c|GT-Afni(u&Go=kon^fQo;Ky6MToX;gY zpl1gj=yLGFQuRx=LvKZ13vC2r1O5lh=}pMHp+lf5_wV>^-rxmS^$7GTxDjw(W=_vl zdKa3G-6gX3QZ;jWJ|Q=>44i!G$V<%Wrz2kmtpz!E_7*dzXFF}s7^pvZ<^$$27v4V` z>PL7S%-?q;(E&YYHV8crs%s7>a(dQJzs&%|U&yn^*-vqnvl`G8YnGZm}0GEGxE0sCD6Zt4~47l=- zCY}U6pTu2gI`(AcjuK*d`YhxQXen4bqwJ#$-r$7|H>#j@VEgWybD7igUA94cfYu$< zLlHHitKrU8+!sQAV{zay^0456zypK7Y=H8KLxcPDbb!GKEaJqzUI9%9;|Gcfp1Uyi z7lAJhk1ctixS^SVX8`y5g#cTDcm(husRZ+$#~+?Zg#bIIm#v4 z^E*u*AakLQf~9~54^OEAfbBkUm@I+HT#H2>c5IMW^K1pxnqP}(=-ks7F4cotKn;ep zPM;nPtLE3X_C zQ(N=^#uthC6LXdwj{KadLs*Un{k<|rUUS&+@5RJ0I%Scwx;fc`-xtu@bzk72N7jzT z!RbWw)>gl!wrT$0L-#k7VZBe8ujcprf?;j^=}&Z|$vyURxjyxAIcl>t7_GI2e9g&e zPL8P6=+GEE-zEq4p9Z7j<&5e3{C+hQP0n7(Y-^odV|K}z=3IU>Q)b+P2mhx_nSAFv zXUvP_A!~{J)snV&ViG|uXk69L&^YfyCX8oaI?vd}COXRcmQkh|2@Gn52 B3b+6O delta 2582 zcmaKue{2&~0LO3OWUOQBwyy29Hz;H52lKGFA20$8VaQlCf`d2=sAapg9bK=sw_p5N zB_X0wlIb%#l?A5JOrv3l5#4OQ2z-Y1%Q2axgV!-eBdK99GO+Nj8@B7}p z_wMf9y?1@-=+eRGG6s+Azh+A=mQueA$TW*gD7n)j3!42u*ie>p8@cerejEAlg`fyH zEnJe6l-xKVGVoY6IncQlWzb4c1?~b1fgeUyz=JdJ9 zo1mLP>(H?`nA5Z47<3QVeyv(zPG68j!4S;bK)I^KSwNqI{3Gb+;P3;(yO`78fqWb~ z3AQX&PcWx1LM}Pb5g6Dzc7Qp(jNA`>2waSM&N8PjKpui7`|-DLWWfXr=zSsLGkqW%@gQ(1ryNU!P6IyUS>n|3UW7Q(u1J-ncPp9)0syJpnJf}CpU~Sr{~-nO2WJc-2UaOSwPQ*PD8%|c~^IqGN)%g z3H=jv`q$Pnr{|>0!Yd?$cfZ*0W=_v5_CvRT{xdC;%<0X^2z?492bz_iSil3Ga0vP% zC~$V4XHL%(rl2$Nj;`vvJkFe+bHWW>0y6PPcuVOe>3%cI!Taoaq(og(>4bhu(1$qK>FW+>S9nf>2JDE&Qdl+&)K^ks)74%4hY^H5OfGUdg@Gs_33%%#-L}wTB|-i z>yJaP0AuG@=47YmK(p|Llau)SyZNM_4ax9%D5!wegC?sXdN$Mq4S||Vg(Iv_&xW3a zz691;_32rE6#5xxwsLyr6VNG^8P6Vdupu6Ba@p~XtpH-o1p{bET!cbwmw5Y*)jb<#(5FaSM!t_bYT0-FtV?~ElAU^h#iyhu_ z@w`$gvWw@4Mc%UFm>%hmgrl082nAx=?Y(tNHu0LXRM_Rh?3V6rMm*4FNQ(HuL_e=l)$yO87i+Ps7L>M zG5uD(g|UDU5?{+rj_zo?ZVviZV>?81agAb)utSd;YOfX!hK%%O^@@^Wzj#%y5?xBE zSnqXZr;GZ;UygEdr_&{TPStK*5}QaE{kW#xZdr^Ky2Wf~t~gg%=ZoRlX(}FTP*ZW` zTEZMky0sqRDXL3yh2^r(ZNV(SRhO$RS7ENSb)W=rdoTmc1La@=;A*UbyTLu625>rY zb;jyoZZ9zFByKzS!`DNa3Gk9iTNOI- zI_HYG?8L?>eV;Y-yUSIx%4&aGply3Fu48JTWB$?=x80m)sVB=frc)0`)8(DwTUVW! vC|a0j%|x@~B^dr1w>hwkdhw=vo=cBuQ8gCVdct~lLhVfGQL`V>;E?|Tr7Z@< 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 d7a9158..b7abae0 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 @@ -164,6 +164,10 @@ ["<Primary>v"] Add images from the clipboard + + KP_0','0']]]> + Pick a color + ["<Primary><Shift>z"] Redo last brushstroke diff --git a/shortcuts.js b/shortcuts.js index df753d4..2b2d1ac 100644 --- a/shortcuts.js +++ b/shortcuts.js @@ -46,7 +46,7 @@ var INTERNAL_KEYBINDINGS = [ ['undo', 'redo', 'delete-last-element', 'smooth-last-element'], ['select-none-shape', 'select-line-shape', 'select-ellipse-shape', 'select-rectangle-shape', 'select-polygon-shape', 'select-polyline-shape', 'select-text-shape', 'select-image-shape', 'select-move-tool', 'select-resize-tool', 'select-mirror-tool'], - ['switch-fill', 'switch-fill-rule', 'switch-color-palette', 'switch-color-palette-reverse'], + ['switch-fill', 'switch-fill-rule', 'switch-color-palette', 'switch-color-palette-reverse', 'pick-color'], ['increment-line-width', 'increment-line-width-more', 'decrement-line-width', 'decrement-line-width-more', 'switch-linejoin', 'switch-linecap', 'switch-dash'], ['switch-font-family', 'switch-font-family-reverse', 'switch-font-weight', 'switch-font-style', 'switch-text-alignment'], @@ -55,6 +55,15 @@ var INTERNAL_KEYBINDINGS = [ ['open-next-json', 'open-previous-json', 'save-as-json', 'export-to-svg', 'open-preferences', 'toggle-help'], ]; +if (GS_VERSION < '3.30') { + // Remove 'pick-color' keybinding. + INTERNAL_KEYBINDINGS.forEach(settingKeys => { + let index = settingKeys.indexOf('pick-color'); + if (index != -1) + settingKeys.splice(index, 1); + }); +} + if (GS_VERSION < '3.36') { // Remove 'open-preferences' keybinding. INTERNAL_KEYBINDINGS.forEach(settingKeys => { From 737bc856c4d1a486d77d38b8fdb1ceb2da8896e8 Mon Sep 17 00:00:00 2001 From: abakkk Date: Sun, 20 Sep 2020 04:54:01 +0200 Subject: [PATCH 2/8] color-picker extension support The color picker use the ["Color Picker" extensions](https://github.com/tuberry/color-picker) if it is available thanks to `pickAsync` "api". In comparaison to the GS color picker, it provides a colored cursor and a menu to adjust the color. --- area.js | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/area.js b/area.js index 8945954..5e1fecc 100644 --- a/area.js +++ b/area.js @@ -50,6 +50,7 @@ const SVG_DEBUG_EXTENDS = false; const TEXT_CURSOR_TIME = 600; // ms const ELEMENT_GRABBER_TIME = 80; // ms, default is about 16 ms const GRID_TILES_HORIZONTAL_NUMBER = 30; +const COLOR_PICKER_EXTENSION_UUID = 'color-picker@tuberry'; const { Shapes, Transformations } = Elements; const { DisplayStrings } = Menu; @@ -75,10 +76,6 @@ const getColorFromString = function(string, fallback) { return color; }; -const getColorFromRGB = function(red, green, blue) { - return getColorFromString(`rgb(${red},${green},${blue})`, 'White'); -}; - // 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. @@ -1038,8 +1035,10 @@ var DrawingArea = new Lang.Class({ }, _onColorPicked: function(color) { - let { red, green, blue } = color; - this.currentColor = getColorFromRGB(red, green, blue); + if (color instanceof Clutter.Color) + color = color.to_string().slice(0, -2); + + this.currentColor = getColorFromString(color); if (this.currentElement) { this.currentElement.color = this.currentColor; this._redisplay(); @@ -1056,6 +1055,20 @@ var DrawingArea = new Lang.Class({ // Translators: It is displayed in an OSD notification to ask the user to start picking, so it should use the imperative mood. this.emit('show-osd', Files.Icons.COLOR_PICKER, pgettext("osd-notification", "Pick a color"), "", -1, false); + let extension = Main.extensionManager && Main.extensionManager.lookup(COLOR_PICKER_EXTENSION_UUID); + if (extension && extension.state == ExtensionUtils.ExtensionState.ENABLED && extension.stateObj && extension.stateObj.pickAsync) { + extension.stateObj.pickAsync().then(result => { + if (typeof result == 'string') + this._onColorPicked(result); + else + this.initPointerCursor(); + }).catch(e => { + this.initPointerCursor(); + }); + + return; + } + try { let screenshot = new Shell.Screenshot(); let pickPixel = new Screenshot.PickPixel(screenshot); From dd18ca305179fd47e4f74af48ba329597fa543f5 Mon Sep 17 00:00:00 2001 From: abakkk Date: Sun, 20 Sep 2020 07:36:23 +0200 Subject: [PATCH 3/8] bunch of menu optimizations * A minimum width for the menu, the same as the aggregate menu, in order to prevent the menu with from growing on submenu open. * A maximum width for uncontrolled content (font, image and drawing names). Same value and same reason. * Style the font family labels in idle loop to open the submenu fastly. * Add the drawing gicons in idle loop to open the submenu smoothly. --- files.js | 11 ++++++++++- menu.js | 25 ++++++++++++++++++++++--- stylesheet.css | 6 ++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/files.js b/files.js index dfefc5d..408d078 100644 --- a/files.js +++ b/files.js @@ -50,7 +50,16 @@ const ThemedIconNames = { TOOL_IMAGE: 'insert-image', TOOL_TEXT: 'insert-text', }; -var Icons = {}; +var Icons = { + get FAKE() { + if (!this._fake) { + let bytes = new GLib.Bytes(''); + this._fake = Gio.BytesIcon.new(bytes); + } + + return this._fake; + } +}; ICON_NAMES.forEach(name => { Object.defineProperty(Icons, name.toUpperCase().replace(/-/gi, '_'), { diff --git a/menu.js b/menu.js index 7d00347..1185dc5 100644 --- a/menu.js +++ b/menu.js @@ -523,6 +523,7 @@ var DrawingMenu = new Lang.Class({ item.icon.set_gicon(icon); item.menu.itemActivated = item.menu.close; + item.menu.actor.add_style_class_name('draw-on-your-screen-menu-ellipsized'); item.menu.openOld = item.menu.open; item.menu.open = (animate) => { @@ -532,8 +533,12 @@ var DrawingMenu = new Lang.Class({ item.label.set_text(DisplayStrings.getFontFamily(family)); this.area.currentFontFamily = family; }); + if (FONT_FAMILY_STYLE) - subItem.label.set_style(`font-family:${family}`); + GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { + subItem.label.set_style(`font-family:${family}`); + }); + getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); }); } @@ -552,6 +557,7 @@ var DrawingMenu = new Lang.Class({ item.update(); item.menu.itemActivated = item.menu.close; + item.menu.actor.add_style_class_name('draw-on-your-screen-menu-ellipsized'); item.menu.openOld = item.menu.open; item.menu.open = (animate) => { @@ -560,7 +566,13 @@ var DrawingMenu = new Lang.Class({ let subItem = item.menu.addAction(image.toString(), () => { this.area.currentImage = image; item.update(); - }, image.thumbnailGicon || undefined); + }, Files.Icons.FAKE); + + GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { + if (subItem.setIcon && image.thumbnailGicon) + subItem.setIcon(image.thumbnailGicon); + }); + getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); }); } @@ -574,6 +586,7 @@ var DrawingMenu = new Lang.Class({ _addDrawingNameItem: function(menu) { this.drawingNameMenuItem = new PopupMenu.PopupMenuItem('', { reactive: false, activate: false }); this.drawingNameMenuItem.setSensitive(false); + getActor(this.drawingNameMenuItem).add_style_class_name('draw-on-your-screen-menu-ellipsized'); menu.addMenuItem(this.drawingNameMenuItem); this._updateDrawingNameMenuItem(); }, @@ -595,6 +608,7 @@ var DrawingMenu = new Lang.Class({ item.icon.set_icon_name(icon); item.menu.itemActivated = item.menu.close; + item.menu.actor.add_style_class_name('draw-on-your-screen-menu-ellipsized'); item.menu.openOld = item.menu.open; item.menu.open = (animate) => { @@ -616,7 +630,12 @@ var DrawingMenu = new Lang.Class({ this.area.loadJson(json); this._updateDrawingNameMenuItem(); this._updateActionSensitivity(); - }, json.gicon); + }, Files.Icons.FAKE); + + GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { + if (subItem.setIcon) + subItem.setIcon(json.gicon); + }); subItem.label.get_clutter_text().set_use_markup(true); getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment); diff --git a/stylesheet.css b/stylesheet.css index 768f8ca..edc6e7d 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -37,6 +37,7 @@ .draw-on-your-screen-menu { font-size: 0.97em; /* default: 1em */ + min-width: 21em; /* like the aggregate menu */ } .draw-on-your-screen-menu .popup-menu-item { @@ -49,6 +50,11 @@ padding-top: 0.03em; } +/* max size for uncontrolled contents (font, image and drawing names) */ +.draw-on-your-screen-menu-ellipsized { + max-width: 21em; +} + .draw-on-your-screen-menu .toggle-switch { height: 1.35em; /* default: 22px */ } From 4cc0c9e02849296e82a813ec03b44675006724e8 Mon Sep 17 00:00:00 2001 From: abakkk Date: Mon, 21 Sep 2020 15:21:12 +0200 Subject: [PATCH 4/8] Mention Color Picker extension in the readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 88f8f48..82e1cf7 100644 --- a/README.md +++ b/README.md @@ -52,3 +52,7 @@ Then save your beautiful work by taking a screenshot. [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`). +* Color Picker extension: + +If the GNOME Shell built-in color picker is too basic for you, have a look at the [Color Picker extension](https://extensions.gnome.org/extension/3396/color-picker), which let's you select the pixel accurately, preview the color and adjust its values. Once installed and enabled, it will be transparently integrated into DrawOnYourScreen. + From 27e194905b8a94cd04a359b43cf8a11f1f2350ba Mon Sep 17 00:00:00 2001 From: abakkk Date: Tue, 22 Sep 2020 17:31:48 +0200 Subject: [PATCH 5/8] add gpl import * Import palettes from a .gpl file (one palette for each column). * Add possibility to use more than 9 palettes. * Color display names support. * color.toJSON for svg and drawing saves, color.toString for display. --- area.js | 9 +- elements.js | 10 +- gimpPaletteParser.js | 95 ++++++++++++++++++ prefs.js | 53 +++++++--- schemas/gschemas.compiled | Bin 7550 -> 7902 bytes ...extensions.draw-on-your-screen.gschema.xml | 12 +-- 6 files changed, 152 insertions(+), 27 deletions(-) create mode 100644 gimpPaletteParser.js diff --git a/area.js b/area.js index 5e1fecc..9192106 100644 --- a/area.js +++ b/area.js @@ -64,14 +64,19 @@ var Tools = Object.assign({ }, Shapes, Manipulations); Object.defineProperty(Tools, 'getNameOf', { enumerable: false }); +// toJSON provides a string suitable for SVG color attribute whereas +// toString provides a string suitable for displaying the color name to the user. const getColorFromString = function(string, fallback) { - let [success, color] = Clutter.Color.from_string(string); - color.toString = () => string; + let [colorString, displayName] = string.split(':'); + let [success, color] = Clutter.Color.from_string(colorString); + color.toJSON = () => colorString; + color.toString = () => displayName || colorString; if (success) return color; log(`${Me.metadata.uuid}: "${string}" color cannot be parsed.`); color = Clutter.Color.get_static(Clutter.StaticColor[fallback.toUpperCase()]); + color.toJSON = () => fallback; color.toString = () => fallback; return color; }; diff --git a/elements.js b/elements.js index 82338dd..52ea4c4 100644 --- a/elements.js +++ b/elements.js @@ -113,7 +113,7 @@ const _DrawingElement = new Lang.Class({ toJSON: function() { return { shape: this.shape, - color: this.color.toString(), + color: this.color, line: this.line, dash: this.dash, fill: this.fill, @@ -314,7 +314,7 @@ const _DrawingElement = new Lang.Class({ _drawSvg: function(transAttribute, bgcolorString) { let row = "\n "; let points = this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100]); - let color = this.eraser ? bgcolorString : this.color.toString(); + let color = this.eraser ? bgcolorString : this.color.toJSON(); let fill = this.fill && !this.isStraightLine; let attributes = this.eraser ? `class="eraser" ` : ''; @@ -633,7 +633,7 @@ const TextElement = new Lang.Class({ return { shape: this.shape, - color: this.color.toString(), + color: this.color, eraser: this.eraser, transformations: this.transformations, text: this.text, @@ -703,7 +703,7 @@ const TextElement = new Lang.Class({ _drawSvg: function(transAttribute, bgcolorString) { let row = "\n "; let [x, y, height] = [Math.round(this.x*100)/100, Math.round(this.y*100)/100, Math.round(this.height*100)/100]; - let color = this.eraser ? bgcolorString : this.color.toString(); + let color = this.eraser ? bgcolorString : this.color.toJSON(); let attributes = this.eraser ? `class="eraser" ` : ''; if (this.points.length == 2) { @@ -775,7 +775,7 @@ const ImageElement = new Lang.Class({ toJSON: function() { return { shape: this.shape, - color: this.color.toString(), + color: this.color, fill: this.fill, eraser: this.eraser, transformations: this.transformations, diff --git a/gimpPaletteParser.js b/gimpPaletteParser.js new file mode 100644 index 0000000..c1eb139 --- /dev/null +++ b/gimpPaletteParser.js @@ -0,0 +1,95 @@ +/* jslint esversion: 6 */ +/* exported parseFile */ + +/* + * Copyright 2019 Abakkk + * + * This file is part of DrawOnYourScreen, a drawing extension for GNOME Shell. + * https://framagit.org/abakkk/DrawOnYourScreen + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +const ByteArray = imports.byteArray; + +/* + * [ + * [ + * 'palette name 1', // a palette for each column + * [ + * 'rgb(...)', + * 'rgb(...):color display name', // the optional name separated with ':' + * ... + * ] + * ], + * [ + * 'palette name 2', + * [...] + * ], + * ... + * ] +*/ + +function parse(contents) { + let lines = contents.split('\n'); + let line, name, columnNumber; + + line = lines.shift(); + if (!line || !line.startsWith('GIMP Palette')) + log("Missing magic header"); + + line = lines.shift(); + if (line.startsWith('Name:')) { + name = line.slice(5).trim() || file.get_basename(); + line = lines.shift(); + } + if (line.startsWith('Columns:')) { + columnNumber = Number(line.slice(8).trim()) || 1; + line = lines.shift(); + } + + let columns = (new Array(columnNumber)).fill(null).map(() => []); + + lines.forEach((line, index) => { + if (!line || line.startsWith('#')) + return; + + line = line.split('#')[0].trim(); + + let [, color, displayName] = line.split(/(^[\d\s]+)/); + + let values = color.trim().split(/\D+/gi).filter(value => value >= 0 && value <= 255); + if (values.length < 3) + return; + + let string = `rgb(${values[0]},${values[1]},${values[2]})`; + if (displayName.trim()) + string += `:${displayName.trim()}`; + + columns[index % columns.length].push(string); + }); + + return columns.map((column, index) => [columnNumber > 1 ? `${name} ${index + 1}` : name, column]); +} + +function parseFile(file) { + if (!file.query_exists(null)) + return []; + + let [, contents] = file.load_contents(null); + if (contents instanceof Uint8Array) + contents = ByteArray.toString(contents); + + return parse(contents); +} diff --git a/prefs.js b/prefs.js index 30369ea..ab266e2 100644 --- a/prefs.js +++ b/prefs.js @@ -31,6 +31,7 @@ const Gtk = imports.gi.Gtk; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); const Convenience = ExtensionUtils.getSettings && ExtensionUtils.initTranslations ? ExtensionUtils : Me.imports.convenience; +const GimpPaletteParser = Me.imports.gimpPaletteParser; const Shortcuts = Me.imports.shortcuts; const gettext = imports.gettext.domain(Me.metadata['gettext-domain']).gettext; const _ = function(string) { @@ -150,26 +151,32 @@ const DrawingPage = new GObject.Class({ let palettesFrame = new Frame({ label: _("Palettes") }); box.add(palettesFrame); + let palettesFrameBox = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL }); + palettesFrame.add(palettesFrameBox); - let palettesScrolledWindow = new Gtk.ScrolledWindow({ vscrollbar_policy: Gtk.PolicyType.NEVER, margin_top: MARGIN / 2, margin_bottom: MARGIN / 2 }); - palettesFrame.add(palettesScrolledWindow); + let palettesScrolledWindow = new Gtk.ScrolledWindow({ vscrollbar_policy: Gtk.PolicyType.NEVER }); + palettesFrameBox.add(palettesScrolledWindow); + let palettesViewport = new Gtk.Viewport({ margin_top: MARGIN / 2, margin_bottom: MARGIN / 2 }); + palettesScrolledWindow.add(palettesViewport); this.palettesListBox = new Gtk.ListBox({ selection_mode: 0, hexpand: true }); this.palettesListBox.get_style_context().add_class('background'); this.palettesListBox.get_accessible().set_name(this.schema.get_key('palettes').get_summary()); this.palettesListBox.get_accessible().set_description(this.schema.get_key('palettes').get_description()); - palettesScrolledWindow.add(this.palettesListBox); + palettesViewport.add(this.palettesListBox); this.settings.connect('changed::palettes', this._updatePalettes.bind(this)); this._updatePalettes(); - this.addBox = new Gtk.Box(ROWBOX_MARGIN_PARAMS); - this.addBox.margin_bottom = MARGIN; // add space for the scrollbar + let addBox = new Gtk.Box(ROWBOX_MARGIN_PARAMS); let addButton = Gtk.Button.new_from_icon_name('list-add-symbolic', Gtk.IconSize.BUTTON); addButton.set_tooltip_text(_("Add a new palette")); - this.addBox.pack_start(addButton, true, true, 4); + addBox.pack_start(addButton, true, true, 4); addButton.connect('clicked', this._addNewPalette.bind(this)); - this.palettesListBox.add(this.addBox); - this.addBox.get_parent().set_activatable(false); + let importButton = Gtk.Button.new_from_icon_name('document-open-symbolic', Gtk.IconSize.BUTTON); + importButton.set_tooltip_text(_GTK("Select a File")); + addBox.pack_start(importButton, true, true, 4); + importButton.connect('clicked', this._importPalette.bind(this)); + palettesFrameBox.add(addBox); let areaFrame = new Frame({ label: _("Area") }); box.add(areaFrame); @@ -285,10 +292,9 @@ const DrawingPage = new GObject.Class({ _updatePalettes: function() { this.palettes = this.settings.get_value('palettes').deep_unpack(); - this.palettesListBox.get_children().filter(row=> row.get_child() != this.addBox) - .slice(this.palettes.length) + this.palettesListBox.get_children().slice(this.palettes.length) .forEach(row => this.palettesListBox.remove(row)); - let paletteBoxes = this.palettesListBox.get_children().map(row => row.get_child()).filter(child => child != this.addBox); + let paletteBoxes = this.palettesListBox.get_children().map(row => row.get_child()); this.palettes.forEach((palette, paletteIndex) => { let [name, colors] = palette; @@ -316,17 +322,20 @@ const DrawingPage = new GObject.Class({ paletteBox.get_parent().set_activatable(false); } - colors.splice(9); while (colors.length < 9) colors.push('transparent'); let colorsBox = paletteBox.get_children()[1]; let colorButtons = colorsBox.get_children(); colors.forEach((color, colorIndex) => { + let [colorString, displayName] = color.split(':'); if (colorButtons[colorIndex]) { - colorButtons[colorIndex].color_string = color; + colorButtons[colorIndex].color_string = colorString; + colorButtons[colorIndex].tooltip_text = displayName || null; } else { - let colorButton = new ColorStringButton({ color_string: color, use_alpha: true, show_editor: true, halign: Gtk.Align.START, hexpand: false }); + let colorButton = new ColorStringButton({ color_string: colorString, tooltip_text: displayName || null, + use_alpha: true, show_editor: true, + halign: Gtk.Align.START, hexpand: false }); colorButton.connect('notify::color-string', this._onPaletteColorChanged.bind(this, paletteIndex, colorIndex)); colorsBox.add(colorButton); } @@ -347,6 +356,8 @@ const DrawingPage = new GObject.Class({ _onPaletteColorChanged: function(paletteIndex, colorIndex, colorButton) { this.palettes[paletteIndex][1][colorIndex] = colorButton.get_rgba().to_string(); + if (colorButton.tooltip_text) + this.palettes[paletteIndex][1][colorIndex] += `:${colorButton.tooltip_text}`; this._savePalettes(); }, @@ -357,6 +368,20 @@ const DrawingPage = new GObject.Class({ this._savePalettes(); }, + _importPalette: function() { + let dialog = new Gtk.FileChooserNative({ action: Gtk.FileChooserAction.OPEN, title: _GTK("Select a File") }); + let filter = new Gtk.FileFilter(); + filter.set_name("GIMP Palette (*.gpl)"); + filter.add_pattern('*.gpl'); + dialog.add_filter(filter); + if (dialog.run() == Gtk.ResponseType.ACCEPT) { + let file = dialog.get_file(); + let palettes = GimpPaletteParser.parseFile(file); + palettes.forEach(palette => this.palettes.push(palette)); + this._savePalettes(); + } + }, + _removePalette: function(paletteIndex) { this.palettes.splice(paletteIndex, 1); this._savePalettes(); diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled index ec56cc35f34e5c54a706cbe5bd5df3c8b1a77293..208135f09a1df2f711e2561c9734980a5f39d9f6 100644 GIT binary patch delta 2671 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 Ll}vkbuY3Ln*FMcm 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 b7abae0..e13b393 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 @@ -100,12 +100,12 @@ [ - ("Palette", ["HotPink","Cyan","yellow","Orangered","Chartreuse","DarkViolet","White","Gray","Black"]), - ("GNOME HIG lighter", ["rgb(153,193,241)","rgb(143,240,164)","rgb(249,240,107)","rgb(255,190,111)","rgb(246,97,81)","rgb(220,138,221)","rgb(205,171,143)","rgb(255,255,255)","rgb(119,118,123)"]), - ("GNOME HIG light", ["rgb(98,160,241)","rgb(87,227,137)","rgb(248,228,92)","rgb(255,163,72)","rgb(237,51,59)","rgb(192,97,203)","rgb(181,131,90)","rgb(246,245,244)","rgb(94,92,100)"]), - ("GNOME HIG normal", ["rgb(53,132,228)","rgb(51,209,122)","rgb(246,211,45)","rgb(255,120,0)","rgb(224,27,36)","rgb(145,65,172)","rgb(152,106,68)","rgb(222,221,218)","rgb(61,56,70)"]), - ("GNOME HIG dark", ["rgb(28,113,216)","rgb(46,194,126)","rgb(245,194,17)","rgb(230,97,0)","rgb(192,28,40)","rgb(129,61,156)","rgb(134,94,60)","rgb(192,191,188)","rgb(36,31,49)"]), - ("GNOME HIG darker", ["rgb(26,095,180)","rgb(38,162,105)","rgb(229,165,10)","rgb(198,70,0)","rgb(165,29,45)","rgb(97,53,131)","rgb(99,69,44)","rgb(154,153,150)","rgb(0,0,0)"]) + ('Palette', ['HotPink', 'Cyan', 'yellow', 'Orangered', 'Chartreuse', 'DarkViolet', 'White', 'Gray', 'Black']), + ('GNOME HIG lighter', ['rgb(153,193,241):Blue 1', 'rgb(143,240,164):Green 1', 'rgb(249,240,107):Yellow 1', 'rgb(255,190,111):Orange 1', 'rgb(246,97,81):Red 1', 'rgb(220,138,221):Purple 1', 'rgb(205,171,143):Brown 1', 'rgb(255,255,255):Light 1', 'rgb(119,118,123):Dark 1']), + ('GNOME HIG light', ['rgb(98,160,234):Blue 2', 'rgb(87,227,137):Green 2', 'rgb(248,228,92):Yellow 2', 'rgb(255,163,72):Orange 2', 'rgb(237,51,59):Red 2', 'rgb(192,97,203):Purple 2', 'rgb(181,131,90):Brown 2', 'rgb(246,245,244):Light 2', 'rgb(94,92,100):Dark 2']), + ('GNOME HIG normal', ['rgb(53,132,228):Blue 3', 'rgb(51,209,122):Green 3', 'rgb(246,211,45):Yellow 3', 'rgb(255,120,0):Orange 3', 'rgb(224,27,36):Red 3', 'rgb(145,65,172):Purple 3', 'rgb(152,106,68):Brown 3', 'rgb(222,221,218):Light 3', 'rgb(61,56,70):Dark 3']), + ('GNOME HIG dark', ['rgb(28,113,216):Blue 4', 'rgb(46,194,126):Green 4', 'rgb(245,194,17):Yellow 4', 'rgb(230,97,0):Orange 4', 'rgb(192,28,40):Red 4', 'rgb(129,61,156):Purple 4', 'rgb(134,94,60):Brown 4', 'rgb(192,191,188):Light 4', 'rgb(36,31,49):Dark 4']), + ('GNOME HIG darker', ['rgb(26,95,180):Blue 5', 'rgb(38,162,105):Green 5', 'rgb(229,165,10):Yellow 5', 'rgb(198,70,0):Orange 5', 'rgb(165,29,45):Red 5', 'rgb(97,53,131):Purple 5', 'rgb(99,69,44):Brown 5', 'rgb(154,153,150):Light 5', 'rgb(0,0,0):Dark 5']) ] Color palettes From 532b9242c97ae1be22ebe21f26049f6aa122ffaf Mon Sep 17 00:00:00 2001 From: abakkk Date: Tue, 22 Sep 2020 18:22:41 +0200 Subject: [PATCH 6/8] image element, minor * image.toJson -> image.toJSON. * remove unused 'fill' and 'operator' properties. --- area.js | 1 - elements.js | 8 +------- files.js | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/area.js b/area.js index 9192106..f2165d1 100644 --- a/area.js +++ b/area.js @@ -630,7 +630,6 @@ var DrawingArea = new Lang.Class({ color: this.currentColor, eraser: eraser, image: this.currentImage, - operator: this.currentOperator, points: [] }); } else { diff --git a/elements.js b/elements.js index 52ea4c4..cd4ee90 100644 --- a/elements.js +++ b/elements.js @@ -767,19 +767,13 @@ const ImageElement = new Lang.Class({ Name: 'DrawOnYourScreenImageElement', Extends: _DrawingElement, - _init: function(params) { - params.fill = false; - this.parent(params); - }, - toJSON: function() { return { shape: this.shape, color: this.color, - fill: this.fill, eraser: this.eraser, transformations: this.transformations, - image: this.image.toJson(), + image: this.image, preserveAspectRatio: this.preserveAspectRatio, points: this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100]) }; diff --git a/files.js b/files.js index 408d078..924b006 100644 --- a/files.js +++ b/files.js @@ -97,7 +97,7 @@ var Image = new Lang.Class({ return this.displayName; }, - toJson: function() { + toJSON: function() { return { displayName: this.displayName, contentType: this.contentType, From 3c67bf3398d6f525a23fce9b7f05b5cc7e3f7e76 Mon Sep 17 00:00:00 2001 From: abakkk Date: Tue, 22 Sep 2020 23:21:48 +0200 Subject: [PATCH 7/8] colored SVG images Color (fill) SVG images by pressing the `Shift` key. --- area.js | 8 ++++---- elements.js | 9 +++++---- files.js | 49 ++++++++++++++++++++++++++++++++++++++++++------- menu.js | 2 -- 4 files changed, 51 insertions(+), 17 deletions(-) diff --git a/area.js b/area.js index f2165d1..7420c5c 100644 --- a/area.js +++ b/area.js @@ -603,7 +603,7 @@ var DrawingArea = new Lang.Class({ this._redisplay(); }, - _startDrawing: function(stageX, stageY, eraser) { + _startDrawing: function(stageX, stageY, shiftPressed) { let [success, startX, startY] = this.transform_stage_point(stageX, stageY); if (!success) @@ -617,7 +617,7 @@ var DrawingArea = new Lang.Class({ this.currentElement = new Elements.DrawingElement({ shape: this.currentTool, color: this.currentColor, - eraser: eraser, + eraser: shiftPressed, font: this.currentFont.copy(), // Translators: initial content of the text area text: pgettext("text-area-content", "Text"), @@ -628,7 +628,7 @@ var DrawingArea = new Lang.Class({ this.currentElement = new Elements.DrawingElement({ shape: this.currentTool, color: this.currentColor, - eraser: eraser, + colored: shiftPressed, image: this.currentImage, points: [] }); @@ -636,7 +636,7 @@ var DrawingArea = new Lang.Class({ this.currentElement = new Elements.DrawingElement({ shape: this.currentTool, color: this.currentColor, - eraser: eraser, + eraser: shiftPressed, fill: this.fill, fillRule: this.currentFillRule, line: { lineWidth: this.currentLineWidth, lineJoin: this.currentLineJoin, lineCap: this.currentLineCap }, diff --git a/elements.js b/elements.js index cd4ee90..85a4387 100644 --- a/elements.js +++ b/elements.js @@ -771,7 +771,7 @@ const ImageElement = new Lang.Class({ return { shape: this.shape, color: this.color, - eraser: this.eraser, + colored: this.colored, transformations: this.transformations, image: this.image, preserveAspectRatio: this.preserveAspectRatio, @@ -791,7 +791,7 @@ const ImageElement = new Lang.Class({ return; cr.save(); - this.image.setCairoSource(cr, x, y, width, height, this.preserveAspectRatio); + this.image.setCairoSource(cr, x, y, width, height, this.preserveAspectRatio, this.colored ? this.color.toJSON() : null); cr.rectangle(x, y, width, height); cr.fill(); cr.restore(); @@ -813,14 +813,15 @@ const ImageElement = new Lang.Class({ _drawSvg: function(transAttribute) { let points = this.points; let row = "\n "; - let attributes = this.eraser ? `class="eraser" ` : ''; + let attributes = ''; if (points.length == 2) { attributes += `fill="none"`; + let base64 = this.image.getBase64ForColor(this.colored ? this.color.toJSON() : null); row += ``; + `id="${this.image.displayName}" xlink:href="data:${this.image.contentType};base64,${base64}"/>`; } return row; diff --git a/files.js b/files.js index 924b006..4f4b6bb 100644 --- a/files.js +++ b/files.js @@ -83,6 +83,18 @@ Object.keys(ThemedIconNames).forEach(key => { }); }); +const replaceColor = function (contents, color) { + if (contents instanceof Uint8Array) + contents = ByteArray.toString(contents); + else + contents = contents.toString(); + + return contents.replace(/fill(?=="transparent"|="none"|:transparent|:none)/gi, 'filll') + .replace(/fill="[^"]+"/gi, `fill="${color}"`) + .replace(/fill:[^";]+/gi, `fill:${color};`) + .replace(/filll/gi, 'fill'); +}; + // Wrapper around image data. If not subclassed, it is used when loading in the area an image element for a drawing file (.json) // and it takes { displayName, contentType, base64, hash } as params. var Image = new Lang.Class({ @@ -122,6 +134,14 @@ var Image = new Lang.Class({ this._base64 = base64; }, + getBase64ForColor: function(color) { + if (!color || this.contentType != 'image/svg+xml') + return this.base64; + + let contents = GLib.base64_decode(this.base64); + return GLib.base64_encode(replaceColor(contents, color)); + }, + // hash is not used get hash() { if (!this._hash) @@ -133,7 +153,22 @@ var Image = new Lang.Class({ this._hash = hash; }, - get pixbuf() { + getBytesForColor: function(color) { + if (!color || this.contentType != 'image/svg+xml') + return this.bytes; + + let contents = this.bytes.get_data(); + return new GLib.Bytes(replaceColor(contents, color)); + }, + + getPixbuf: function(color) { + if (color) { + let stream = Gio.MemoryInputStream.new_from_bytes(this.getBytesForColor(color)); + let pixbuf = GdkPixbuf.Pixbuf.new_from_stream(stream, null); + stream.close(null); + return pixbuf; + } + if (!this._pixbuf) { let stream = Gio.MemoryInputStream.new_from_bytes(this.bytes); this._pixbuf = GdkPixbuf.Pixbuf.new_from_stream(stream, null); @@ -142,16 +177,16 @@ var Image = new Lang.Class({ return this._pixbuf; }, - getPixbufAtScale: function(width, height) { - let stream = Gio.MemoryInputStream.new_from_bytes(this.bytes); + getPixbufAtScale: function(width, height, color) { + let stream = Gio.MemoryInputStream.new_from_bytes(this.getBytesForColor(color)); let pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale(stream, width, height, true, null); stream.close(null); return pixbuf; }, - setCairoSource: function(cr, x, y, width, height, preserveAspectRatio) { - let pixbuf = preserveAspectRatio ? this.getPixbufAtScale(width, height) - : this.pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.BILINEAR); + setCairoSource: function(cr, x, y, width, height, preserveAspectRatio, color) { + let pixbuf = preserveAspectRatio ? this.getPixbufAtScale(width, height, color) + : this.getPixbuf(color).scale_simple(width, height, GdkPixbuf.InterpType.BILINEAR); Gdk.cairo_set_source_pixbuf(cr, pixbuf, x, y); } }); @@ -552,7 +587,7 @@ var Jsons = { var getDateString = function() { let date = GLib.DateTime.new_now_local(); - return `${date.format("%F")} ${date.format("%X")}`; + return `${date.format("%F")} ${date.format("%T")}`; }; var saveSvg = function(content) { diff --git a/menu.js b/menu.js index 1185dc5..ebad93b 100644 --- a/menu.js +++ b/menu.js @@ -315,8 +315,6 @@ var DrawingMenu = new Lang.Class({ this.lineSection.actor.visible = !isText && !isImage; this.fontSection.actor.visible = isText; this.imageSection.actor.visible = isImage; - this.colorItem.setSensitive(!isImage); - this.paletteItem.setSensitive(!isImage); this.fillItem.setSensitive(!isText && !isImage); this.fillSection.setSensitive(!isText && !isImage); From fba13926c42893d33a2ad49b9ea9c55268af44b2 Mon Sep 17 00:00:00 2001 From: abakkk Date: Tue, 22 Sep 2020 23:24:17 +0200 Subject: [PATCH 8/8] version -> 7 --- NEWS | 7 +++++++ metadata.json | 5 +++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index b4a505a..70bf954 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,13 @@ v6.4 - September 2020 ===================== +* Can import GIMP palettes. +* Can color SVG images. +* Integrate color picker (GS buit-in or Color Picker extension). + +v6.4 - September 2020 +===================== + * Prepare GS 3.38 compatibility. * Add tooltips to sub-menu buttons also. * Fix drawing directory monitoring when it does not exist yet. diff --git a/metadata.json b/metadata.json index de88d13..f9d26b9 100644 --- a/metadata.json +++ b/metadata.json @@ -15,7 +15,8 @@ "3.30", "3.32", "3.34", - "3.36" + "3.36", + "3.38" ], - "version": 6.4 + "version": 7 }