From af694382c8b21ad63a678810f46f52d7ee1131b3 Mon Sep 17 00:00:00 2001 From: abakkk Date: Sat, 19 Sep 2020 15:43:30 +0200 Subject: [PATCH] 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 => {