Merge branch 'dev' into 'master'
v6.2 See merge request abakkk/DrawOnYourScreen!13
This commit is contained in:
commit
0fe72b7746
14
NEWS
14
NEWS
|
|
@ -1,5 +1,15 @@
|
||||||
|
v6.2 - August 2020
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Show the text entry when ibusCandidatePopup is used
|
||||||
|
* Regroup first menu items
|
||||||
|
* Center grid overlay
|
||||||
|
* Image shape
|
||||||
|
* Extend font family choices
|
||||||
|
* Fix sub-menu scroll view adjustment
|
||||||
|
|
||||||
v6.1 - June 2020
|
v6.1 - June 2020
|
||||||
=================
|
================
|
||||||
|
|
||||||
* Fix empty media-keys settings case #28
|
* Fix empty media-keys settings case #28
|
||||||
* Fix label color in OSD and menu #31
|
* Fix label color in OSD and menu #31
|
||||||
|
|
@ -20,7 +30,7 @@ v6.1 - June 2020
|
||||||
* Attributes are now persistent through drawing mode toggling #27
|
* Attributes are now persistent through drawing mode toggling #27
|
||||||
|
|
||||||
v6 - March 2020
|
v6 - March 2020
|
||||||
=================
|
===============
|
||||||
|
|
||||||
* GS 3.36 compatibility
|
* GS 3.36 compatibility
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ Then save your beautiful work by taking a screenshot.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Basic shapes (rectangle, circle, ellipse, line, curve, text, free)
|
* Basic shapes (rectangle, circle, ellipse, line, curve, text, image, free)
|
||||||
* Basic transformations (move, rotate, resize, stretch, mirror, inverse)
|
* Basic transformations (move, rotate, resize, stretch, mirror, inverse)
|
||||||
* Smooth stroke
|
* Smooth stroke
|
||||||
* Draw over applications
|
* Draw over applications
|
||||||
|
|
@ -40,6 +40,10 @@ Then save your beautiful work by taking a screenshot.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
* Insertable images:
|
||||||
|
|
||||||
|
Add your images (jpeg, png, svg) to `~/.local/share/drawOnYourScreen/images/`.
|
||||||
|
|
||||||
* Screenshot Tool extension:
|
* Screenshot Tool extension:
|
||||||
|
|
||||||
[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`).
|
[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`).
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
<svg viewBox="0 0 576 576" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect fill="#555" stroke="none" x="99.06" y="116.46" width="426.79" height="88.9" transform="translate(-1.13,-26.30) translate(297.56,177.87) rotate(-17.00) translate(-297.56,-177.87) translate(-14.89,16.96)"/>
|
||||||
|
<rect fill="#555" stroke="none" x="382.92" y="219.17" width="137.39" height="55" transform="translate(-7.43,-27.23) translate(449.62,218.30) rotate(-16.48) translate(-449.62,-218.30) translate(-2,-19.20)"/>
|
||||||
|
<rect fill="#555" stroke="none" x="99.06" y="116.46" width="426.79" height="88.9" transform="translate(0, 284.75) rotate(180) scale(1, -1) rotate(-180) translate(0, -284.75) translate(-1.13,-26.30) translate(297.56,177.87) rotate(-17.00) translate(-297.56,-177.87) translate(-14.89,16.96)"/>
|
||||||
|
<rect fill="#555" stroke="none" x="382.92" y="219.17" width="137.39" height="55" transform="translate(0, 284.02) rotate(180) scale(1, -1) rotate(-180) translate(0, -284.02) translate(-7.43,-27.23) translate(449.62,218.30) rotate(-16.48) translate(-449.62,-218.30) translate(-2,-19.20)"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
<!-- https://commons.wikimedia.org/wiki/File:Bananas.svg -->
|
||||||
|
<!-- public domain -->
|
||||||
|
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://web.resource.org/cc/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="559.62469" height="373.29596" id="svg2" sodipodi:version="0.32" inkscape:version="0.44" version="1.0" sodipodi:docbase="C:\Documents and Settings\Elembis\My Documents\Images\Shartak\Shartak.com" sodipodi:docname="bananas3.svg" inkscape:export-filename="C:\Documents and Settings\Elembis\My Documents\Images\Shartak\Shartak.com\banana.png" inkscape:export-xdpi="25.797226" inkscape:export-ydpi="25.797226">
|
||||||
|
<defs id="defs4"/>
|
||||||
|
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" gridtolerance="10000" guidetolerance="10" objecttolerance="10" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="1" inkscape:cx="257.85399" inkscape:cy="231.60006" inkscape:document-units="px" inkscape:current-layer="layer1" inkscape:window-width="1024" inkscape:window-height="721" inkscape:window-x="-4" inkscape:window-y="-4"/>
|
||||||
|
<metadata id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="translate(-66.38047,-391.3222)">
|
||||||
|
<g id="g9893" inkscape:transform-center-x="88.0002" inkscape:transform-center-y="113.00018">
|
||||||
|
<path transform="translate(-17.85713,318.6479)" sodipodi:nodetypes="csccccc" id="path17976" d="M 543,119 C 543,119 547,177 531,189 C 515,201 397,320 397,320 L 419,345 L 562,248 L 573,121 L 543,119 z " style="fill:#ffc701;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
|
||||||
|
<path sodipodi:nodetypes="csccccc" id="path18863" d="M 525.14247,437.64826 C 525.14247,437.64826 529.14247,495.64826 513.14247,507.64826 C 497.14247,519.64826 379.14247,638.64826 379.14247,638.64826 L 401.14247,663.64826 L 544.14247,566.64826 L 555.14247,439.64826 L 525.14247,437.64826 z " style="opacity:0.7;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
|
||||||
|
</g>
|
||||||
|
<g id="g2783">
|
||||||
|
<path id="path6424" d="M 519.9114,399.65406 L 483.38679,454.45323 C 483.38679,454.45323 454.12626,461.72103 439.29443,470.94146 C 424.46259,480.16189 377.85779,514.19114 274.74769,506.89067 C 171.63759,499.59021 156.32242,477.55739 133.97629,476.75109 C 111.63016,475.9448 77.83775,479.82705 69.64709,486.06507 C 61.45643,492.3031 71.25003,511.09366 71.25003,511.09366 C 71.25003,511.09366 101.21736,578.4951 210.30564,606.14897 C 319.39391,633.80284 448.5453,576.70729 448.5453,576.70729 L 538.74205,411.07361 L 519.9114,399.65406 z " style="fill:#ffc701;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
|
||||||
|
<path id="path1895" d="M 517.00522,404.00971 L 483.38022,454.44721 C 483.38022,454.44721 454.1183,461.72678 439.28647,470.94721 C 424.45462,480.16764 377.86532,514.18518 274.75522,506.88471 C 171.64512,499.58425 156.3201,477.56601 133.97397,476.75971 C 111.62785,475.95343 77.85213,479.83419 69.66147,486.07221 C 69.2897,486.35536 68.96174,486.67778 68.66147,487.00971 C 87.94596,484.21174 116.37515,484.78053 136.38022,488.41596 C 174.77085,495.39252 209.97775,518.65573 299.50522,516.22846 C 426.73709,512.73998 441.6649,473.10304 459.38022,468.41596 C 484.00837,461.89991 530.48444,452.34626 517.00522,404.00971 z " style="fill:white;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;opacity:0.4"/>
|
||||||
|
<path id="path9086" d="M 106.96213,554.70796 C 128.49383,573.41467 161.44348,593.76255 210.30593,606.14918 C 222.07021,609.13143 234.0755,611.11073 246.11929,612.29962 L 284.83188,604.45269 C 220.5418,614.51673 142.31641,575.18761 106.96213,554.70796 z " style="opacity:0.4;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
|
||||||
|
</g>
|
||||||
|
<g id="g9941">
|
||||||
|
<path transform="translate(-17.85713,318.6479)" inkscape:transform-center-y="181" inkscape:transform-center-x="126" sodipodi:nodetypes="cssssssssc" id="path12644" d="M 546,120 C 546,120 567,175 561,189 C 555,203 520,283 455,313 C 390,343 223,339 219,331 C 215,323 219,407 320,438 C 421,469 550.08904,401.09271 573,379 C 602,351 657,279 641,225 C 625,171 613,187 604,172 C 595,157 585,128 585,116 C 585,104 547,120 546,120 z " style="opacity:1;fill:#ffc701;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
|
||||||
|
<path id="path8981" d="M 533.7244,527.63751 C 518.52971,557.28537 486.49949,608.85189 437.13065,631.63751 C 372.13066,661.63751 205.13065,657.63751 201.13065,649.63751 C 200.13065,647.63751 199.64628,651.40313 201.0369,658.63751 L 201.06815,658.76251 L 203.13065,665.63751 C 203.13064,665.63751 202.9682,665.65254 202.75565,665.73126 C 202.85933,666.09273 202.98841,666.48596 203.0994,666.85626 C 203.2463,667.34553 203.37675,667.82087 203.5369,668.32501 C 203.82494,669.23173 204.17292,670.18568 204.50565,671.13751 C 204.61749,671.45746 204.70108,671.78151 204.81815,672.10626 C 205.04405,672.73471 205.32226,673.36688 205.56815,674.01251 C 205.8517,674.75499 206.10087,675.49879 206.4119,676.26251 C 206.6323,676.80515 206.89597,677.36609 207.13065,677.91876 C 207.44564,678.65842 207.75805,679.38112 208.0994,680.13751 C 208.26157,680.49795 208.4311,680.86725 208.5994,681.23126 C 209.69117,683.59259 210.89044,686.02408 212.25565,688.51251 C 212.29126,688.57728 212.34485,688.63516 212.38065,688.70001 C 212.39669,688.72911 212.39582,688.76464 212.4119,688.79376 C 212.77389,689.44818 213.15496,690.10127 213.5369,690.76251 C 213.57214,690.82361 213.59523,690.88885 213.63065,690.95001 C 214.03425,691.64604 214.45442,692.3411 214.88065,693.04376 C 215.33612,693.79463 215.80496,694.56759 216.2869,695.32501 C 216.29464,695.33717 216.3104,695.34409 216.31815,695.35626 C 216.77977,696.08134 217.23774,696.81355 217.7244,697.54376 C 217.72759,697.54854 217.75246,697.53898 217.75565,697.54376 C 258.55477,715.44577 403.2244,681.10626 468.13065,633.63751 C 513.82083,600.22229 528.80654,553.79612 533.7244,527.63751 z " style="opacity:0.6;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
|
||||||
|
<path id="path17089" d="M 559.79937,430.17885 C 558.18509,430.20703 556.38497,430.42195 554.48687,430.74135 C 560.75792,449.99636 590.83405,543.70808 588.14312,561.6476 C 585.14312,581.6476 553.30943,654.34782 501.14312,685.6476 C 466.14312,706.6476 390.14312,739.6476 363.14312,744.6476 C 346.05557,747.81196 312.61613,752.46521 286.48687,751.11635 C 291.4351,753.10006 296.63167,754.95597 302.14312,756.6476 C 403.14312,787.6476 532.23216,719.74031 555.14312,697.6476 C 584.14312,669.6476 639.14312,597.6476 623.14312,543.6476 C 607.14312,489.6476 595.14312,505.6476 586.14312,490.6476 C 577.14312,475.6476 567.14312,446.6476 567.14312,434.6476 C 567.14312,431.2726 564.14254,430.10304 559.79937,430.17885 z " style="opacity:0.4;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
|
||||||
|
</g>
|
||||||
|
<g id="g9903">
|
||||||
|
<path sodipodi:nodetypes="cssccccc" id="path1933" d="M 533.14287,448.6479 C 533.14287,448.6479 514.14287,467.6479 519.14287,485.6479 C 524.14287,503.6479 541.14287,547.6479 478.14287,597.6479 C 415.14287,647.6479 232.14287,734.6479 109.14287,639.6479 C 74.14287,599.6479 94.14287,573.6479 94.14287,573.6479 C 94.14287,573.6479 114.12992,570.75757 122.14287,571.6479 C 132.14287,568.6479 179.14287,571.6479 223.14287,573.6479 C 282.05327,572.49008 443.14287,497.6479 474.14287,468.6479 C 474.14287,468.6479 512.14287,422.6479 513.14287,414.6479 C 514.14287,406.6479 540.14287,430.6479 541.14287,437.6479 C 542.14287,444.6479 533.14287,447.6479 533.14287,448.6479 z " style="fill:#ffc701;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
|
||||||
|
<path sodipodi:nodetypes="cssccccsscc" id="path7201" d="M 514.60028,413.04467 C 513.79169,413.14037 513.25653,413.63842 513.13153,414.63842 C 512.13153,422.63842 474.13153,468.63841 474.13153,468.63842 C 443.13153,497.63842 282.04191,572.4806 223.13152,573.63842 C 179.13152,571.63842 132.13152,568.63842 122.13152,571.63842 C 114.11858,570.74809 94.13152,573.63842 94.13152,573.63842 C 94.13152,573.63842 93.7656,574.16602 93.22527,575.07592 C 104.18437,578.28284 124.12588,582.75014 156.38152,583.60717 C 230.77215,585.58373 262.00653,581.54467 326.50653,556.41967 C 394.13686,530.0753 484.82418,496.21955 520.38153,415.35717 C 517.97095,413.81698 515.90107,412.89071 514.60028,413.04467 z " style="opacity:0.4;fill:white;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
|
||||||
|
<path id="path4646" d="M 518.58062,482.8981 C 510.39544,511.03311 477.26645,585.47414 350.14312,628.6481 C 194.69709,681.44109 156.81818,651.08254 106.61187,636.61685 C 107.43132,637.61484 108.2539,638.63184 109.14312,639.6481 C 232.14312,734.6481 415.14312,647.6481 478.14312,597.6481 C 541.14312,547.6481 524.14312,503.6481 519.14312,485.6481 C 518.88851,484.73149 518.71644,483.81726 518.58062,482.8981 z " style="opacity:0.4;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
|
||||||
|
</g>
|
||||||
|
<g id="g9946">
|
||||||
|
<path sodipodi:nodetypes="czsssz" id="path23294" d="M 534.47367,391.42537 C 528.72367,389.92537 508.71616,405.72925 509.06846,411.02017 C 509.42029,416.30405 528.53454,433.43637 540.64281,437.57407 C 544.64281,438.57407 566.64281,436.57407 567.64281,432.57407 C 568.64281,428.57407 568.64281,411.57407 564.64281,407.57407 C 560.64281,403.57407 541.16441,393.17078 534.47367,391.42537 z " style="fill:#884a13;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/>
|
||||||
|
<path sodipodi:nodetypes="czsssz" id="path22407" d="M 534.47374,391.42502 C 528.72374,389.92502 508.71623,405.7289 509.06853,411.01982 C 509.42036,416.3037 528.53461,433.43602 540.64288,437.57372 C 544.64288,438.57372 566.64288,436.57372 567.64288,432.57372 C 568.64288,428.57372 568.64288,411.57372 564.64288,407.57372 C 560.64288,403.57372 541.16448,393.17043 534.47374,391.42502 z " style="fill:#884a13;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
|
||||||
|
<path id="path25958" d="M 533.23672,391.3354 C 526.30206,391.85508 508.75019,406.06266 509.08047,411.0229 C 509.222,413.14841 512.41917,417.17996 516.95547,421.5229 C 517.24178,413.18494 521.37892,401.08827 540.67422,393.7729 C 538.17765,392.66554 536.00097,391.82417 534.48672,391.42915 C 534.12734,391.3354 533.69903,391.30075 533.23672,391.3354 z " style="opacity:0.3;fill:white;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
|
||||||
|
<path id="path26854" d="M 564.58087,407.52257 C 564.73328,425.7813 549.04695,433.48138 538.20587,436.61632 C 539.04145,436.98636 539.85952,437.31721 540.64337,437.58507 C 544.64338,438.58507 566.64337,436.58507 567.64337,432.58507 C 568.64338,428.58507 568.64337,411.58507 564.64337,407.58507 C 564.62587,407.56758 564.59896,407.54031 564.58087,407.52257 z " style="opacity:0.4;fill:black;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -0,0 +1,907 @@
|
||||||
|
/* jslint esversion: 6 */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Cairo = imports.cairo;
|
||||||
|
const Clutter = imports.gi.Clutter;
|
||||||
|
const Lang = imports.lang;
|
||||||
|
const Pango = imports.gi.Pango;
|
||||||
|
const PangoCairo = imports.gi.PangoCairo;
|
||||||
|
|
||||||
|
const reverseEnumeration = function(obj) {
|
||||||
|
let reversed = {};
|
||||||
|
Object.keys(obj).forEach(key => {
|
||||||
|
reversed[obj[key]] = key.slice(0,1) + key.slice(1).toLowerCase().replace('_', '-');
|
||||||
|
});
|
||||||
|
return reversed;
|
||||||
|
};
|
||||||
|
|
||||||
|
var Shapes = { NONE: 0, LINE: 1, ELLIPSE: 2, RECTANGLE: 3, TEXT: 4, POLYGON: 5, POLYLINE: 6, IMAGE: 7 };
|
||||||
|
var ShapeNames = { 0: "Free drawing", 1: "Line", 2: "Ellipse", 3: "Rectangle", 4: "Text", 5: "Polygon", 6: "Polyline", 7: "Image" };
|
||||||
|
var Transformations = { TRANSLATION: 0, ROTATION: 1, SCALE_PRESERVE: 2, STRETCH: 3, REFLECTION: 4, INVERSION: 5 };
|
||||||
|
var LineCapNames = Object.assign(reverseEnumeration(Cairo.LineCap), { 2: 'Square' });
|
||||||
|
var LineJoinNames = reverseEnumeration(Cairo.LineJoin);
|
||||||
|
var FillRuleNames = { 0: 'Nonzero', 1: 'Evenodd' };
|
||||||
|
var FontWeightNames = Object.assign(reverseEnumeration(Pango.Weight), { 200: "Ultra-light", 350: "Semi-light", 600: "Semi-bold", 800: "Ultra-bold" });
|
||||||
|
delete FontWeightNames[Pango.Weight.ULTRAHEAVY];
|
||||||
|
var FontStyleNames = reverseEnumeration(Pango.Style);
|
||||||
|
var FontStretchNames = reverseEnumeration(Pango.Stretch);
|
||||||
|
var FontVariantNames = reverseEnumeration(Pango.Variant);
|
||||||
|
|
||||||
|
var getPangoFontFamilies = function() {
|
||||||
|
return PangoCairo.font_map_get_default().list_families().map(fontFamily => fontFamily.get_name()).sort((a,b) => a.localeCompare(b));
|
||||||
|
};
|
||||||
|
|
||||||
|
const SVG_DEBUG_SUPERPOSES_CAIRO = false;
|
||||||
|
const RADIAN = 180 / Math.PI; // degree
|
||||||
|
const INVERSION_CIRCLE_RADIUS = 12; // px
|
||||||
|
const REFLECTION_TOLERANCE = 5; // px, to select vertical and horizontal directions
|
||||||
|
const STRETCH_TOLERANCE = Math.PI / 8; // rad, to select vertical and horizontal directions
|
||||||
|
const MIN_REFLECTION_LINE_LENGTH = 10; // px
|
||||||
|
const MIN_TRANSLATION_DISTANCE = 1; // px
|
||||||
|
const MIN_ROTATION_ANGLE = Math.PI / 1000; // rad
|
||||||
|
const MIN_DRAWING_SIZE = 3; // px
|
||||||
|
|
||||||
|
var DrawingElement = function(params) {
|
||||||
|
return params.shape == Shapes.TEXT ? new TextElement(params) :
|
||||||
|
params.shape == Shapes.IMAGE ? new ImageElement(params) :
|
||||||
|
new _DrawingElement(params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// DrawingElement represents a "brushstroke".
|
||||||
|
// It can be converted into a cairo path as well as a svg element.
|
||||||
|
// See DrawingArea._startDrawing() to know its params.
|
||||||
|
const _DrawingElement = new Lang.Class({
|
||||||
|
Name: 'DrawOnYourScreenDrawingElement',
|
||||||
|
|
||||||
|
_init: function(params) {
|
||||||
|
for (let key in params)
|
||||||
|
this[key] = params[key];
|
||||||
|
|
||||||
|
// compatibility with json generated by old extension versions
|
||||||
|
|
||||||
|
if (params.transformations === undefined)
|
||||||
|
this.transformations = [];
|
||||||
|
if (params.font && params.font.weight === 0)
|
||||||
|
this.font.weight = 400;
|
||||||
|
if (params.font && params.font.weight === 1)
|
||||||
|
this.font.weight = 700;
|
||||||
|
|
||||||
|
if (params.transform && params.transform.center) {
|
||||||
|
let angle = (params.transform.angle || 0) + (params.transform.startAngle || 0);
|
||||||
|
if (angle)
|
||||||
|
this.transformations.push({ type: Transformations.ROTATION, angle: angle });
|
||||||
|
}
|
||||||
|
if (params.shape == Shapes.ELLIPSE && params.transform && params.transform.ratio && params.transform.ratio != 1 && params.points.length >= 2) {
|
||||||
|
let [ratio, p0, p1] = [params.transform.ratio, params.points[0], params.points[1]];
|
||||||
|
// Add a fake point that will give the right ellipse ratio when building the element.
|
||||||
|
this.points.push([ratio * (p1[0] - p0[0]) + p0[0], ratio * (p1[1] - p0[1]) + p0[1]]);
|
||||||
|
}
|
||||||
|
delete this.transform;
|
||||||
|
},
|
||||||
|
|
||||||
|
// toJSON is called by JSON.stringify
|
||||||
|
toJSON: function() {
|
||||||
|
return {
|
||||||
|
shape: this.shape,
|
||||||
|
color: this.color,
|
||||||
|
line: this.line,
|
||||||
|
dash: this.dash,
|
||||||
|
fill: this.fill,
|
||||||
|
fillRule: this.fillRule,
|
||||||
|
eraser: this.eraser,
|
||||||
|
transformations: this.transformations,
|
||||||
|
points: this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100])
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
buildCairo: function(cr, params) {
|
||||||
|
if (this.color) {
|
||||||
|
let [success, color] = Clutter.Color.from_string(this.color);
|
||||||
|
if (success)
|
||||||
|
Clutter.cairo_set_source_color(cr, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.showSymmetryElement) {
|
||||||
|
let transformation = this.lastTransformation;
|
||||||
|
setDummyStroke(cr);
|
||||||
|
if (transformation.type == Transformations.REFLECTION) {
|
||||||
|
cr.moveTo(transformation.startX, transformation.startY);
|
||||||
|
cr.lineTo(transformation.endX, transformation.endY);
|
||||||
|
} else {
|
||||||
|
cr.arc(transformation.endX, transformation.endY, INVERSION_CIRCLE_RADIUS, 0, 2 * Math.PI);
|
||||||
|
}
|
||||||
|
cr.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.line) {
|
||||||
|
cr.setLineCap(this.line.lineCap);
|
||||||
|
cr.setLineJoin(this.line.lineJoin);
|
||||||
|
cr.setLineWidth(this.line.lineWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.fillRule)
|
||||||
|
cr.setFillRule(this.fillRule);
|
||||||
|
|
||||||
|
if (this.dash && this.dash.active && this.dash.array && this.dash.array[0] && this.dash.array[1])
|
||||||
|
cr.setDash(this.dash.array, this.dash.offset);
|
||||||
|
|
||||||
|
if (this.eraser)
|
||||||
|
cr.setOperator(Cairo.Operator.CLEAR);
|
||||||
|
else
|
||||||
|
cr.setOperator(Cairo.Operator.OVER);
|
||||||
|
|
||||||
|
if (params.dummyStroke)
|
||||||
|
setDummyStroke(cr);
|
||||||
|
|
||||||
|
if (SVG_DEBUG_SUPERPOSES_CAIRO) {
|
||||||
|
Clutter.cairo_set_source_color(cr, Clutter.Color.new(255, 0, 0, 255));
|
||||||
|
cr.setLineWidth(this.line.lineWidth / 2 || 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.transformations.slice(0).reverse().forEach(transformation => {
|
||||||
|
if (transformation.type == Transformations.TRANSLATION) {
|
||||||
|
cr.translate(transformation.slideX, transformation.slideY);
|
||||||
|
} else if (transformation.type == Transformations.ROTATION) {
|
||||||
|
let center = this._getTransformedCenter(transformation);
|
||||||
|
cr.translate(center[0], center[1]);
|
||||||
|
cr.rotate(transformation.angle);
|
||||||
|
cr.translate(-center[0], -center[1]);
|
||||||
|
} else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.STRETCH) {
|
||||||
|
let center = this._getTransformedCenter(transformation);
|
||||||
|
cr.translate(center[0], center[1]);
|
||||||
|
cr.rotate(transformation.angle);
|
||||||
|
cr.scale(transformation.scaleX, transformation.scaleY);
|
||||||
|
cr.rotate(-transformation.angle);
|
||||||
|
cr.translate(-center[0], -center[1]);
|
||||||
|
} else if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) {
|
||||||
|
cr.translate(transformation.slideX, transformation.slideY);
|
||||||
|
cr.rotate(transformation.angle);
|
||||||
|
cr.scale(transformation.scaleX, transformation.scaleY);
|
||||||
|
cr.rotate(-transformation.angle);
|
||||||
|
cr.translate(-transformation.slideX, -transformation.slideY);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._drawCairo(cr, params);
|
||||||
|
|
||||||
|
cr.identityMatrix();
|
||||||
|
},
|
||||||
|
|
||||||
|
_drawCairo: function(cr, params) {
|
||||||
|
let [points, shape] = [this.points, this.shape];
|
||||||
|
|
||||||
|
if (shape == Shapes.LINE && points.length == 3) {
|
||||||
|
cr.moveTo(points[0][0], points[0][1]);
|
||||||
|
cr.curveTo(points[0][0], points[0][1], points[1][0], points[1][1], points[2][0], points[2][1]);
|
||||||
|
|
||||||
|
} else if (shape == Shapes.LINE && points.length == 4) {
|
||||||
|
cr.moveTo(points[0][0], points[0][1]);
|
||||||
|
cr.curveTo(points[1][0], points[1][1], points[2][0], points[2][1], points[3][0], points[3][1]);
|
||||||
|
|
||||||
|
} else if (shape == Shapes.NONE || shape == Shapes.LINE) {
|
||||||
|
cr.moveTo(points[0][0], points[0][1]);
|
||||||
|
for (let j = 1; j < points.length; j++) {
|
||||||
|
cr.lineTo(points[j][0], points[j][1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (shape == Shapes.ELLIPSE && points.length >= 2) {
|
||||||
|
let radius = Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]);
|
||||||
|
let ratio = 1;
|
||||||
|
|
||||||
|
if (points[2]) {
|
||||||
|
ratio = Math.hypot(points[2][0] - points[0][0], points[2][1] - points[0][1]) / radius;
|
||||||
|
cr.translate(points[0][0], points[0][1]);
|
||||||
|
cr.scale(ratio, 1);
|
||||||
|
cr.translate(-points[0][0], -points[0][1]);
|
||||||
|
cr.arc(points[0][0], points[0][1], radius, 0, 2 * Math.PI);
|
||||||
|
cr.translate(points[0][0], points[0][1]);
|
||||||
|
cr.scale(1 / ratio, 1);
|
||||||
|
cr.translate(-points[0][0], -points[0][1]);
|
||||||
|
} else
|
||||||
|
cr.arc(points[0][0], points[0][1], radius, 0, 2 * Math.PI);
|
||||||
|
|
||||||
|
} else if (shape == Shapes.RECTANGLE && points.length == 2) {
|
||||||
|
cr.rectangle(points[0][0], points[0][1], points[1][0] - points[0][0], points[1][1] - points[0][1]);
|
||||||
|
|
||||||
|
} else if ((shape == Shapes.POLYGON || shape == Shapes.POLYLINE) && points.length >= 2) {
|
||||||
|
cr.moveTo(points[0][0], points[0][1]);
|
||||||
|
for (let j = 1; j < points.length; j++) {
|
||||||
|
cr.lineTo(points[j][0], points[j][1]);
|
||||||
|
}
|
||||||
|
if (shape == Shapes.POLYGON)
|
||||||
|
cr.closePath();
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getContainsPoint: function(cr, x, y) {
|
||||||
|
cr.save();
|
||||||
|
cr.setLineWidth(Math.max(this.line.lineWidth, 25));
|
||||||
|
cr.setDash([], 0);
|
||||||
|
|
||||||
|
// Check whether the point is inside/on/near the element.
|
||||||
|
let inElement = cr.inStroke(x, y) || this.fill && cr.inFill(x, y);
|
||||||
|
cr.restore();
|
||||||
|
return inElement;
|
||||||
|
},
|
||||||
|
|
||||||
|
buildSVG: function(bgColor) {
|
||||||
|
let transAttribute = '';
|
||||||
|
this.transformations.slice(0).reverse().forEach(transformation => {
|
||||||
|
transAttribute += transAttribute ? ' ' : ' transform="';
|
||||||
|
let center = this._getTransformedCenter(transformation);
|
||||||
|
|
||||||
|
if (transformation.type == Transformations.TRANSLATION) {
|
||||||
|
transAttribute += `translate(${transformation.slideX},${transformation.slideY})`;
|
||||||
|
} else if (transformation.type == Transformations.ROTATION) {
|
||||||
|
transAttribute += `translate(${center[0]},${center[1]}) `;
|
||||||
|
transAttribute += `rotate(${transformation.angle * RADIAN}) `;
|
||||||
|
transAttribute += `translate(${-center[0]},${-center[1]})`;
|
||||||
|
} else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.STRETCH) {
|
||||||
|
transAttribute += `translate(${center[0]},${center[1]}) `;
|
||||||
|
transAttribute += `rotate(${transformation.angle * RADIAN}) `;
|
||||||
|
transAttribute += `scale(${transformation.scaleX},${transformation.scaleY}) `;
|
||||||
|
transAttribute += `rotate(${-transformation.angle * RADIAN}) `;
|
||||||
|
transAttribute += `translate(${-center[0]},${-center[1]})`;
|
||||||
|
} else if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) {
|
||||||
|
transAttribute += `translate(${transformation.slideX}, ${transformation.slideY}) `;
|
||||||
|
transAttribute += `rotate(${transformation.angle * RADIAN}) `;
|
||||||
|
transAttribute += `scale(${transformation.scaleX}, ${transformation.scaleY}) `;
|
||||||
|
transAttribute += `rotate(${-transformation.angle * RADIAN}) `;
|
||||||
|
transAttribute += `translate(${-transformation.slideX}, ${-transformation.slideY})`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
transAttribute += transAttribute ? '"' : '';
|
||||||
|
|
||||||
|
return this._drawSvg(transAttribute);
|
||||||
|
},
|
||||||
|
|
||||||
|
_drawSvg: function(transAttribute) {
|
||||||
|
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 ? bgColor : this.color;
|
||||||
|
let fill = this.fill && !this.isStraightLine;
|
||||||
|
let attributes = '';
|
||||||
|
|
||||||
|
if (fill) {
|
||||||
|
attributes = `fill="${color}"`;
|
||||||
|
if (this.fillRule)
|
||||||
|
attributes += ` fill-rule="${FillRuleNames[this.fillRule].toLowerCase()}"`;
|
||||||
|
} else {
|
||||||
|
attributes = `fill="none"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.line && this.line.lineWidth) {
|
||||||
|
attributes += ` stroke="${color}"` +
|
||||||
|
` stroke-width="${this.line.lineWidth}"`;
|
||||||
|
if (this.line.lineCap)
|
||||||
|
attributes += ` stroke-linecap="${LineCapNames[this.line.lineCap].toLowerCase()}"`;
|
||||||
|
if (this.line.lineJoin && !this.isStraightLine)
|
||||||
|
attributes += ` stroke-linejoin="${LineJoinNames[this.line.lineJoin].toLowerCase()}"`;
|
||||||
|
if (this.dash && this.dash.active && this.dash.array && this.dash.array[0] && this.dash.array[1])
|
||||||
|
attributes += ` stroke-dasharray="${this.dash.array[0]} ${this.dash.array[1]}" stroke-dashoffset="${this.dash.offset}"`;
|
||||||
|
} else {
|
||||||
|
attributes += ` stroke="none"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.shape == Shapes.LINE && points.length == 4) {
|
||||||
|
row += `<path ${attributes} d="M${points[0][0]} ${points[0][1]}`;
|
||||||
|
row += ` C ${points[1][0]} ${points[1][1]}, ${points[2][0]} ${points[2][1]}, ${points[3][0]} ${points[3][1]}`;
|
||||||
|
row += `${fill ? 'z' : ''}"${transAttribute}/>`;
|
||||||
|
|
||||||
|
} else if (this.shape == Shapes.LINE && points.length == 3) {
|
||||||
|
row += `<path ${attributes} d="M${points[0][0]} ${points[0][1]}`;
|
||||||
|
row += ` C ${points[0][0]} ${points[0][1]}, ${points[1][0]} ${points[1][1]}, ${points[2][0]} ${points[2][1]}`;
|
||||||
|
row += `${fill ? 'z' : ''}"${transAttribute}/>`;
|
||||||
|
|
||||||
|
} else if (this.shape == Shapes.LINE) {
|
||||||
|
row += `<line ${attributes} x1="${points[0][0]}" y1="${points[0][1]}" x2="${points[1][0]}" y2="${points[1][1]}"${transAttribute}/>`;
|
||||||
|
|
||||||
|
} else if (this.shape == Shapes.NONE) {
|
||||||
|
row += `<path ${attributes} d="M${points[0][0]} ${points[0][1]}`;
|
||||||
|
for (let i = 1; i < points.length; i++)
|
||||||
|
row += ` L ${points[i][0]} ${points[i][1]}`;
|
||||||
|
row += `${fill ? 'z' : ''}"${transAttribute}/>`;
|
||||||
|
|
||||||
|
} else if (this.shape == Shapes.ELLIPSE && points.length == 3) {
|
||||||
|
let ry = Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]);
|
||||||
|
let rx = Math.hypot(points[2][0] - points[0][0], points[2][1] - points[0][1]);
|
||||||
|
row += `<ellipse ${attributes} cx="${points[0][0]}" cy="${points[0][1]}" rx="${rx}" ry="${ry}"${transAttribute}/>`;
|
||||||
|
|
||||||
|
} else if (this.shape == Shapes.ELLIPSE && points.length == 2) {
|
||||||
|
let r = Math.hypot(points[1][0] - points[0][0], points[1][1] - points[0][1]);
|
||||||
|
row += `<circle ${attributes} cx="${points[0][0]}" cy="${points[0][1]}" r="${r}"${transAttribute}/>`;
|
||||||
|
|
||||||
|
} else if (this.shape == Shapes.RECTANGLE && points.length == 2) {
|
||||||
|
row += `<rect ${attributes} x="${Math.min(points[0][0], points[1][0])}" y="${Math.min(points[0][1], points[1][1])}" ` +
|
||||||
|
`width="${Math.abs(points[1][0] - points[0][0])}" height="${Math.abs(points[1][1] - points[0][1])}"${transAttribute}/>`;
|
||||||
|
|
||||||
|
} else if (this.shape == Shapes.POLYGON && points.length >= 3) {
|
||||||
|
row += `<polygon ${attributes} points="`;
|
||||||
|
for (let i = 0; i < points.length; i++)
|
||||||
|
row += ` ${points[i][0]},${points[i][1]}`;
|
||||||
|
row += `"${transAttribute}/>`;
|
||||||
|
|
||||||
|
} else if (this.shape == Shapes.POLYLINE && points.length >= 2) {
|
||||||
|
row += `<polyline ${attributes} points="`;
|
||||||
|
for (let i = 0; i < points.length; i++)
|
||||||
|
row += ` ${points[i][0]},${points[i][1]}`;
|
||||||
|
row += `"${transAttribute}/>`;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return row;
|
||||||
|
},
|
||||||
|
|
||||||
|
get lastTransformation() {
|
||||||
|
if (!this.transformations.length)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return this.transformations[this.transformations.length - 1];
|
||||||
|
},
|
||||||
|
|
||||||
|
get isStraightLine() {
|
||||||
|
return this.shape == Shapes.LINE && this.points.length == 2;
|
||||||
|
},
|
||||||
|
|
||||||
|
smoothAll: function() {
|
||||||
|
for (let i = 0; i < this.points.length; i++) {
|
||||||
|
this._smooth(i);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addPoint: function() {
|
||||||
|
if (this.shape == Shapes.POLYGON || this.shape == Shapes.POLYLINE) {
|
||||||
|
// copy last point
|
||||||
|
let [lastPoint, secondToLastPoint] = [this.points[this.points.length - 1], this.points[this.points.length - 2]];
|
||||||
|
if (!getNearness(secondToLastPoint, lastPoint, MIN_DRAWING_SIZE))
|
||||||
|
this.points.push([lastPoint[0], lastPoint[1]]);
|
||||||
|
} else if (this.shape == Shapes.LINE) {
|
||||||
|
if (this.points.length == 2) {
|
||||||
|
this.points[2] = this.points[1];
|
||||||
|
} else if (this.points.length == 3) {
|
||||||
|
this.points[3] = this.points[2];
|
||||||
|
this.points[2] = this.points[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
startDrawing: function(startX, startY) {
|
||||||
|
this.points.push([startX, startY]);
|
||||||
|
|
||||||
|
if (this.shape == Shapes.POLYGON || this.shape == Shapes.POLYLINE)
|
||||||
|
this.points.push([startX, startY]);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateDrawing: function(x, y, transform) {
|
||||||
|
let points = this.points;
|
||||||
|
if (x == points[points.length - 1][0] && y == points[points.length - 1][1])
|
||||||
|
return;
|
||||||
|
|
||||||
|
transform = transform || this.transformations.length >= 1;
|
||||||
|
|
||||||
|
if (this.shape == Shapes.NONE) {
|
||||||
|
points.push([x, y]);
|
||||||
|
if (transform)
|
||||||
|
this._smooth(points.length - 1);
|
||||||
|
|
||||||
|
} else if ((this.shape == Shapes.RECTANGLE || this.shape == Shapes.POLYGON || this.shape == Shapes.POLYLINE) && transform) {
|
||||||
|
if (points.length < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let center = this._getOriginalCenter();
|
||||||
|
this.transformations[0] = { type: Transformations.ROTATION,
|
||||||
|
angle: getAngle(center[0], center[1], points[points.length - 1][0], points[points.length - 1][1], x, y) };
|
||||||
|
|
||||||
|
} else if (this.shape == Shapes.ELLIPSE && transform) {
|
||||||
|
if (points.length < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
points[2] = [x, y];
|
||||||
|
let center = this._getOriginalCenter();
|
||||||
|
this.transformations[0] = { type: Transformations.ROTATION,
|
||||||
|
angle: getAngle(center[0], center[1], center[0] + 1, center[1], x, y) };
|
||||||
|
|
||||||
|
} else if (this.shape == Shapes.POLYGON || this.shape == Shapes.POLYLINE) {
|
||||||
|
points[points.length - 1] = [x, y];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
points[1] = [x, y];
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
stopDrawing: function() {
|
||||||
|
// skip when the size is too small to be visible (3px) (except for free drawing)
|
||||||
|
if (this.shape != Shapes.NONE && this.points.length >= 2) {
|
||||||
|
let lastPoint = this.points[this.points.length - 1];
|
||||||
|
let secondToLastPoint = this.points[this.points.length - 2];
|
||||||
|
if (getNearness(secondToLastPoint, lastPoint, MIN_DRAWING_SIZE))
|
||||||
|
this.points.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.transformations[0] && this.transformations[0].type == Transformations.ROTATION &&
|
||||||
|
Math.abs(this.transformations[0].angle) < MIN_ROTATION_ANGLE)
|
||||||
|
this.transformations.shift();
|
||||||
|
},
|
||||||
|
|
||||||
|
startTransformation: function(startX, startY, type) {
|
||||||
|
if (type == Transformations.TRANSLATION)
|
||||||
|
this.transformations.push({ startX: startX, startY: startY, type: type, slideX: 0, slideY: 0 });
|
||||||
|
else if (type == Transformations.ROTATION)
|
||||||
|
this.transformations.push({ startX: startX, startY: startY, type: type, angle: 0 });
|
||||||
|
else if (type == Transformations.SCALE_PRESERVE || type == Transformations.STRETCH)
|
||||||
|
this.transformations.push({ startX: startX, startY: startY, type: type, scaleX: 1, scaleY: 1, angle: 0 });
|
||||||
|
else if (type == Transformations.REFLECTION)
|
||||||
|
this.transformations.push({ startX: startX, startY: startY, endX: startX, endY: startY, type: type,
|
||||||
|
scaleX: 1, scaleY: 1, slideX: 0, slideY: 0, angle: 0 });
|
||||||
|
else if (type == Transformations.INVERSION)
|
||||||
|
this.transformations.push({ startX: startX, startY: startY, endX: startX, endY: startY, type: type,
|
||||||
|
scaleX: -1, scaleY: -1, slideX: startX, slideY: startY,
|
||||||
|
angle: Math.PI + Math.atan(startY / (startX || 1)) });
|
||||||
|
|
||||||
|
if (type == Transformations.REFLECTION || type == Transformations.INVERSION)
|
||||||
|
this.showSymmetryElement = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateTransformation: function(x, y) {
|
||||||
|
let transformation = this.lastTransformation;
|
||||||
|
|
||||||
|
if (transformation.type == Transformations.TRANSLATION) {
|
||||||
|
transformation.slideX = x - transformation.startX;
|
||||||
|
transformation.slideY = y - transformation.startY;
|
||||||
|
} else if (transformation.type == Transformations.ROTATION) {
|
||||||
|
let center = this._getTransformedCenter(transformation);
|
||||||
|
transformation.angle = getAngle(center[0], center[1], transformation.startX, transformation.startY, x, y);
|
||||||
|
} else if (transformation.type == Transformations.SCALE_PRESERVE) {
|
||||||
|
let center = this._getTransformedCenter(transformation);
|
||||||
|
let scale = Math.hypot(x - center[0], y - center[1]) / Math.hypot(transformation.startX - center[0], transformation.startY - center[1]) || 1;
|
||||||
|
[transformation.scaleX, transformation.scaleY] = [scale, scale];
|
||||||
|
} else if (transformation.type == Transformations.STRETCH) {
|
||||||
|
let center = this._getTransformedCenter(transformation);
|
||||||
|
let startAngle = getAngle(center[0], center[1], center[0] + 1, center[1], transformation.startX, transformation.startY);
|
||||||
|
let vertical = Math.abs(Math.sin(startAngle)) >= Math.sin(Math.PI / 2 - STRETCH_TOLERANCE);
|
||||||
|
let horizontal = Math.abs(Math.cos(startAngle)) >= Math.cos(STRETCH_TOLERANCE);
|
||||||
|
let scale = Math.hypot(x - center[0], y - center[1]) / Math.hypot(transformation.startX - center[0], transformation.startY - center[1]) || 1;
|
||||||
|
transformation.scaleX = vertical ? 1 : scale;
|
||||||
|
transformation.scaleY = !vertical ? 1 : scale;
|
||||||
|
transformation.angle = vertical || horizontal ? 0 : getAngle(center[0], center[1], center[0] + 1, center[1], x, y);
|
||||||
|
} else if (transformation.type == Transformations.REFLECTION) {
|
||||||
|
[transformation.endX, transformation.endY] = [x, y];
|
||||||
|
if (getNearness([transformation.startX, transformation.startY], [x, y], MIN_REFLECTION_LINE_LENGTH)) {
|
||||||
|
// do nothing to avoid jumps (no transformation at starting and locked transformation after)
|
||||||
|
} else if (Math.abs(y - transformation.startY) <= REFLECTION_TOLERANCE && Math.abs(x - transformation.startX) > REFLECTION_TOLERANCE) {
|
||||||
|
[transformation.scaleX, transformation.scaleY] = [1, -1];
|
||||||
|
[transformation.slideX, transformation.slideY] = [0, transformation.startY];
|
||||||
|
transformation.angle = Math.PI;
|
||||||
|
} else if (Math.abs(x - transformation.startX) <= REFLECTION_TOLERANCE && Math.abs(y - transformation.startY) > REFLECTION_TOLERANCE) {
|
||||||
|
[transformation.scaleX, transformation.scaleY] = [-1, 1];
|
||||||
|
[transformation.slideX, transformation.slideY] = [transformation.startX, 0];
|
||||||
|
transformation.angle = Math.PI;
|
||||||
|
} else if (x != transformation.startX) {
|
||||||
|
let tan = (y - transformation.startY) / (x - transformation.startX);
|
||||||
|
[transformation.scaleX, transformation.scaleY] = [1, -1];
|
||||||
|
[transformation.slideX, transformation.slideY] = [0, transformation.startY - transformation.startX * tan];
|
||||||
|
transformation.angle = Math.PI + Math.atan(tan);
|
||||||
|
} else if (y != transformation.startY) {
|
||||||
|
let tan = (x - transformation.startX) / (y - transformation.startY);
|
||||||
|
[transformation.scaleX, transformation.scaleY] = [-1, 1];
|
||||||
|
[transformation.slideX, transformation.slideY] = [transformation.startX - transformation.startY * tan, 0];
|
||||||
|
transformation.angle = Math.PI - Math.atan(tan);
|
||||||
|
}
|
||||||
|
} else if (transformation.type == Transformations.INVERSION) {
|
||||||
|
[transformation.endX, transformation.endY] = [x, y];
|
||||||
|
[transformation.scaleX, transformation.scaleY] = [-1, -1];
|
||||||
|
[transformation.slideX, transformation.slideY] = [x, y];
|
||||||
|
transformation.angle = Math.PI + Math.atan(y / (x || 1));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
stopTransformation: function() {
|
||||||
|
// Clean transformations
|
||||||
|
let transformation = this.lastTransformation;
|
||||||
|
|
||||||
|
if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION)
|
||||||
|
this.showSymmetryElement = false;
|
||||||
|
|
||||||
|
if (transformation.type == Transformations.REFLECTION &&
|
||||||
|
getNearness([transformation.startX, transformation.startY], [transformation.endX, transformation.endY], MIN_REFLECTION_LINE_LENGTH) ||
|
||||||
|
transformation.type == Transformations.TRANSLATION && Math.hypot(transformation.slideX, transformation.slideY) < MIN_TRANSLATION_DISTANCE ||
|
||||||
|
transformation.type == Transformations.ROTATION && Math.abs(transformation.angle) < MIN_ROTATION_ANGLE) {
|
||||||
|
|
||||||
|
this.transformations.pop();
|
||||||
|
} else {
|
||||||
|
delete transformation.startX;
|
||||||
|
delete transformation.startY;
|
||||||
|
delete transformation.endX;
|
||||||
|
delete transformation.endY;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// The figure rotation center before transformations (original).
|
||||||
|
// this.textWidth is computed during Cairo building.
|
||||||
|
_getOriginalCenter: function() {
|
||||||
|
if (!this._originalCenter) {
|
||||||
|
let points = this.points;
|
||||||
|
this._originalCenter = this.shape == Shapes.ELLIPSE ? [points[0][0], points[0][1]] :
|
||||||
|
this.shape == Shapes.LINE && points.length == 4 ? getCurveCenter(points[0], points[1], points[2], points[3]) :
|
||||||
|
this.shape == Shapes.LINE && points.length == 3 ? getCurveCenter(points[0], points[0], points[1], points[2]) :
|
||||||
|
points.length >= 3 ? getCentroid(points) :
|
||||||
|
getNaiveCenter(points);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._originalCenter;
|
||||||
|
},
|
||||||
|
|
||||||
|
// The figure rotation center, whose position is affected by all transformations done before 'transformation'.
|
||||||
|
_getTransformedCenter: function(transformation) {
|
||||||
|
if (!transformation.elementTransformedCenter) {
|
||||||
|
let matrix = new Pango.Matrix({ xx: 1, xy: 0, yx: 0, yy: 1, x0: 0, y0: 0 });
|
||||||
|
|
||||||
|
// Apply transformations to the matrice in reverse order
|
||||||
|
// because Pango multiply matrices by the left when applying a transformation
|
||||||
|
this.transformations.slice(0, this.transformations.indexOf(transformation)).reverse().forEach(transformation => {
|
||||||
|
if (transformation.type == Transformations.TRANSLATION) {
|
||||||
|
matrix.translate(transformation.slideX, transformation.slideY);
|
||||||
|
} else if (transformation.type == Transformations.ROTATION) {
|
||||||
|
// nothing, the center position is preserved.
|
||||||
|
} else if (transformation.type == Transformations.SCALE_PRESERVE || transformation.type == Transformations.STRETCH) {
|
||||||
|
// nothing, the center position is preserved.
|
||||||
|
} else if (transformation.type == Transformations.REFLECTION || transformation.type == Transformations.INVERSION) {
|
||||||
|
matrix.translate(transformation.slideX, transformation.slideY);
|
||||||
|
matrix.rotate(-transformation.angle * RADIAN);
|
||||||
|
matrix.scale(transformation.scaleX, transformation.scaleY);
|
||||||
|
matrix.rotate(transformation.angle * RADIAN);
|
||||||
|
matrix.translate(-transformation.slideX, -transformation.slideY);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let originalCenter = this._getOriginalCenter();
|
||||||
|
transformation.elementTransformedCenter = matrix.transform_point(originalCenter[0], originalCenter[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return transformation.elementTransformedCenter;
|
||||||
|
},
|
||||||
|
|
||||||
|
_smooth: function(i) {
|
||||||
|
if (i < 2)
|
||||||
|
return;
|
||||||
|
this.points[i-1] = [(this.points[i-2][0] + this.points[i][0]) / 2, (this.points[i-2][1] + this.points[i][1]) / 2];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const TextElement = new Lang.Class({
|
||||||
|
Name: 'DrawOnYourScreenTextElement',
|
||||||
|
Extends: _DrawingElement,
|
||||||
|
|
||||||
|
toJSON: function() {
|
||||||
|
return {
|
||||||
|
shape: this.shape,
|
||||||
|
color: this.color,
|
||||||
|
eraser: this.eraser,
|
||||||
|
transformations: this.transformations,
|
||||||
|
text: this.text,
|
||||||
|
lineIndex: this.lineIndex !== undefined ? this.lineIndex : undefined,
|
||||||
|
textRightAligned: this.textRightAligned,
|
||||||
|
font: this.font,
|
||||||
|
points: this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100])
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
get x() {
|
||||||
|
// this.textWidth is computed during Cairo building.
|
||||||
|
return this.points[1][0] - (this.textRightAligned ? this.textWidth : 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
get y() {
|
||||||
|
return Math.max(this.points[0][1], this.points[1][1]);
|
||||||
|
},
|
||||||
|
|
||||||
|
get height() {
|
||||||
|
return Math.abs(this.points[1][1] - this.points[0][1]);
|
||||||
|
},
|
||||||
|
|
||||||
|
// When rotating grouped lines, lineOffset is used to retrieve the rotation center of the first line.
|
||||||
|
get lineOffset() {
|
||||||
|
return (this.lineIndex || 0) * this.height;
|
||||||
|
},
|
||||||
|
|
||||||
|
_drawCairo: function(cr, params) {
|
||||||
|
if (this.points.length == 2) {
|
||||||
|
let layout = PangoCairo.create_layout(cr);
|
||||||
|
let fontSize = this.height * Pango.SCALE;
|
||||||
|
let fontDescription = new Pango.FontDescription();
|
||||||
|
fontDescription.set_absolute_size(fontSize);
|
||||||
|
['family', 'weight', 'style', 'stretch', 'variant'].forEach(attribute => {
|
||||||
|
if (this.font[attribute] !== undefined)
|
||||||
|
try {
|
||||||
|
fontDescription[`set_${attribute}`](this.font[attribute]);
|
||||||
|
} catch(e) {}
|
||||||
|
});
|
||||||
|
layout.set_font_description(fontDescription);
|
||||||
|
layout.set_text(this.text, -1);
|
||||||
|
this.textWidth = layout.get_pixel_size()[0];
|
||||||
|
cr.moveTo(this.x, this.y - layout.get_baseline() / Pango.SCALE);
|
||||||
|
layout.set_text(this.text, -1);
|
||||||
|
PangoCairo.show_layout(cr, layout);
|
||||||
|
|
||||||
|
if (params.showTextCursor) {
|
||||||
|
let cursorPosition = this.cursorPosition == -1 ? this.text.length : this.cursorPosition;
|
||||||
|
layout.set_text(this.text.slice(0, cursorPosition), -1);
|
||||||
|
let width = layout.get_pixel_size()[0];
|
||||||
|
cr.rectangle(this.x + width, this.y,
|
||||||
|
this.height / 25, - this.height);
|
||||||
|
cr.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.showTextRectangle) {
|
||||||
|
cr.rectangle(this.x, this.y - this.lineOffset,
|
||||||
|
this.textWidth, - this.height);
|
||||||
|
setDummyStroke(cr);
|
||||||
|
} else if (params.drawTextRectangle) {
|
||||||
|
cr.rectangle(this.x, this.y,
|
||||||
|
this.textWidth, - this.height);
|
||||||
|
// Only draw the rectangle to find the element, not to show it.
|
||||||
|
cr.setLineWidth(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getContainsPoint: function(cr, x, y) {
|
||||||
|
return cr.inFill(x, y);
|
||||||
|
},
|
||||||
|
|
||||||
|
_drawSvg: function(transAttribute) {
|
||||||
|
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 ? bgColor : this.color;
|
||||||
|
let attributes = '';
|
||||||
|
|
||||||
|
if (this.points.length == 2) {
|
||||||
|
attributes = `fill="${color}" ` +
|
||||||
|
`stroke="transparent" ` +
|
||||||
|
`stroke-opacity="0" ` +
|
||||||
|
`font-size="${height}"`;
|
||||||
|
|
||||||
|
if (this.font.family)
|
||||||
|
attributes += ` font-family="${this.font.family}"`;
|
||||||
|
if (this.font.weight && this.font.weight != Pango.Weight.NORMAL)
|
||||||
|
attributes += ` font-weight="${this.font.weight}"`;
|
||||||
|
if (this.font.style && FontStyleNames[this.font.style])
|
||||||
|
attributes += ` font-style="${FontStyleNames[this.font.style].toLowerCase()}"`;
|
||||||
|
if (FontStretchNames[this.font.stretch] && this.font.stretch != Pango.Stretch.NORMAL)
|
||||||
|
attributes += ` font-stretch="${FontStretchNames[this.font.stretch].toLowerCase()}"`;
|
||||||
|
if (this.font.variant && FontVariantNames[this.font.variant])
|
||||||
|
attributes += ` font-variant="${FontVariantNames[this.font.variant].toLowerCase()}"`;
|
||||||
|
|
||||||
|
row += `<text ${attributes} x="${x}" `;
|
||||||
|
row += `y="${y}"${transAttribute}>${this.text}</text>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return row;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateDrawing: function(x, y, transform) {
|
||||||
|
let points = this.points;
|
||||||
|
if (x == points[points.length - 1][0] && y == points[points.length - 1][1])
|
||||||
|
return;
|
||||||
|
|
||||||
|
transform = transform || this.transformations.length >= 1;
|
||||||
|
|
||||||
|
if (transform) {
|
||||||
|
if (points.length < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let [slideX, slideY] = [x - points[1][0], y - points[1][1]];
|
||||||
|
points[0] = [points[0][0] + slideX, points[0][1] + slideY];
|
||||||
|
points[1] = [x, y];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
points[1] = [x, y];
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_getOriginalCenter: function() {
|
||||||
|
if (!this._originalCenter) {
|
||||||
|
let points = this.points;
|
||||||
|
this._originalCenter = this.textWidth ? [points[1][0], Math.max(points[0][1], points[1][1]) - this.lineOffset] :
|
||||||
|
points.length >= 3 ? getCentroid(points) :
|
||||||
|
getNaiveCenter(points);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._originalCenter;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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(),
|
||||||
|
preserveAspectRatio: this.preserveAspectRatio,
|
||||||
|
points: this.points.map((point) => [Math.round(point[0]*100)/100, Math.round(point[1]*100)/100])
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
_drawCairo: function(cr, params) {
|
||||||
|
if (this.points.length < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let points = this.points;
|
||||||
|
let [x, y] = [Math.min(points[0][0], points[1][0]), Math.min(points[0][1], points[1][1])];
|
||||||
|
let [width, height] = [Math.abs(points[1][0] - points[0][0]), Math.abs(points[1][1] - points[0][1])];
|
||||||
|
|
||||||
|
if (width < 1 || height < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cr.save();
|
||||||
|
this.image.setCairoSource(cr, x, y, width, height, this.preserveAspectRatio);
|
||||||
|
cr.rectangle(x, y, width, height);
|
||||||
|
cr.fill();
|
||||||
|
cr.restore();
|
||||||
|
|
||||||
|
if (params.showTextRectangle) {
|
||||||
|
cr.rectangle(x, y, width, height);
|
||||||
|
setDummyStroke(cr);
|
||||||
|
} else if (params.drawTextRectangle) {
|
||||||
|
cr.rectangle(x, y, width, height);
|
||||||
|
// Only draw the rectangle to find the element, not to show it.
|
||||||
|
cr.setLineWidth(0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getContainsPoint: function(cr, x, y) {
|
||||||
|
return cr.inFill(x, y);
|
||||||
|
},
|
||||||
|
|
||||||
|
_drawSvg: function(transAttribute) {
|
||||||
|
let points = this.points;
|
||||||
|
let row = "\n ";
|
||||||
|
let attributes = '';
|
||||||
|
|
||||||
|
if (points.length == 2) {
|
||||||
|
attributes = `fill="none"`;
|
||||||
|
row += `<image ${attributes} x="${Math.min(points[0][0], points[1][0])}" y="${Math.min(points[0][1], points[1][1])}" ` +
|
||||||
|
`width="${Math.abs(points[1][0] - points[0][0])}" height="${Math.abs(points[1][1] - points[0][1])}"${transAttribute} ` +
|
||||||
|
`preserveAspectRatio="${this.preserveAspectRatio ? 'xMinYMin' : 'none'}" ` +
|
||||||
|
`id="${this.image.displayName}" xlink:href="data:${this.image.contentType};base64,${this.image.base64}"/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return row;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateDrawing: function(x, y, transform) {
|
||||||
|
let points = this.points;
|
||||||
|
if (x == points[0][0] || y == points[0][1])
|
||||||
|
return;
|
||||||
|
|
||||||
|
points[1] = [x, y];
|
||||||
|
this.preserveAspectRatio = !transform;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const setDummyStroke = function(cr) {
|
||||||
|
cr.setLineWidth(2);
|
||||||
|
cr.setLineCap(0);
|
||||||
|
cr.setLineJoin(0);
|
||||||
|
cr.setDash([1, 2], 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNearness = function(pointA, pointB, distance) {
|
||||||
|
return Math.hypot(pointB[0] - pointA[0], pointB[1] - pointA[1]) < distance;
|
||||||
|
};
|
||||||
|
|
||||||
|
// mean of the vertices, ok for regular polygons
|
||||||
|
const getNaiveCenter = function(points) {
|
||||||
|
return points.reduce((accumulator, point) => accumulator = [accumulator[0] + point[0], accumulator[1] + point[1]])
|
||||||
|
.map(coord => coord / points.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://en.wikipedia.org/wiki/Centroid#Of_a_polygon
|
||||||
|
const getCentroid = function(points) {
|
||||||
|
let n = points.length;
|
||||||
|
points.push(points[0]);
|
||||||
|
|
||||||
|
let [sA, sX, sY] = [0, 0, 0];
|
||||||
|
for (let i = 0; i <= n-1; i++) {
|
||||||
|
let a = points[i][0]*points[i+1][1] - points[i+1][0]*points[i][1];
|
||||||
|
sA += a;
|
||||||
|
sX += (points[i][0] + points[i+1][0]) * a;
|
||||||
|
sY += (points[i][1] + points[i+1][1]) * a;
|
||||||
|
}
|
||||||
|
|
||||||
|
points.pop();
|
||||||
|
if (sA == 0)
|
||||||
|
return getNaiveCenter(points);
|
||||||
|
return [sX / (3 * sA), sY / (3 * sA)];
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Cubic Bézier:
|
||||||
|
[0, 1] -> ℝ², P(t) = (1-t)³P₀ + 3t(1-t)²P₁ + 3t²(1-t)P₂ + t³P₃
|
||||||
|
|
||||||
|
general case:
|
||||||
|
|
||||||
|
const cubicBezierCoord = function(x0, x1, x2, x3, t) {
|
||||||
|
return (1-t)**3*x0 + 3*t*(1-t)**2*x1 + 3*t**2*(1-t)*x2 + t**3*x3;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cubicBezierPoint = function(p0, p1, p2, p3, t) {
|
||||||
|
return [cubicBezier(p0[0], p1[0], p2[0], p3[0], t), cubicBezier(p0[1], p1[1], p2[1], p3[1], t)];
|
||||||
|
}
|
||||||
|
|
||||||
|
Approximatively:
|
||||||
|
control point: p0 ---- p1 ---- p2 ---- p3 (p2 is not on the curve)
|
||||||
|
t: 0 ---- 1/3 ---- 2/3 ---- 1
|
||||||
|
*/
|
||||||
|
|
||||||
|
// If the curve has a symmetry axis, it is truly a center (the intersection of the curve and the axis).
|
||||||
|
// In other cases, it is not a notable point, just a visual approximation.
|
||||||
|
const getCurveCenter = function(p0, p1, p2, p3) {
|
||||||
|
if (p0[0] == p1[0] && p0[1] == p1[1])
|
||||||
|
// p0 = p1, t = 2/3
|
||||||
|
return [(p1[0] + 6*p1[0] + 12*p2[0] + 8*p3[0]) / 27, (p1[1] + 6*p1[1] + 12*p2[1] + 8*p3[1]) / 27];
|
||||||
|
else
|
||||||
|
// t = 1/2
|
||||||
|
return [(p0[0] + 3*p1[0] + 3*p2[0] + p3[0]) / 8, (p0[1] + 3*p1[1] + 3*p2[1] + p3[1]) / 8];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAngle = function(xO, yO, xA, yA, xB, yB) {
|
||||||
|
// calculate angle of rotation in absolute value
|
||||||
|
// cos(AOB) = (OA.OB)/(||OA||*||OB||) where OA.OB = (xA-xO)*(xB-xO) + (yA-yO)*(yB-yO)
|
||||||
|
let cos = ((xA - xO)*(xB - xO) + (yA - yO)*(yB - yO)) / (Math.hypot(xA - xO, yA - yO) * Math.hypot(xB - xO, yB - yO));
|
||||||
|
|
||||||
|
// acos is defined on [-1, 1] but
|
||||||
|
// with A == B and imperfect computer calculations, cos may be equal to 1.00000001.
|
||||||
|
cos = Math.min(Math.max(-1, cos), 1);
|
||||||
|
let angle = Math.acos( cos );
|
||||||
|
|
||||||
|
// determine the sign of the angle
|
||||||
|
if (xA == xO) {
|
||||||
|
if (xB > xO)
|
||||||
|
angle = -angle;
|
||||||
|
} else {
|
||||||
|
// equation of OA: y = ax + b
|
||||||
|
let a = (yA - yO) / (xA - xO);
|
||||||
|
let b = yA - a*xA;
|
||||||
|
if (yB < a*xB + b)
|
||||||
|
angle = - angle;
|
||||||
|
if (xA < xO)
|
||||||
|
angle = - angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return angle;
|
||||||
|
};
|
||||||
|
|
||||||
50
extension.js
50
extension.js
|
|
@ -35,7 +35,8 @@ const PanelMenu = imports.ui.panelMenu;
|
||||||
const ExtensionUtils = imports.misc.extensionUtils;
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
const Me = ExtensionUtils.getCurrentExtension();
|
const Me = ExtensionUtils.getCurrentExtension();
|
||||||
const Convenience = ExtensionUtils.getSettings && ExtensionUtils.initTranslations ? ExtensionUtils : Me.imports.convenience;
|
const Convenience = ExtensionUtils.getSettings && ExtensionUtils.initTranslations ? ExtensionUtils : Me.imports.convenience;
|
||||||
const Draw = Me.imports.draw;
|
const Area = Me.imports.area;
|
||||||
|
const Helper = Me.imports.helper;
|
||||||
const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext;
|
const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext;
|
||||||
|
|
||||||
const GS_VERSION = Config.PACKAGE_VERSION;
|
const GS_VERSION = Config.PACKAGE_VERSION;
|
||||||
|
|
@ -154,9 +155,9 @@ var AreaManager = new Lang.Class({
|
||||||
for (let i = 0; i < this.monitors.length; i++) {
|
for (let i = 0; i < this.monitors.length; i++) {
|
||||||
let monitor = this.monitors[i];
|
let monitor = this.monitors[i];
|
||||||
let container = new St.Widget({ name: 'drawOnYourSreenContainer' + i });
|
let container = new St.Widget({ name: 'drawOnYourSreenContainer' + i });
|
||||||
let helper = new Draw.DrawingHelper({ name: 'drawOnYourSreenHelper' + i }, monitor);
|
let helper = new Helper.DrawingHelper({ name: 'drawOnYourSreenHelper' + i }, monitor);
|
||||||
let loadPersistent = i == Main.layoutManager.primaryIndex && this.settings.get_boolean('persistent-drawing');
|
let loadPersistent = i == Main.layoutManager.primaryIndex && this.settings.get_boolean('persistent-drawing');
|
||||||
let area = new Draw.DrawingArea({ name: 'drawOnYourSreenArea' + i }, monitor, helper, loadPersistent);
|
let area = new Area.DrawingArea({ name: 'drawOnYourSreenArea' + i }, monitor, helper, loadPersistent);
|
||||||
container.add_child(area);
|
container.add_child(area);
|
||||||
container.add_child(helper);
|
container.add_child(helper);
|
||||||
|
|
||||||
|
|
@ -170,6 +171,7 @@ var AreaManager = new Lang.Class({
|
||||||
area.leaveDrawingHandler = area.connect('leave-drawing-mode', this.toggleDrawing.bind(this));
|
area.leaveDrawingHandler = area.connect('leave-drawing-mode', this.toggleDrawing.bind(this));
|
||||||
area.updateActionModeHandler = area.connect('update-action-mode', this.updateActionMode.bind(this));
|
area.updateActionModeHandler = area.connect('update-action-mode', this.updateActionMode.bind(this));
|
||||||
area.showOsdHandler = area.connect('show-osd', this.showOsd.bind(this));
|
area.showOsdHandler = area.connect('show-osd', this.showOsd.bind(this));
|
||||||
|
area.showOsdGiconHandler = area.connect('show-osd-gicon', this.showOsd.bind(this));
|
||||||
this.areas.push(area);
|
this.areas.push(area);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -185,21 +187,23 @@ var AreaManager = new Lang.Class({
|
||||||
'decrement-line-width': () => this.activeArea.incrementLineWidth(-1),
|
'decrement-line-width': () => this.activeArea.incrementLineWidth(-1),
|
||||||
'increment-line-width-more': () => this.activeArea.incrementLineWidth(5),
|
'increment-line-width-more': () => this.activeArea.incrementLineWidth(5),
|
||||||
'decrement-line-width-more': () => this.activeArea.incrementLineWidth(-5),
|
'decrement-line-width-more': () => this.activeArea.incrementLineWidth(-5),
|
||||||
'toggle-linejoin': this.activeArea.toggleLineJoin.bind(this.activeArea),
|
'switch-linejoin': this.activeArea.switchLineJoin.bind(this.activeArea),
|
||||||
'toggle-linecap': this.activeArea.toggleLineCap.bind(this.activeArea),
|
'switch-linecap': this.activeArea.switchLineCap.bind(this.activeArea),
|
||||||
'toggle-fill-rule': this.activeArea.toggleFillRule.bind(this.activeArea),
|
'switch-fill-rule': this.activeArea.switchFillRule.bind(this.activeArea),
|
||||||
'toggle-dash' : this.activeArea.toggleDash.bind(this.activeArea),
|
'switch-dash' : this.activeArea.switchDash.bind(this.activeArea),
|
||||||
'toggle-fill' : this.activeArea.toggleFill.bind(this.activeArea),
|
'switch-fill' : this.activeArea.switchFill.bind(this.activeArea),
|
||||||
'select-none-shape': () => this.activeArea.selectTool(Draw.Tools.NONE),
|
'switch-image-file' : this.activeArea.switchImageFile.bind(this.activeArea),
|
||||||
'select-line-shape': () => this.activeArea.selectTool(Draw.Tools.LINE),
|
'select-none-shape': () => this.activeArea.selectTool(Area.Tools.NONE),
|
||||||
'select-ellipse-shape': () => this.activeArea.selectTool(Draw.Tools.ELLIPSE),
|
'select-line-shape': () => this.activeArea.selectTool(Area.Tools.LINE),
|
||||||
'select-rectangle-shape': () => this.activeArea.selectTool(Draw.Tools.RECTANGLE),
|
'select-ellipse-shape': () => this.activeArea.selectTool(Area.Tools.ELLIPSE),
|
||||||
'select-text-shape': () => this.activeArea.selectTool(Draw.Tools.TEXT),
|
'select-rectangle-shape': () => this.activeArea.selectTool(Area.Tools.RECTANGLE),
|
||||||
'select-polygon-shape': () => this.activeArea.selectTool(Draw.Tools.POLYGON),
|
'select-text-shape': () => this.activeArea.selectTool(Area.Tools.TEXT),
|
||||||
'select-polyline-shape': () => this.activeArea.selectTool(Draw.Tools.POLYLINE),
|
'select-image-shape': () => this.activeArea.selectTool(Area.Tools.IMAGE),
|
||||||
'select-move-tool': () => this.activeArea.selectTool(Draw.Tools.MOVE),
|
'select-polygon-shape': () => this.activeArea.selectTool(Area.Tools.POLYGON),
|
||||||
'select-resize-tool': () => this.activeArea.selectTool(Draw.Tools.RESIZE),
|
'select-polyline-shape': () => this.activeArea.selectTool(Area.Tools.POLYLINE),
|
||||||
'select-mirror-tool': () => this.activeArea.selectTool(Draw.Tools.MIRROR)
|
'select-move-tool': () => this.activeArea.selectTool(Area.Tools.MOVE),
|
||||||
|
'select-resize-tool': () => this.activeArea.selectTool(Area.Tools.RESIZE),
|
||||||
|
'select-mirror-tool': () => this.activeArea.selectTool(Area.Tools.MIRROR)
|
||||||
};
|
};
|
||||||
|
|
||||||
// available when writing
|
// available when writing
|
||||||
|
|
@ -211,10 +215,11 @@ var AreaManager = new Lang.Class({
|
||||||
'toggle-background': this.activeArea.toggleBackground.bind(this.activeArea),
|
'toggle-background': this.activeArea.toggleBackground.bind(this.activeArea),
|
||||||
'toggle-grid': this.activeArea.toggleGrid.bind(this.activeArea),
|
'toggle-grid': this.activeArea.toggleGrid.bind(this.activeArea),
|
||||||
'toggle-square-area': this.activeArea.toggleSquareArea.bind(this.activeArea),
|
'toggle-square-area': this.activeArea.toggleSquareArea.bind(this.activeArea),
|
||||||
'toggle-font-family': this.activeArea.toggleFontFamily.bind(this.activeArea),
|
'reverse-switch-font-family': this.activeArea.switchFontFamily.bind(this.activeArea, true),
|
||||||
'toggle-font-weight': this.activeArea.toggleFontWeight.bind(this.activeArea),
|
'switch-font-family': this.activeArea.switchFontFamily.bind(this.activeArea, false),
|
||||||
'toggle-font-style': this.activeArea.toggleFontStyle.bind(this.activeArea),
|
'switch-font-weight': this.activeArea.switchFontWeight.bind(this.activeArea),
|
||||||
'toggle-text-alignment': this.activeArea.toggleTextAlignment.bind(this.activeArea),
|
'switch-font-style': this.activeArea.switchFontStyle.bind(this.activeArea),
|
||||||
|
'switch-text-alignment': this.activeArea.switchTextAlignment.bind(this.activeArea),
|
||||||
'toggle-panel-and-dock-visibility': this.togglePanelAndDockOpacity.bind(this),
|
'toggle-panel-and-dock-visibility': this.togglePanelAndDockOpacity.bind(this),
|
||||||
'toggle-help': this.activeArea.toggleHelp.bind(this.activeArea),
|
'toggle-help': this.activeArea.toggleHelp.bind(this.activeArea),
|
||||||
'open-user-stylesheet': this.openUserStyleFile.bind(this),
|
'open-user-stylesheet': this.openUserStyleFile.bind(this),
|
||||||
|
|
@ -504,6 +509,7 @@ var AreaManager = new Lang.Class({
|
||||||
area.disconnect(area.leaveDrawingHandler);
|
area.disconnect(area.leaveDrawingHandler);
|
||||||
area.disconnect(area.updateActionModeHandler);
|
area.disconnect(area.updateActionModeHandler);
|
||||||
area.disconnect(area.showOsdHandler);
|
area.disconnect(area.showOsdHandler);
|
||||||
|
area.disconnect(area.showOsdGiconHandler);
|
||||||
let container = area.get_parent();
|
let container = area.get_parent();
|
||||||
container.get_parent().remove_actor(container);
|
container.get_parent().remove_actor(container);
|
||||||
container.destroy();
|
container.destroy();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,241 @@
|
||||||
|
/* jslint esversion: 6 */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const ByteArray = imports.byteArray;
|
||||||
|
const Gdk = imports.gi.Gdk;
|
||||||
|
const GdkPixbuf = imports.gi.GdkPixbuf;
|
||||||
|
const Gio = imports.gi.Gio;
|
||||||
|
const GLib = imports.gi.GLib;
|
||||||
|
const Lang = imports.lang;
|
||||||
|
|
||||||
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
|
const Me = ExtensionUtils.getCurrentExtension();
|
||||||
|
const EXAMPLE_IMAGES = Me.dir.get_child('data').get_child('images');
|
||||||
|
const USER_IMAGES = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir'], 'images']));
|
||||||
|
|
||||||
|
// wrapper around an image file
|
||||||
|
var Image = new Lang.Class({
|
||||||
|
Name: 'DrawOnYourScreenImage',
|
||||||
|
|
||||||
|
_init: function(params) {
|
||||||
|
for (let key in params)
|
||||||
|
this[key] = params[key];
|
||||||
|
},
|
||||||
|
|
||||||
|
toString: function() {
|
||||||
|
return this.displayName;
|
||||||
|
},
|
||||||
|
|
||||||
|
toJson: function() {
|
||||||
|
return {
|
||||||
|
displayName: this.displayName,
|
||||||
|
contentType: this.contentType,
|
||||||
|
base64: this.base64,
|
||||||
|
hash: this.hash
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// only called from menu so file exists
|
||||||
|
get gicon() {
|
||||||
|
if (!this._gicon)
|
||||||
|
this._gicon = new Gio.FileIcon({ file: this.file });
|
||||||
|
return this._gicon;
|
||||||
|
},
|
||||||
|
|
||||||
|
get bytes() {
|
||||||
|
if (!this._bytes) {
|
||||||
|
if (this.file)
|
||||||
|
try {
|
||||||
|
// load_bytes available in GLib 2.56+
|
||||||
|
this._bytes = this.file.load_bytes(null)[0];
|
||||||
|
} catch(e) {
|
||||||
|
let [success_, contents] = this.file.load_contents(null);
|
||||||
|
if (contents instanceof Uint8Array)
|
||||||
|
this._bytes = ByteArray.toGBytes(contents);
|
||||||
|
else
|
||||||
|
this._bytes = contents.toGBytes();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this._bytes = new GLib.Bytes(GLib.base64_decode(this.base64));
|
||||||
|
}
|
||||||
|
return this._bytes;
|
||||||
|
},
|
||||||
|
|
||||||
|
get base64() {
|
||||||
|
if (!this._base64)
|
||||||
|
this._base64 = GLib.base64_encode(this.bytes.get_data());
|
||||||
|
return this._base64;
|
||||||
|
},
|
||||||
|
|
||||||
|
set base64(base64) {
|
||||||
|
this._base64 = base64;
|
||||||
|
},
|
||||||
|
|
||||||
|
// hash is not used
|
||||||
|
get hash() {
|
||||||
|
if (!this._hash)
|
||||||
|
this._hash = this.bytes.hash();
|
||||||
|
return this._hash;
|
||||||
|
},
|
||||||
|
|
||||||
|
set hash(hash) {
|
||||||
|
this._hash = hash;
|
||||||
|
},
|
||||||
|
|
||||||
|
get pixbuf() {
|
||||||
|
if (!this._pixbuf) {
|
||||||
|
let stream = Gio.MemoryInputStream.new_from_bytes(this.bytes);
|
||||||
|
this._pixbuf = GdkPixbuf.Pixbuf.new_from_stream(stream, null);
|
||||||
|
stream.close(null);
|
||||||
|
}
|
||||||
|
return this._pixbuf;
|
||||||
|
},
|
||||||
|
|
||||||
|
getPixbufAtScale: function(width, height) {
|
||||||
|
let stream = Gio.MemoryInputStream.new_from_bytes(this.bytes);
|
||||||
|
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);
|
||||||
|
Gdk.cairo_set_source_pixbuf(cr, pixbuf, x, y);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var getImages = function() {
|
||||||
|
let images = [];
|
||||||
|
|
||||||
|
[EXAMPLE_IMAGES, USER_IMAGES].forEach(directory => {
|
||||||
|
let enumerator;
|
||||||
|
try {
|
||||||
|
enumerator = directory.enumerate_children('standard::display-name,standard::content-type', Gio.FileQueryInfoFlags.NONE, null);
|
||||||
|
} catch(e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileInfo = enumerator.next_file(null);
|
||||||
|
while (fileInfo) {
|
||||||
|
if (fileInfo.get_content_type().indexOf('image') == 0)
|
||||||
|
images.push(new Image({ file: enumerator.get_child(fileInfo), contentType: fileInfo.get_content_type(), displayName: fileInfo.get_display_name() }));
|
||||||
|
fileInfo = enumerator.next_file(null);
|
||||||
|
}
|
||||||
|
enumerator.close(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
images.sort((a, b) => {
|
||||||
|
return a.displayName.localeCompare(b.displayName);
|
||||||
|
});
|
||||||
|
|
||||||
|
return images;
|
||||||
|
};
|
||||||
|
|
||||||
|
// wrapper around a json file
|
||||||
|
var Json = new Lang.Class({
|
||||||
|
Name: 'DrawOnYourScreenJson',
|
||||||
|
|
||||||
|
_init: function(params) {
|
||||||
|
for (let key in params)
|
||||||
|
this[key] = params[key];
|
||||||
|
},
|
||||||
|
|
||||||
|
toString: function() {
|
||||||
|
return this.displayName || this.name;
|
||||||
|
},
|
||||||
|
|
||||||
|
delete: function() {
|
||||||
|
this.file.delete(null);
|
||||||
|
},
|
||||||
|
|
||||||
|
get file() {
|
||||||
|
if (!this._file && this.name)
|
||||||
|
this._file = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir'], `${this.name}.json`]));
|
||||||
|
|
||||||
|
return this._file || null;
|
||||||
|
},
|
||||||
|
|
||||||
|
set file(file) {
|
||||||
|
this._file = file;
|
||||||
|
},
|
||||||
|
|
||||||
|
get contents() {
|
||||||
|
let success_, contents;
|
||||||
|
try {
|
||||||
|
[success_, contents] = this.file.load_contents(null);
|
||||||
|
if (contents instanceof Uint8Array)
|
||||||
|
contents = ByteArray.toString(contents);
|
||||||
|
} catch(e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return contents;
|
||||||
|
},
|
||||||
|
|
||||||
|
set contents(contents) {
|
||||||
|
try {
|
||||||
|
this.file.replace_contents(contents, null, false, Gio.FileCreateFlags.NONE, null);
|
||||||
|
} catch(e) {
|
||||||
|
this.file.get_parent().make_directory_with_parents(null);
|
||||||
|
this.file.replace_contents(contents, null, false, Gio.FileCreateFlags.NONE, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var getJsons = function() {
|
||||||
|
let directory = Gio.File.new_for_path(GLib.build_filenamev([GLib.get_user_data_dir(), Me.metadata['data-dir']]));
|
||||||
|
|
||||||
|
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 jsons = [];
|
||||||
|
let fileInfo = enumerator.next_file(null);
|
||||||
|
while (fileInfo) {
|
||||||
|
if (fileInfo.get_content_type().indexOf('json') != -1 && fileInfo.get_name() != `${Me.metadata['persistent-file-name']}.json`) {
|
||||||
|
let file = enumerator.get_child(fileInfo);
|
||||||
|
jsons.push(new Json({
|
||||||
|
file,
|
||||||
|
name: fileInfo.get_name().slice(0, -5),
|
||||||
|
displayName: fileInfo.get_display_name().slice(0, -5),
|
||||||
|
// fileInfo.get_modification_date_time: Gio 2.62+
|
||||||
|
modificationUnixTime: fileInfo.get_attribute_uint64('time::modified')
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
fileInfo = enumerator.next_file(null);
|
||||||
|
}
|
||||||
|
enumerator.close(null);
|
||||||
|
|
||||||
|
jsons.sort((a, b) => {
|
||||||
|
return b.modificationUnixTime - a.modificationUnixTime;
|
||||||
|
});
|
||||||
|
|
||||||
|
return jsons;
|
||||||
|
};
|
||||||
|
|
||||||
|
var getDateString = function() {
|
||||||
|
let date = GLib.DateTime.new_now_local();
|
||||||
|
return `${date.format("%F")} ${date.format("%X")}`;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,186 @@
|
||||||
|
/* jslint esversion: 6 */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Gtk = imports.gi.Gtk;
|
||||||
|
const Lang = imports.lang;
|
||||||
|
const St = imports.gi.St;
|
||||||
|
|
||||||
|
const Config = imports.misc.config;
|
||||||
|
const Tweener = imports.ui.tweener;
|
||||||
|
|
||||||
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
|
const Me = ExtensionUtils.getCurrentExtension();
|
||||||
|
const Convenience = ExtensionUtils.getSettings ? ExtensionUtils : Me.imports.convenience;
|
||||||
|
const Prefs = Me.imports.prefs;
|
||||||
|
const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext;
|
||||||
|
|
||||||
|
const GS_VERSION = Config.PACKAGE_VERSION;
|
||||||
|
|
||||||
|
const HELPER_ANIMATION_TIME = 0.25;
|
||||||
|
const MEDIA_KEYS_SCHEMA = 'org.gnome.settings-daemon.plugins.media-keys';
|
||||||
|
const MEDIA_KEYS_KEYS = {
|
||||||
|
'screenshot': "Screenshot",
|
||||||
|
'screenshot-clip': "Screenshot to clipboard",
|
||||||
|
'area-screenshot': "Area screenshot",
|
||||||
|
'area-screenshot-clip': "Area screenshot to clipboard"
|
||||||
|
};
|
||||||
|
|
||||||
|
// DrawingHelper provides the "help osd" (Ctrl + F1)
|
||||||
|
// It uses the same texts as in prefs
|
||||||
|
var DrawingHelper = new Lang.Class({
|
||||||
|
Name: 'DrawOnYourScreenDrawingHelper',
|
||||||
|
Extends: St.ScrollView,
|
||||||
|
|
||||||
|
_init: function(params, monitor) {
|
||||||
|
params.style_class = 'osd-window draw-on-your-screen-helper';
|
||||||
|
this.parent(params);
|
||||||
|
this.monitor = monitor;
|
||||||
|
this.hide();
|
||||||
|
this.settings = Convenience.getSettings();
|
||||||
|
|
||||||
|
this.settingHandler = this.settings.connect('changed', this._onSettingChanged.bind(this));
|
||||||
|
this.connect('destroy', () => this.settings.disconnect(this.settingHandler));
|
||||||
|
},
|
||||||
|
|
||||||
|
_onSettingChanged: function(settings, key) {
|
||||||
|
if (key == 'toggle-help')
|
||||||
|
this._updateHelpKeyLabel();
|
||||||
|
|
||||||
|
if (this.vbox) {
|
||||||
|
this.vbox.destroy();
|
||||||
|
this.vbox = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateHelpKeyLabel: function() {
|
||||||
|
let [keyval, mods] = Gtk.accelerator_parse(this.settings.get_strv('toggle-help')[0]);
|
||||||
|
this._helpKeyLabel = Gtk.accelerator_get_label(keyval, mods);
|
||||||
|
},
|
||||||
|
|
||||||
|
get helpKeyLabel() {
|
||||||
|
if (!this._helpKeyLabel)
|
||||||
|
this._updateHelpKeyLabel();
|
||||||
|
|
||||||
|
return this._helpKeyLabel;
|
||||||
|
},
|
||||||
|
|
||||||
|
_populate: function() {
|
||||||
|
this.vbox = new St.BoxLayout({ vertical: true });
|
||||||
|
this.add_actor(this.vbox);
|
||||||
|
this.vbox.add_child(new St.Label({ text: _("Global") }));
|
||||||
|
|
||||||
|
for (let settingKey in Prefs.GLOBAL_KEYBINDINGS) {
|
||||||
|
let hbox = new St.BoxLayout({ vertical: false });
|
||||||
|
if (settingKey.indexOf('-separator-') != -1) {
|
||||||
|
this.vbox.add_child(hbox);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!this.settings.get_strv(settingKey)[0])
|
||||||
|
continue;
|
||||||
|
let [keyval, mods] = Gtk.accelerator_parse(this.settings.get_strv(settingKey)[0]);
|
||||||
|
hbox.add_child(new St.Label({ text: _(Prefs.GLOBAL_KEYBINDINGS[settingKey]) }));
|
||||||
|
hbox.add_child(new St.Label({ text: Gtk.accelerator_get_label(keyval, mods), x_expand: true }));
|
||||||
|
this.vbox.add_child(hbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.vbox.add_child(new St.Label({ text: _("Internal") }));
|
||||||
|
|
||||||
|
for (let i = 0; i < Prefs.OTHER_SHORTCUTS.length; i++) {
|
||||||
|
if (Prefs.OTHER_SHORTCUTS[i].desc.indexOf('-separator-') != -1) {
|
||||||
|
this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' }));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let hbox = new St.BoxLayout({ vertical: false });
|
||||||
|
hbox.add_child(new St.Label({ text: _(Prefs.OTHER_SHORTCUTS[i].desc) }));
|
||||||
|
hbox.add_child(new St.Label({ text: Prefs.OTHER_SHORTCUTS[i].shortcut, x_expand: true }));
|
||||||
|
hbox.get_children()[0].get_clutter_text().set_use_markup(true);
|
||||||
|
this.vbox.add_child(hbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' }));
|
||||||
|
|
||||||
|
for (let settingKey in Prefs.INTERNAL_KEYBINDINGS) {
|
||||||
|
if (settingKey.indexOf('-separator-') != -1) {
|
||||||
|
this.vbox.add_child(new St.BoxLayout({ vertical: false, style_class: 'draw-on-your-screen-helper-separator' }));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let hbox = new St.BoxLayout({ vertical: false });
|
||||||
|
if (!this.settings.get_strv(settingKey)[0])
|
||||||
|
continue;
|
||||||
|
let [keyval, mods] = Gtk.accelerator_parse(this.settings.get_strv(settingKey)[0]);
|
||||||
|
hbox.add_child(new St.Label({ text: _(Prefs.INTERNAL_KEYBINDINGS[settingKey]) }));
|
||||||
|
hbox.add_child(new St.Label({ text: Gtk.accelerator_get_label(keyval, mods), x_expand: true }));
|
||||||
|
this.vbox.add_child(hbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mediaKeysSettings;
|
||||||
|
try { mediaKeysSettings = Convenience.getSettings(MEDIA_KEYS_SCHEMA); } catch(e) { return; }
|
||||||
|
this.vbox.add_child(new St.Label({ text: _("System") }));
|
||||||
|
|
||||||
|
for (let settingKey in MEDIA_KEYS_KEYS) {
|
||||||
|
if (!mediaKeysSettings.settings_schema.has_key(settingKey))
|
||||||
|
continue;
|
||||||
|
let shortcut = GS_VERSION < '3.33.0' ? mediaKeysSettings.get_string(settingKey) : mediaKeysSettings.get_strv(settingKey)[0];
|
||||||
|
if (!shortcut)
|
||||||
|
continue;
|
||||||
|
let [keyval, mods] = Gtk.accelerator_parse(shortcut);
|
||||||
|
let hbox = new St.BoxLayout({ vertical: false });
|
||||||
|
hbox.add_child(new St.Label({ text: _(MEDIA_KEYS_KEYS[settingKey]) }));
|
||||||
|
hbox.add_child(new St.Label({ text: Gtk.accelerator_get_label(keyval, mods), x_expand: true }));
|
||||||
|
this.vbox.add_child(hbox);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showHelp: function() {
|
||||||
|
if (!this.vbox)
|
||||||
|
this._populate();
|
||||||
|
|
||||||
|
this.opacity = 0;
|
||||||
|
this.show();
|
||||||
|
|
||||||
|
let maxHeight = this.monitor.height * 3 / 4;
|
||||||
|
this.set_height(Math.min(this.height, maxHeight));
|
||||||
|
this.set_position(Math.floor(this.monitor.width / 2 - this.width / 2),
|
||||||
|
Math.floor(this.monitor.height / 2 - this.height / 2));
|
||||||
|
|
||||||
|
if (this.height == maxHeight)
|
||||||
|
this.vscrollbar_policy = Gtk.PolicyType.ALWAYS;
|
||||||
|
else
|
||||||
|
this.vscrollbar_policy = Gtk.PolicyType.NEVER;
|
||||||
|
|
||||||
|
Tweener.removeTweens(this);
|
||||||
|
Tweener.addTween(this, { opacity: 255,
|
||||||
|
time: HELPER_ANIMATION_TIME,
|
||||||
|
transition: 'easeOutQuad',
|
||||||
|
onComplete: null });
|
||||||
|
},
|
||||||
|
|
||||||
|
hideHelp: function() {
|
||||||
|
Tweener.removeTweens(this);
|
||||||
|
Tweener.addTween(this, { opacity: 0,
|
||||||
|
time: HELPER_ANIMATION_TIME,
|
||||||
|
transition: 'easeOutQuad',
|
||||||
|
onComplete: this.hide.bind(this) });
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
@ -179,6 +179,9 @@ msgstr ""
|
||||||
msgid "Select polyline"
|
msgid "Select polyline"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Select image"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Select text"
|
msgid "Select text"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -215,7 +218,10 @@ msgstr ""
|
||||||
msgid "Toggle fill rule"
|
msgid "Toggle fill rule"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Change font family (generic name)"
|
msgid "Change font family"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Change font family (reverse)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Change font weight"
|
msgid "Change font weight"
|
||||||
|
|
@ -227,6 +233,9 @@ msgstr ""
|
||||||
msgid "Toggle text alignment"
|
msgid "Toggle text alignment"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Change image file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Hide panel and dock"
|
msgid "Hide panel and dock"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
@ -316,6 +325,9 @@ msgstr ""
|
||||||
msgid "Polyline"
|
msgid "Polyline"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Image"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Move"
|
msgid "Move"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,655 @@
|
||||||
|
/* jslint esversion: 6 */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Clutter = imports.gi.Clutter;
|
||||||
|
const Gio = imports.gi.Gio;
|
||||||
|
const GLib = imports.gi.GLib;
|
||||||
|
const GObject = imports.gi.GObject;
|
||||||
|
const Gtk = imports.gi.Gtk;
|
||||||
|
const Lang = imports.lang;
|
||||||
|
const St = imports.gi.St;
|
||||||
|
|
||||||
|
const BoxPointer = imports.ui.boxpointer;
|
||||||
|
const Config = imports.misc.config;
|
||||||
|
const Main = imports.ui.main;
|
||||||
|
const PopupMenu = imports.ui.popupMenu;
|
||||||
|
const Slider = imports.ui.slider;
|
||||||
|
|
||||||
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
|
const Me = ExtensionUtils.getCurrentExtension();
|
||||||
|
const Area = Me.imports.area;
|
||||||
|
const Elements = Me.imports.elements;
|
||||||
|
const Extension = Me.imports.extension;
|
||||||
|
const Files = Me.imports.files;
|
||||||
|
const _ = imports.gettext.domain(Me.metadata['gettext-domain']).gettext;
|
||||||
|
|
||||||
|
const GS_VERSION = Config.PACKAGE_VERSION;
|
||||||
|
|
||||||
|
const ICON_DIR = Me.dir.get_child('data').get_child('icons');
|
||||||
|
const SMOOTH_ICON_PATH = ICON_DIR.get_child('smooth-symbolic.svg').get_path();
|
||||||
|
const COLOR_ICON_PATH = ICON_DIR.get_child('color-symbolic.svg').get_path();
|
||||||
|
const FILL_ICON_PATH = ICON_DIR.get_child('fill-symbolic.svg').get_path();
|
||||||
|
const STROKE_ICON_PATH = ICON_DIR.get_child('stroke-symbolic.svg').get_path();
|
||||||
|
const LINEJOIN_ICON_PATH = ICON_DIR.get_child('linejoin-symbolic.svg').get_path();
|
||||||
|
const LINECAP_ICON_PATH = ICON_DIR.get_child('linecap-symbolic.svg').get_path();
|
||||||
|
const FILLRULE_NONZERO_ICON_PATH = ICON_DIR.get_child('fillrule-nonzero-symbolic.svg').get_path();
|
||||||
|
const FILLRULE_EVENODD_ICON_PATH = ICON_DIR.get_child('fillrule-evenodd-symbolic.svg').get_path();
|
||||||
|
const DASHED_LINE_ICON_PATH = ICON_DIR.get_child('dashed-line-symbolic.svg').get_path();
|
||||||
|
const FULL_LINE_ICON_PATH = ICON_DIR.get_child('full-line-symbolic.svg').get_path();
|
||||||
|
|
||||||
|
// 150 labels with font-family style take ~15Mo
|
||||||
|
const FONT_FAMILY_STYLE = true;
|
||||||
|
|
||||||
|
const getActor = function(object) {
|
||||||
|
return GS_VERSION < '3.33.0' ? object.actor : object;
|
||||||
|
};
|
||||||
|
|
||||||
|
var DrawingMenu = new Lang.Class({
|
||||||
|
Name: 'DrawOnYourScreenDrawingMenu',
|
||||||
|
|
||||||
|
_init: function(area, monitor) {
|
||||||
|
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(GS_VERSION < '3.33.0' ? { actor: this.area } : 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.set_style('max-height:' + monitor.height + 'px;');
|
||||||
|
this.menu.actor.hide();
|
||||||
|
this.hasSeparators = monitor.height >= 750;
|
||||||
|
|
||||||
|
// do not close the menu on item activated
|
||||||
|
this.menu.itemActivated = () => {};
|
||||||
|
this.menu.connect('open-state-changed', this._onMenuOpenStateChanged.bind(this));
|
||||||
|
|
||||||
|
// Case where the menu is closed (escape key) while the save entry clutter_text is active:
|
||||||
|
// St.Entry clutter_text set the DEFAULT cursor on leave event with a delay and
|
||||||
|
// overrides the cursor set by area.updatePointerCursor().
|
||||||
|
// In order to update drawing cursor on menu closed, we need to leave the saveEntry before closing menu.
|
||||||
|
// Since escape key press event can't be captured easily, the job is done in the menu close function.
|
||||||
|
let menuCloseFunc = this.menu.close;
|
||||||
|
this.menu.close = (animate) => {
|
||||||
|
if (this.saveDrawingSubMenu && this.saveDrawingSubMenu.isOpen)
|
||||||
|
this.saveDrawingSubMenu.close();
|
||||||
|
menuCloseFunc.bind(this.menu)(animate);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.colorIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(COLOR_ICON_PATH) });
|
||||||
|
this.smoothIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(SMOOTH_ICON_PATH) });
|
||||||
|
this.strokeIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(STROKE_ICON_PATH) });
|
||||||
|
this.fillIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(FILL_ICON_PATH) });
|
||||||
|
this.fillRuleNonzeroIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(FILLRULE_NONZERO_ICON_PATH) });
|
||||||
|
this.fillRuleEvenoddIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(FILLRULE_EVENODD_ICON_PATH) });
|
||||||
|
this.linejoinIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(LINEJOIN_ICON_PATH) });
|
||||||
|
this.linecapIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(LINECAP_ICON_PATH) });
|
||||||
|
this.fullLineIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(FULL_LINE_ICON_PATH) });
|
||||||
|
this.dashedLineIcon = new Gio.FileIcon({ file: Gio.File.new_for_path(DASHED_LINE_ICON_PATH) });
|
||||||
|
},
|
||||||
|
|
||||||
|
disable: function() {
|
||||||
|
this.menuManager.removeMenu(this.menu);
|
||||||
|
Main.layoutManager.uiGroup.remove_actor(this.menu.actor);
|
||||||
|
this.menu.destroy();
|
||||||
|
},
|
||||||
|
|
||||||
|
_onMenuOpenStateChanged: function(menu, open) {
|
||||||
|
if (open) {
|
||||||
|
this.area.setPointerCursor('DEFAULT');
|
||||||
|
} else {
|
||||||
|
this.area.updatePointerCursor();
|
||||||
|
// actionMode has changed, set previous actionMode in order to keep internal shortcuts working
|
||||||
|
this.area.updateActionMode();
|
||||||
|
this.area.grab_key_focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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.actionButtons = [];
|
||||||
|
let groupItem = new PopupMenu.PopupBaseMenuItem({ reactive: false, can_focus: false, style_class: "draw-on-your-screen-menu-group-item" });
|
||||||
|
getActor(groupItem).add_child(this._createActionButton(_("Undo"), this.area.undo.bind(this.area), 'edit-undo-symbolic'));
|
||||||
|
getActor(groupItem).add_child(this._createActionButton(_("Redo"), this.area.redo.bind(this.area), 'edit-redo-symbolic'));
|
||||||
|
getActor(groupItem).add_child(this._createActionButton(_("Erase"), this.area.deleteLastElement.bind(this.area), 'edit-clear-all-symbolic'));
|
||||||
|
getActor(groupItem).add_child(this._createActionButton(_("Smooth"), this.area.smoothLastElement.bind(this.area), this.smoothIcon));
|
||||||
|
this.menu.addMenuItem(groupItem);
|
||||||
|
this._addSeparator(this.menu, true);
|
||||||
|
|
||||||
|
this._addSubMenuItem(this.menu, 'document-edit-symbolic', Area.ToolNames, this.area, 'currentTool', this._updateSectionVisibility.bind(this));
|
||||||
|
this.colorItem = this._addColorSubMenuItem(this.menu);
|
||||||
|
this.fillItem = this._addSwitchItem(this.menu, _("Fill"), this.strokeIcon, this.fillIcon, this.area, 'fill', this._updateSectionVisibility.bind(this));
|
||||||
|
this.fillSection = new PopupMenu.PopupMenuSection();
|
||||||
|
this.fillSection.itemActivated = () => {};
|
||||||
|
this.fillRuleItem = this._addSwitchItem(this.fillSection, _("Evenodd"), this.fillRuleNonzeroIcon, this.fillRuleEvenoddIcon, this.area, 'currentEvenodd');
|
||||||
|
this.menu.addMenuItem(this.fillSection);
|
||||||
|
this._addSeparator(this.menu);
|
||||||
|
|
||||||
|
let lineSection = new PopupMenu.PopupMenuSection();
|
||||||
|
this._addSliderItem(lineSection, this.area, 'currentLineWidth');
|
||||||
|
this._addSubMenuItem(lineSection, this.linejoinIcon, Elements.LineJoinNames, this.area, 'currentLineJoin');
|
||||||
|
this._addSubMenuItem(lineSection, this.linecapIcon, Elements.LineCapNames, this.area, 'currentLineCap');
|
||||||
|
this._addSwitchItem(lineSection, _("Dashed"), this.fullLineIcon, this.dashedLineIcon, this.area, 'dashedLine');
|
||||||
|
this._addSeparator(lineSection);
|
||||||
|
this.menu.addMenuItem(lineSection);
|
||||||
|
lineSection.itemActivated = () => {};
|
||||||
|
this.lineSection = lineSection;
|
||||||
|
|
||||||
|
let fontSection = new PopupMenu.PopupMenuSection();
|
||||||
|
this._addFontFamilySubMenuItem(fontSection, 'font-x-generic-symbolic');
|
||||||
|
this._addSubMenuItem(fontSection, 'format-text-bold-symbolic', Elements.FontWeightNames, this.area, 'currentFontWeight');
|
||||||
|
this._addSubMenuItem(fontSection, 'format-text-italic-symbolic', Elements.FontStyleNames, this.area, 'currentFontStyle');
|
||||||
|
this._addSwitchItem(fontSection, _("Right aligned"), 'format-justify-left-symbolic', 'format-justify-right-symbolic', this.area, 'currentTextRightAligned');
|
||||||
|
this._addSeparator(fontSection);
|
||||||
|
this.menu.addMenuItem(fontSection);
|
||||||
|
fontSection.itemActivated = () => {};
|
||||||
|
this.fontSection = fontSection;
|
||||||
|
|
||||||
|
let imageSection = new PopupMenu.PopupMenuSection();
|
||||||
|
let images = this.area.getImages();
|
||||||
|
if (images.length) {
|
||||||
|
if (this.area.currentImage > images.length - 1)
|
||||||
|
this.area.currentImage = images.length - 1;
|
||||||
|
this._addSubMenuItem(imageSection, null, images, this.area, 'currentImage');
|
||||||
|
}
|
||||||
|
this._addSeparator(imageSection);
|
||||||
|
this.menu.addMenuItem(imageSection);
|
||||||
|
imageSection.itemActivated = () => {};
|
||||||
|
this.imageSection = imageSection;
|
||||||
|
|
||||||
|
let manager = Extension.manager;
|
||||||
|
this._addSimpleSwitchItem(this.menu, _("Hide panel and dock"), manager.hiddenList ? true : false, manager.togglePanelAndDockOpacity.bind(manager));
|
||||||
|
this._addSimpleSwitchItem(this.menu, _("Add a drawing background"), this.area.hasBackground, this.area.toggleBackground.bind(this.area));
|
||||||
|
this._addSimpleSwitchItem(this.menu, _("Add a grid overlay"), this.area.hasGrid, this.area.toggleGrid.bind(this.area));
|
||||||
|
this._addSimpleSwitchItem(this.menu, _("Square drawing area"), this.area.isSquareArea, this.area.toggleSquareArea.bind(this.area));
|
||||||
|
this._addSeparator(this.menu);
|
||||||
|
|
||||||
|
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(_("Edit style"), manager.openUserStyleFile.bind(manager), 'document-page-setup-symbolic');
|
||||||
|
this.menu.addAction(_("Show help"), () => { this.close(); this.area.toggleHelp(); }, 'preferences-desktop-keyboard-shortcuts-symbolic');
|
||||||
|
|
||||||
|
this._updateActionSensitivity();
|
||||||
|
this._updateSectionVisibility();
|
||||||
|
},
|
||||||
|
|
||||||
|
// from system.js (GS 3.34-)
|
||||||
|
_createActionButton: function(accessibleName, callback, icon) {
|
||||||
|
let button = new St.Button({ track_hover: true,
|
||||||
|
x_align: Clutter.ActorAlign.CENTER,
|
||||||
|
accessible_name: accessibleName,
|
||||||
|
// use 'popup-menu' and 'popup-menu-item' style classes to provide theme colors
|
||||||
|
style_class: 'system-menu-action popup-menu-item popup-menu' });
|
||||||
|
button.child = new St.Icon(typeof icon == 'string' ? { icon_name: icon } : { gicon: icon });
|
||||||
|
button.connect('clicked', () => {
|
||||||
|
callback();
|
||||||
|
this._updateActionSensitivity();
|
||||||
|
});
|
||||||
|
button.bind_property('reactive', button, 'can_focus', GObject.BindingFlags.DEFAULT);
|
||||||
|
this.actionButtons.push(button);
|
||||||
|
return new St.Bin({ child: button, x_expand: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateActionSensitivity: function() {
|
||||||
|
let [undoButton, redoButton, eraseButton, smoothButton] = this.actionButtons;
|
||||||
|
undoButton.reactive = this.area.elements.length > 0;
|
||||||
|
redoButton.reactive = this.area.undoneElements.length > 0;
|
||||||
|
eraseButton.reactive = this.area.elements.length > 0;
|
||||||
|
smoothButton.reactive = this.area.elements.length > 0 && this.area.elements[this.area.elements.length - 1].shape == Area.Tools.NONE;
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateSectionVisibility: function() {
|
||||||
|
let [isText, isImage] = [this.area.currentTool == Area.Tools.TEXT, this.area.currentTool == Area.Tools.IMAGE];
|
||||||
|
this.lineSection.actor.visible = !isText && !isImage;
|
||||||
|
this.fontSection.actor.visible = isText;
|
||||||
|
this.imageSection.actor.visible = isImage;
|
||||||
|
this.colorItem.setSensitive(!isImage);
|
||||||
|
this.fillItem.setSensitive(!isText && !isImage);
|
||||||
|
this.fillSection.setSensitive(!isText && !isImage);
|
||||||
|
|
||||||
|
if (this.area.fill)
|
||||||
|
this.fillSection.actor.show();
|
||||||
|
else
|
||||||
|
this.fillSection.actor.hide();
|
||||||
|
},
|
||||||
|
|
||||||
|
_addSwitchItem: function(menu, label, iconFalse, iconTrue, target, targetProperty, onToggled) {
|
||||||
|
let item = new PopupMenu.PopupSwitchMenuItem(label, target[targetProperty]);
|
||||||
|
|
||||||
|
item.icon = new St.Icon({ style_class: 'popup-menu-icon' });
|
||||||
|
getActor(item).insert_child_at_index(item.icon, 1);
|
||||||
|
let icon = target[targetProperty] ? iconTrue : iconFalse;
|
||||||
|
if (icon && icon instanceof GObject.Object && GObject.type_is_a(icon, Gio.Icon))
|
||||||
|
item.icon.set_gicon(icon);
|
||||||
|
else if (icon)
|
||||||
|
item.icon.set_icon_name(icon);
|
||||||
|
|
||||||
|
item.connect('toggled', (item, state) => {
|
||||||
|
target[targetProperty] = state;
|
||||||
|
let icon = target[targetProperty] ? iconTrue : iconFalse;
|
||||||
|
if (icon && icon instanceof GObject.Object && GObject.type_is_a(icon, Gio.Icon))
|
||||||
|
item.icon.set_gicon(icon);
|
||||||
|
else if (icon)
|
||||||
|
item.icon.set_icon_name(icon);
|
||||||
|
if (onToggled)
|
||||||
|
onToggled();
|
||||||
|
});
|
||||||
|
menu.addMenuItem(item);
|
||||||
|
return item;
|
||||||
|
},
|
||||||
|
|
||||||
|
_addSimpleSwitchItem: 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: _("%d px").format(target[targetProperty]), style_class: 'draw-on-your-screen-menu-slider-label' });
|
||||||
|
let slider = new Slider.Slider(target[targetProperty] / 50);
|
||||||
|
|
||||||
|
if (GS_VERSION < '3.33.0') {
|
||||||
|
slider.connect('value-changed', (slider, value, property) => {
|
||||||
|
target[targetProperty] = Math.max(Math.round(value * 50), 0);
|
||||||
|
label.set_text(target[targetProperty] + " px");
|
||||||
|
if (target[targetProperty] === 0)
|
||||||
|
label.add_style_class_name(Extension.WARNING_COLOR_STYLE_CLASS_NAME);
|
||||||
|
else
|
||||||
|
label.remove_style_class_name(Extension.WARNING_COLOR_STYLE_CLASS_NAME);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
slider.connect('notify::value', () => {
|
||||||
|
target[targetProperty] = Math.max(Math.round(slider.value * 50), 0);
|
||||||
|
label.set_text(target[targetProperty] + " px");
|
||||||
|
if (target[targetProperty] === 0)
|
||||||
|
label.add_style_class_name(Extension.WARNING_COLOR_STYLE_CLASS_NAME);
|
||||||
|
else
|
||||||
|
label.remove_style_class_name(Extension.WARNING_COLOR_STYLE_CLASS_NAME);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getActor(slider).x_expand = true;
|
||||||
|
getActor(item).add_child(getActor(slider));
|
||||||
|
getActor(item).add_child(label);
|
||||||
|
if (slider.onKeyPressEvent)
|
||||||
|
getActor(item).connect('key-press-event', slider.onKeyPressEvent.bind(slider));
|
||||||
|
menu.addMenuItem(item);
|
||||||
|
},
|
||||||
|
|
||||||
|
_addSubMenuItem: function(menu, icon, obj, target, targetProperty, callback) {
|
||||||
|
if (targetProperty == 'currentImage')
|
||||||
|
icon = obj[target[targetProperty]].gicon;
|
||||||
|
let item = new PopupMenu.PopupSubMenuMenuItem(_(String(obj[target[targetProperty]])), icon ? true : false);
|
||||||
|
if (icon && icon instanceof GObject.Object && GObject.type_is_a(icon, Gio.Icon))
|
||||||
|
item.icon.set_gicon(icon);
|
||||||
|
else if (icon)
|
||||||
|
item.icon.set_icon_name(icon);
|
||||||
|
|
||||||
|
item.menu.itemActivated = () => {
|
||||||
|
item.menu.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
|
||||||
|
for (let i in obj) {
|
||||||
|
let text;
|
||||||
|
if (targetProperty == 'currentFontWeight')
|
||||||
|
text = `<span font_weight="${i}">${_(obj[i])}</span>`;
|
||||||
|
else if (targetProperty == 'currentFontStyle')
|
||||||
|
text = `<span font_style="${obj[i].toLowerCase()}">${_(obj[i])}</span>`;
|
||||||
|
else
|
||||||
|
text = _(String(obj[i]));
|
||||||
|
|
||||||
|
let iCaptured = Number(i);
|
||||||
|
let subItem = item.menu.addAction(text, () => {
|
||||||
|
item.label.set_text(_(String(obj[iCaptured])));
|
||||||
|
target[targetProperty] = iCaptured;
|
||||||
|
if (targetProperty == 'currentImage')
|
||||||
|
item.icon.set_gicon(obj[iCaptured].gicon);
|
||||||
|
if (callback)
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
|
||||||
|
subItem.label.get_clutter_text().set_use_markup(true);
|
||||||
|
getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment);
|
||||||
|
|
||||||
|
// change the display order of tools
|
||||||
|
if (obj == Area.ToolNames && i == Area.Tools.POLYGON)
|
||||||
|
item.menu.moveMenuItem(subItem, 4);
|
||||||
|
else if (obj == Area.ToolNames && i == Area.Tools.POLYLINE)
|
||||||
|
item.menu.moveMenuItem(subItem, 5);
|
||||||
|
}
|
||||||
|
return GLib.SOURCE_REMOVE;
|
||||||
|
});
|
||||||
|
menu.addMenuItem(item);
|
||||||
|
},
|
||||||
|
|
||||||
|
_addColorSubMenuItem: function(menu) {
|
||||||
|
let item = new PopupMenu.PopupSubMenuMenuItem(_("Color"), true);
|
||||||
|
item.icon.set_gicon(this.colorIcon);
|
||||||
|
item.icon.set_style(`color:${this.area.currentColor.to_string().slice(0, 7)};`);
|
||||||
|
|
||||||
|
item.menu.itemActivated = () => {
|
||||||
|
item.menu.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
|
||||||
|
for (let i = 1; i < this.area.colors.length; i++) {
|
||||||
|
let text = this.area.colors[i].to_string();
|
||||||
|
let iCaptured = i;
|
||||||
|
let colorItem = item.menu.addAction(text, () => {
|
||||||
|
this.area.currentColor = this.area.colors[iCaptured];
|
||||||
|
item.icon.set_style(`color:${this.area.currentColor.to_string().slice(0, 7)};`);
|
||||||
|
});
|
||||||
|
// Foreground color markup is not displayed since 3.36, use style instead but the transparency is lost.
|
||||||
|
colorItem.label.set_style(`color:${this.area.colors[i].to_string().slice(0, 7)};`);
|
||||||
|
getActor(colorItem).connect('key-focus-in', updateSubMenuAdjustment);
|
||||||
|
}
|
||||||
|
return GLib.SOURCE_REMOVE;
|
||||||
|
});
|
||||||
|
menu.addMenuItem(item);
|
||||||
|
return item;
|
||||||
|
},
|
||||||
|
|
||||||
|
_addFontFamilySubMenuItem: function(menu, icon) {
|
||||||
|
let item = new PopupMenu.PopupSubMenuMenuItem(this.area.currentFontFamily, true);
|
||||||
|
item.icon.set_icon_name(icon);
|
||||||
|
|
||||||
|
item.menu.itemActivated = () => {
|
||||||
|
item.menu.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
item.menu.openOld = item.menu.open;
|
||||||
|
item.menu.open = (animate) => {
|
||||||
|
if (!item.menu.isOpen && item.menu.isEmpty()) {
|
||||||
|
this.area.fontFamilies.forEach(family => {
|
||||||
|
let subItem = item.menu.addAction(_(family), () => {
|
||||||
|
item.label.set_text(_(family));
|
||||||
|
this.area.currentFontFamily = family;
|
||||||
|
});
|
||||||
|
if (FONT_FAMILY_STYLE)
|
||||||
|
subItem.label.set_style(`font-family:${family}`);
|
||||||
|
getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
item.menu.openOld();
|
||||||
|
};
|
||||||
|
|
||||||
|
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) {
|
||||||
|
let prefix = this.area.drawingContentsHasChanged ? "* " : "";
|
||||||
|
this.drawingNameMenuItem.label.set_text(`<i>${prefix}${this.area.jsonName}</i>`);
|
||||||
|
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.setSensitive(Boolean(Files.getJsons().length));
|
||||||
|
item.icon.set_icon_name('document-open-symbolic');
|
||||||
|
|
||||||
|
item.menu.itemActivated = () => {
|
||||||
|
item.menu.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
item.menu.openOld = item.menu.open;
|
||||||
|
item.menu.open = (animate) => {
|
||||||
|
if (!item.menu.isOpen)
|
||||||
|
this._populateOpenDrawingSubMenu();
|
||||||
|
item.menu.openOld();
|
||||||
|
};
|
||||||
|
|
||||||
|
menu.addMenuItem(item);
|
||||||
|
},
|
||||||
|
|
||||||
|
_populateOpenDrawingSubMenu: function() {
|
||||||
|
this.openDrawingSubMenu.removeAll();
|
||||||
|
let jsons = Files.getJsons();
|
||||||
|
jsons.forEach(json => {
|
||||||
|
let subItem = this.openDrawingSubMenu.addAction(`<i>${String(json)}</i>`, () => {
|
||||||
|
this.area.loadJson(json.name);
|
||||||
|
this._updateDrawingNameMenuItem();
|
||||||
|
this._updateSaveDrawingSubMenuItemSensitivity();
|
||||||
|
});
|
||||||
|
subItem.label.get_clutter_text().set_use_markup(true);
|
||||||
|
getActor(subItem).connect('key-focus-in', updateSubMenuAdjustment);
|
||||||
|
|
||||||
|
let expander = new St.Bin({
|
||||||
|
style_class: 'popup-menu-item-expander',
|
||||||
|
x_expand: true,
|
||||||
|
});
|
||||||
|
getActor(subItem).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(subItem).add_child(deleteButton);
|
||||||
|
|
||||||
|
deleteButton.connect('clicked', () => {
|
||||||
|
json.delete();
|
||||||
|
subItem.destroy();
|
||||||
|
this.openDrawingSubMenuItem.setSensitive(!this.openDrawingSubMenu.isEmpty());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
item.menu.openOld = item.menu.open;
|
||||||
|
item.menu.open = (animate) => {
|
||||||
|
if (!item.menu.isOpen)
|
||||||
|
this._populateSaveDrawingSubMenu();
|
||||||
|
item.menu.openOld();
|
||||||
|
};
|
||||||
|
menu.addMenuItem(item);
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateSaveDrawingSubMenuItemSensitivity: function() {
|
||||||
|
this.saveDrawingSubMenuItem.setSensitive(this.area.elements.length > 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
_onDrawingSaved() {
|
||||||
|
this._updateDrawingNameMenuItem();
|
||||||
|
this.openDrawingSubMenuItem.setSensitive(true);
|
||||||
|
},
|
||||||
|
|
||||||
|
_populateSaveDrawingSubMenu: function() {
|
||||||
|
this.saveDrawingSubMenu.removeAll();
|
||||||
|
let saveEntry = new DrawingMenuEntry({ initialTextGetter: Files.getDateString,
|
||||||
|
entryActivateCallback: (text) => {
|
||||||
|
this.area.saveAsJsonWithName(text, this._onDrawingSaved.bind(this));
|
||||||
|
this.saveDrawingSubMenu.toggle();
|
||||||
|
},
|
||||||
|
invalidStrings: [Me.metadata['persistent-file-name'], '/'],
|
||||||
|
primaryIconName: 'insert-text' });
|
||||||
|
this.saveDrawingSubMenu.addMenuItem(saveEntry.item);
|
||||||
|
},
|
||||||
|
|
||||||
|
_addSeparator: function(menu, thin) {
|
||||||
|
if (this.hasSeparators) {
|
||||||
|
let separatorItem = new PopupMenu.PopupSeparatorMenuItem(' ');
|
||||||
|
getActor(separatorItem).add_style_class_name('draw-on-your-screen-menu-separator-item');
|
||||||
|
if (thin)
|
||||||
|
getActor(separatorItem).add_style_class_name('draw-on-your-screen-menu-thin-separator-item');
|
||||||
|
menu.addMenuItem(separatorItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// based on ApplicationsButton.scrollToButton , https://gitlab.gnome.org/GNOME/gnome-shell-extensions/blob/master/extensions/apps-menu/extension.js
|
||||||
|
const updateSubMenuAdjustment = function(itemActor) {
|
||||||
|
let scrollView = itemActor.get_parent().get_parent();
|
||||||
|
let adjustment = scrollView.get_vscroll_bar().get_adjustment();
|
||||||
|
let scrollViewAlloc = scrollView.get_allocation_box();
|
||||||
|
let currentScrollValue = adjustment.get_value();
|
||||||
|
let height = scrollViewAlloc.y2 - scrollViewAlloc.y1;
|
||||||
|
let itemActorAlloc = itemActor.get_allocation_box();
|
||||||
|
let newScrollValue = currentScrollValue;
|
||||||
|
if (currentScrollValue > itemActorAlloc.y1 - 10)
|
||||||
|
newScrollValue = itemActorAlloc.y1 - 10;
|
||||||
|
if (height + currentScrollValue < itemActorAlloc.y2 + 10)
|
||||||
|
newScrollValue = itemActorAlloc.y2 - height + 10;
|
||||||
|
if (newScrollValue != currentScrollValue)
|
||||||
|
adjustment.set_value(newScrollValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
// based on searchItem.js, https://github.com/leonardo-bartoli/gnome-shell-extension-Recents
|
||||||
|
const 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,
|
||||||
|
x_expand: 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_child(this.entry);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -17,5 +17,5 @@
|
||||||
"3.34",
|
"3.34",
|
||||||
"3.36"
|
"3.36"
|
||||||
],
|
],
|
||||||
"version": 6.1
|
"version": 6.2
|
||||||
}
|
}
|
||||||
|
|
|
||||||
53
prefs.js
53
prefs.js
|
|
@ -54,25 +54,28 @@ var INTERNAL_KEYBINDINGS = {
|
||||||
'select-polygon-shape': "Select polygon",
|
'select-polygon-shape': "Select polygon",
|
||||||
'select-polyline-shape': "Select polyline",
|
'select-polyline-shape': "Select polyline",
|
||||||
'select-text-shape': "Select text",
|
'select-text-shape': "Select text",
|
||||||
|
'select-image-shape': "Select image",
|
||||||
'select-move-tool': "Select move",
|
'select-move-tool': "Select move",
|
||||||
'select-resize-tool': "Select resize",
|
'select-resize-tool': "Select resize",
|
||||||
'select-mirror-tool': "Select mirror",
|
'select-mirror-tool': "Select mirror",
|
||||||
'-separator-2': '',
|
'-separator-2': '',
|
||||||
'toggle-fill': "Toggle fill/outline",
|
'switch-fill': "Toggle fill/outline",
|
||||||
'toggle-fill-rule': "Toggle fill rule",
|
'switch-fill-rule': "Toggle fill rule",
|
||||||
'-separator-3': '',
|
'-separator-3': '',
|
||||||
'increment-line-width': "Increment line width",
|
'increment-line-width': "Increment line width",
|
||||||
'decrement-line-width': "Decrement line width",
|
'decrement-line-width': "Decrement line width",
|
||||||
'increment-line-width-more': "Increment line width even more",
|
'increment-line-width-more': "Increment line width even more",
|
||||||
'decrement-line-width-more': "Decrement line width even more",
|
'decrement-line-width-more': "Decrement line width even more",
|
||||||
'toggle-linejoin': "Change linejoin",
|
'switch-linejoin': "Change linejoin",
|
||||||
'toggle-linecap': "Change linecap",
|
'switch-linecap': "Change linecap",
|
||||||
'toggle-dash': "Dashed line",
|
'switch-dash': "Dashed line",
|
||||||
'-separator-4': '',
|
'-separator-4': '',
|
||||||
'toggle-font-family': "Change font family (generic name)",
|
'switch-font-family': "Change font family",
|
||||||
'toggle-font-weight': "Change font weight",
|
'reverse-switch-font-family': "Change font family (reverse)",
|
||||||
'toggle-font-style': "Change font style",
|
'switch-font-weight': "Change font weight",
|
||||||
'toggle-text-alignment': "Toggle text alignment",
|
'switch-font-style': "Change font style",
|
||||||
|
'switch-text-alignment': "Toggle text alignment",
|
||||||
|
'switch-image-file': "Change image file",
|
||||||
'-separator-5': '',
|
'-separator-5': '',
|
||||||
'toggle-panel-and-dock-visibility': "Hide panel and dock",
|
'toggle-panel-and-dock-visibility': "Hide panel and dock",
|
||||||
'toggle-background': "Add a drawing background",
|
'toggle-background': "Add a drawing background",
|
||||||
|
|
@ -392,26 +395,24 @@ const KeybindingsWidget = new GObject.Class({
|
||||||
accel_mode: Gtk.CellRendererAccelMode.GTK,
|
accel_mode: Gtk.CellRendererAccelMode.GTK,
|
||||||
xalign: 1
|
xalign: 1
|
||||||
});
|
});
|
||||||
keybinding_renderer.connect('accel-edited',
|
keybinding_renderer.connect('accel-edited', (renderer, iter, key, mods) => {
|
||||||
Lang.bind(this, function(renderer, iter, key, mods) {
|
let value = Gtk.accelerator_name(key, mods);
|
||||||
let value = Gtk.accelerator_name(key, mods);
|
let [success, iterator ] =
|
||||||
let [success, iterator ] =
|
this._store.get_iter_from_string(iter);
|
||||||
this._store.get_iter_from_string(iter);
|
|
||||||
|
|
||||||
if(!success) {
|
if(!success) {
|
||||||
printerr("Can't change keybinding");
|
printerr("Can't change keybinding");
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = this._store.get_value(iterator, 0);
|
let name = this._store.get_value(iterator, 0);
|
||||||
|
|
||||||
this._store.set(
|
this._store.set(
|
||||||
iterator,
|
iterator,
|
||||||
[this._columns.MODS, this._columns.KEY],
|
[this._columns.MODS, this._columns.KEY],
|
||||||
[mods, key]
|
[mods, key]
|
||||||
);
|
);
|
||||||
this._settings.set_strv(name, [value]);
|
this._settings.set_strv(name, [value]);
|
||||||
})
|
});
|
||||||
);
|
|
||||||
|
|
||||||
let keybinding_column = new Gtk.TreeViewColumn({
|
let keybinding_column = new Gtk.TreeViewColumn({
|
||||||
title: "",
|
title: "",
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -106,6 +106,11 @@
|
||||||
<summary>select text</summary>
|
<summary>select text</summary>
|
||||||
<description>select text</description>
|
<description>select text</description>
|
||||||
</key>
|
</key>
|
||||||
|
<key type="as" name="select-image-shape">
|
||||||
|
<default>["<Primary>i"]</default>
|
||||||
|
<summary>select image</summary>
|
||||||
|
<description>select image</description>
|
||||||
|
</key>
|
||||||
<key type="as" name="select-none-shape">
|
<key type="as" name="select-none-shape">
|
||||||
<default>["<Primary>p"]</default>
|
<default>["<Primary>p"]</default>
|
||||||
<summary>unselect shape (free drawing)</summary>
|
<summary>unselect shape (free drawing)</summary>
|
||||||
|
|
@ -146,30 +151,30 @@
|
||||||
<summary>decrement the line width even more</summary>
|
<summary>decrement the line width even more</summary>
|
||||||
<description>decrement the line width even more</description>
|
<description>decrement the line width even more</description>
|
||||||
</key>
|
</key>
|
||||||
<key type="as" name="toggle-linejoin">
|
<key type="as" name="switch-linejoin">
|
||||||
<default>["<Primary>j"]</default>
|
<default>["<Primary>j"]</default>
|
||||||
<summary>toggle linejoin</summary>
|
<summary>switch linejoin</summary>
|
||||||
<description>toggle linejoin</description>
|
<description>switch linejoin</description>
|
||||||
</key>
|
</key>
|
||||||
<key type="as" name="toggle-linecap">
|
<key type="as" name="switch-linecap">
|
||||||
<default>["<Primary>k"]</default>
|
<default>["<Primary>k"]</default>
|
||||||
<summary>toggle linecap</summary>
|
<summary>switch linecap</summary>
|
||||||
<description>toggle linecap</description>
|
<description>switch linecap</description>
|
||||||
</key>
|
</key>
|
||||||
<key type="as" name="toggle-fill-rule">
|
<key type="as" name="switch-fill-rule">
|
||||||
<default><![CDATA[['<Primary>KP_Multiply','<Primary>asterisk','<Primary><Shift>asterisk']]]></default>
|
<default><![CDATA[['<Primary>KP_Multiply','<Primary>asterisk','<Primary><Shift>asterisk']]]></default>
|
||||||
<summary>toggle fill rule</summary>
|
<summary>switch fill rule</summary>
|
||||||
<description>toggle fill rule</description>
|
<description>switch fill rule</description>
|
||||||
</key>
|
</key>
|
||||||
<key type="as" name="toggle-dash">
|
<key type="as" name="switch-dash">
|
||||||
<default>["<Primary>period"]</default>
|
<default>["<Primary>period"]</default>
|
||||||
<summary>toggle dash</summary>
|
<summary>switch dash</summary>
|
||||||
<description>toggle dash</description>
|
<description>switch dash</description>
|
||||||
</key>
|
</key>
|
||||||
<key type="as" name="toggle-fill">
|
<key type="as" name="switch-fill">
|
||||||
<default>["<Primary>a"]</default>
|
<default>["<Primary>a"]</default>
|
||||||
<summary>toggle fill</summary>
|
<summary>switch fill</summary>
|
||||||
<description>toggle fill</description>
|
<description>switch fill</description>
|
||||||
</key>
|
</key>
|
||||||
<key type="as" name="select-color1">
|
<key type="as" name="select-color1">
|
||||||
<default><![CDATA[['<Primary>KP_1','<Primary>1']]]></default>
|
<default><![CDATA[['<Primary>KP_1','<Primary>1']]]></default>
|
||||||
|
|
@ -216,25 +221,35 @@
|
||||||
<summary>select color9</summary>
|
<summary>select color9</summary>
|
||||||
<description>select color9</description>
|
<description>select color9</description>
|
||||||
</key>
|
</key>
|
||||||
<key type="as" name="toggle-font-family">
|
<key type="as" name="switch-font-family">
|
||||||
<default>["<Primary>f"]</default>
|
<default>["<Primary>f"]</default>
|
||||||
<summary>toggle font family</summary>
|
<summary>switch font family</summary>
|
||||||
<description>toggle font family</description>
|
<description>switch font family</description>
|
||||||
</key>
|
</key>
|
||||||
<key type="as" name="toggle-font-weight">
|
<key type="as" name="reverse-switch-font-family">
|
||||||
|
<default>["<Primary><Shift>f"]</default>
|
||||||
|
<summary>switch font family (reverse)</summary>
|
||||||
|
<description>switch font family (reverse)</description>
|
||||||
|
</key>
|
||||||
|
<key type="as" name="switch-font-weight">
|
||||||
<default>["<Primary>w"]</default>
|
<default>["<Primary>w"]</default>
|
||||||
<summary>toggle font weight</summary>
|
<summary>switch font weight</summary>
|
||||||
<description>toggle font weight</description>
|
<description>switch font weight</description>
|
||||||
</key>
|
</key>
|
||||||
<key type="as" name="toggle-font-style">
|
<key type="as" name="switch-font-style">
|
||||||
<default>["<Primary>i"]</default>
|
<default>["<Primary><Shift>w"]</default>
|
||||||
<summary>toggle font style</summary>
|
<summary>switch font style</summary>
|
||||||
<description>toggle font style</description>
|
<description>switch font style</description>
|
||||||
</key>
|
</key>
|
||||||
<key type="as" name="toggle-text-alignment">
|
<key type="as" name="switch-text-alignment">
|
||||||
<default>["<Primary><Shift>a"]</default>
|
<default>["<Primary><Shift>a"]</default>
|
||||||
<summary>toggle text alignment</summary>
|
<summary>switch text alignment</summary>
|
||||||
<description>toggle text alignment</description>
|
<description>switch text alignment</description>
|
||||||
|
</key>
|
||||||
|
<key type="as" name="switch-image-file">
|
||||||
|
<default>["<Primary><Shift>i"]</default>
|
||||||
|
<summary>switch image file</summary>
|
||||||
|
<description>switch image file</description>
|
||||||
</key>
|
</key>
|
||||||
<key type="as" name="open-user-stylesheet">
|
<key type="as" name="open-user-stylesheet">
|
||||||
<default>["<Primary>o"]</default>
|
<default>["<Primary>o"]</default>
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.draw-on-your-screen-menu-separator-item {
|
.draw-on-your-screen-menu-separator-item {
|
||||||
|
margin-top: 0;
|
||||||
padding-top: 0.14em;
|
padding-top: 0.14em;
|
||||||
padding-bottom: 0.14em;
|
padding-bottom: 0.14em;
|
||||||
}
|
}
|
||||||
|
|
@ -71,6 +72,33 @@
|
||||||
margin-bottom: 0.2em; /* default 6px */
|
margin-bottom: 0.2em; /* default 6px */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.draw-on-your-screen-menu-separator-item .popup-separator-menu-item-separator {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draw-on-your-screen-menu-thin-separator-item .popup-separator-menu-item-separator {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* system-menu-action: from GS 3.34- */
|
||||||
|
.draw-on-your-screen-menu .system-menu-action {
|
||||||
|
min-width: 0;
|
||||||
|
border: none;
|
||||||
|
border-radius: 32px;
|
||||||
|
padding: 12px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draw-on-your-screen-menu .system-menu-action:hover,
|
||||||
|
.draw-on-your-screen-menu .system-menu-action:focus {
|
||||||
|
border: none;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draw-on-your-screen-menu .system-menu-action > StIcon {
|
||||||
|
icon-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.draw-on-your-screen-menu-slider-label {
|
.draw-on-your-screen-menu-slider-label {
|
||||||
min-width: 3em;
|
min-width: 3em;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue