ENH: Open Prinables.com Links and Zip Archives (#3823)
* Enable ability to open `prusaslicer://` links * Add needed function to miniz * Import Zip Functionality Allows zip file to be drag and dropped or imported via the menu option Based on prusa3d/PrusaSlicer@ce38e57 and current master branch files * Update dialog style to match Orca * Ensure link is from printables * add toggle option in preferences doesn't actually control anything yet * Add Downloader classes As-is from PS master * Create Orca Styled Variant of Icons * Add Icons to ImGui * Use PS's Downloader impl for `prusaslicer://` links * Implement URL Registering on Windows * Implement prusaslicer:// link on macOS * Remove unnecessary class name qualifier in Plater.hpp * Add downloader desktop integration registration and undo * Revert Info.plist --------- Co-authored-by: SoftFever <softfeverever@gmail.com>
40
resources/images/notification_open.svg
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.0"
|
||||
id="close_window"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 100 100"
|
||||
enable-background="new 0 0 100 100"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
||||
id="metadata19"><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><defs
|
||||
id="defs17" />
|
||||
|
||||
|
||||
|
||||
<g
|
||||
id="open"
|
||||
transform="matrix(4.05,0,0,4.05,15.4645,15.277381)"
|
||||
style="fill:#323a3e;fill-opacity:1"><circle
|
||||
style="fill:#eeeeee;fill-opacity:1;stroke-width:0.969441"
|
||||
id="path1"
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="40.299999"
|
||||
transform="matrix(0.24691358,0,0,0.24691358,-3.8183951,-3.7721928)" /><g
|
||||
id="folder"><path
|
||||
id="path2-7"
|
||||
d="M 1.9606173,14 V 3 c 0,0 0,-1 1,-1 0.9999999,0 3.9999998,0 4.9999998,0 1,0 1,2 1.9999999,2 0.99999,0 3.99999,0 3.99999,0 0,0 1,0 1,1 v 2 h -1 c 0,0 0,0 0,-1 0,-1 -1,-1 -1,-1 H 9.460617 C 8.4606171,5 8.4606171,3 7.4606171,3 c -1,0 -3.4999999,0 -3.4999999,0 C 2.9606173,3 2.9606173,4 2.9606173,4 v 9 1 h 0.9999999 v 1 c 0,0 0,0 -0.9999999,0 -1,0 -1,-1 -1,-1 z"
|
||||
fill="#808080"
|
||||
style="fill:#acacac;fill-opacity:1" /><path
|
||||
id="path4"
|
||||
d="m 5.7406172,6 c -0.55,0 -1.14,0.43 -1.32,0.95 l -2.3699999,7.1 c -0.17,0.52 0.14,0.95 0.69,0.95 h 9.9999897 c 0.55,0 1.14,-0.43 1.32,-0.95 l 2.37,-7.1 c 0.17,-0.52 -0.14,-0.95 -0.69,-0.95 z"
|
||||
fill="#323a3e"
|
||||
style="fill:#acacac;fill-opacity:1" /></g></g></svg>
|
After Width: | Height: | Size: 1.7 KiB |
40
resources/images/notification_open_dark.svg
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.0"
|
||||
id="close_window"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 100 100"
|
||||
enable-background="new 0 0 100 100"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
||||
id="metadata19"><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><defs
|
||||
id="defs17" />
|
||||
|
||||
|
||||
|
||||
<g
|
||||
id="open"
|
||||
transform="matrix(4.05,0,0,4.05,15.4645,15.277381)"
|
||||
style="fill:#323a3e;fill-opacity:1"><circle
|
||||
style="fill:#3e3e45;fill-opacity:1;stroke-width:0.969441"
|
||||
id="path1"
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="40.299999"
|
||||
transform="matrix(0.24691358,0,0,0.24691358,-3.8183951,-3.7721928)" /><g
|
||||
id="folder"><path
|
||||
id="path2-7"
|
||||
d="M 1.9606173,14 V 3 c 0,0 0,-1 1,-1 0.9999999,0 3.9999998,0 4.9999998,0 1,0 1,2 1.9999999,2 0.99999,0 3.99999,0 3.99999,0 0,0 1,0 1,1 v 2 h -1 c 0,0 0,0 0,-1 0,-1 -1,-1 -1,-1 H 9.460617 C 8.4606171,5 8.4606171,3 7.4606171,3 c -1,0 -3.4999999,0 -3.4999999,0 C 2.9606173,3 2.9606173,4 2.9606173,4 v 9 1 h 0.9999999 v 1 c 0,0 0,0 -0.9999999,0 -1,0 -1,-1 -1,-1 z"
|
||||
fill="#808080"
|
||||
style="fill:#818183;fill-opacity:1" /><path
|
||||
id="path4"
|
||||
d="m 5.7406172,6 c -0.55,0 -1.14,0.43 -1.32,0.95 l -2.3699999,7.1 c -0.17,0.52 0.14,0.95 0.69,0.95 h 9.9999897 c 0.55,0 1.14,-0.43 1.32,-0.95 l 2.37,-7.1 c 0.17,-0.52 -0.14,-0.95 -0.69,-0.95 z"
|
||||
fill="#323a3e"
|
||||
style="fill:#818183;fill-opacity:1" /></g></g></svg>
|
After Width: | Height: | Size: 1.7 KiB |
40
resources/images/notification_open_hover.svg
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.0"
|
||||
id="close_window"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 100 100"
|
||||
enable-background="new 0 0 100 100"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
||||
id="metadata19"><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><defs
|
||||
id="defs17" />
|
||||
|
||||
|
||||
|
||||
<g
|
||||
id="open"
|
||||
transform="matrix(4.7936726,0,0,4.7936726,9.1229931,8.9015147)"
|
||||
style="fill:#323a3e;fill-opacity:1"><circle
|
||||
style="fill:#eeeeee;fill-opacity:1;stroke-width:0.969441"
|
||||
id="path1"
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="40.299999"
|
||||
transform="matrix(0.24691358,0,0,0.24691358,-3.8183951,-3.7721928)" /><g
|
||||
id="folder"><path
|
||||
id="path2-7"
|
||||
d="M 1.9606173,14 V 3 c 0,0 0,-1 1,-1 0.9999999,0 3.9999998,0 4.9999998,0 1,0 1,2 1.9999999,2 0.99999,0 3.99999,0 3.99999,0 0,0 1,0 1,1 v 2 h -1 c 0,0 0,0 0,-1 0,-1 -1,-1 -1,-1 H 9.460617 C 8.4606171,5 8.4606171,3 7.4606171,3 c -1,0 -3.4999999,0 -3.4999999,0 C 2.9606173,3 2.9606173,4 2.9606173,4 v 9 1 h 0.9999999 v 1 c 0,0 0,0 -0.9999999,0 -1,0 -1,-1 -1,-1 z"
|
||||
fill="#808080"
|
||||
style="fill:#acacac;fill-opacity:1" /><path
|
||||
id="path4"
|
||||
d="m 5.7406172,6 c -0.55,0 -1.14,0.43 -1.32,0.95 l -2.3699999,7.1 c -0.17,0.52 0.14,0.95 0.69,0.95 h 9.9999897 c 0.55,0 1.14,-0.43 1.32,-0.95 l 2.37,-7.1 c 0.17,-0.52 -0.14,-0.95 -0.69,-0.95 z"
|
||||
fill="#323a3e"
|
||||
style="fill:#acacac;fill-opacity:1" /></g></g></svg>
|
After Width: | Height: | Size: 1.8 KiB |
40
resources/images/notification_open_hover_dark.svg
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.0"
|
||||
id="close_window"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 100 100"
|
||||
enable-background="new 0 0 100 100"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
||||
id="metadata19"><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><defs
|
||||
id="defs17" />
|
||||
|
||||
|
||||
|
||||
<g
|
||||
id="open"
|
||||
transform="matrix(4.7936726,0,0,4.7936726,9.1229931,8.9015147)"
|
||||
style="fill:#323a3e;fill-opacity:1"><circle
|
||||
style="fill:#3e3e45;fill-opacity:1;stroke-width:0.969441"
|
||||
id="path1"
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="40.299999"
|
||||
transform="matrix(0.24691358,0,0,0.24691358,-3.8183951,-3.7721928)" /><g
|
||||
id="folder"><path
|
||||
id="path2-7"
|
||||
d="M 1.9606173,14 V 3 c 0,0 0,-1 1,-1 0.9999999,0 3.9999998,0 4.9999998,0 1,0 1,2 1.9999999,2 0.99999,0 3.99999,0 3.99999,0 0,0 1,0 1,1 v 2 h -1 c 0,0 0,0 0,-1 0,-1 -1,-1 -1,-1 H 9.460617 C 8.4606171,5 8.4606171,3 7.4606171,3 c -1,0 -3.4999999,0 -3.4999999,0 C 2.9606173,3 2.9606173,4 2.9606173,4 v 9 1 h 0.9999999 v 1 c 0,0 0,0 -0.9999999,0 -1,0 -1,-1 -1,-1 z"
|
||||
fill="#808080"
|
||||
style="fill:#818183;fill-opacity:1" /><path
|
||||
id="path4"
|
||||
d="m 5.7406172,6 c -0.55,0 -1.14,0.43 -1.32,0.95 l -2.3699999,7.1 c -0.17,0.52 0.14,0.95 0.69,0.95 h 9.9999897 c 0.55,0 1.14,-0.43 1.32,-0.95 l 2.37,-7.1 c 0.17,-0.52 -0.14,-0.95 -0.69,-0.95 z"
|
||||
fill="#323a3e"
|
||||
style="fill:#818183;fill-opacity:1" /></g></g></svg>
|
After Width: | Height: | Size: 1.8 KiB |
46
resources/images/notification_pause.svg
Normal file
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xml:space="preserve"
|
||||
viewBox="0 0 800 800"
|
||||
y="0px"
|
||||
x="0px"
|
||||
id="Layer_1"
|
||||
version="1.1"
|
||||
style="enable-background:new 0 0 800 800;"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
||||
id="metadata15"><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><defs
|
||||
id="defs13" />
|
||||
<style
|
||||
id="style2"
|
||||
type="text/css">
|
||||
.st0{fill:#323a3e;}
|
||||
</style>
|
||||
<circle
|
||||
style="fill:#eeeeee;fill-opacity:1;stroke-width:7.75552"
|
||||
id="path1"
|
||||
cx="400"
|
||||
cy="400"
|
||||
r="322.39999" /><path
|
||||
id="path4"
|
||||
d="m 317.95173,562.36128 h -60.775 v -330.565 h 60.775 v 330.565 m 48.195,27.88 v -386.24 c 0,-11.22 -9.095,-20.315 -20.315,-20.315 h -116.535 c -11.22,0 -20.315,9.095 -20.315,20.315 v 386.24 c 0,11.22 9.095,20.315 20.315,20.315 h 116.45 c 11.22,0 20.4,-9.095 20.4,-20.315 z"
|
||||
class="st0"
|
||||
style="stroke-width:0.85;fill:#acacac;fill-opacity:1" />
|
||||
|
||||
|
||||
<path
|
||||
id="path21"
|
||||
d="m 210.06357,199.0892 c 1.10499,-4.08272 3.30912,-7.53117 6.63124,-10.37477 5.42019,-4.63948 2.10678,-4.43387 71.33297,-4.42657 l 62.44927,0.007 3.44194,1.60268 c 4.05635,1.88877 7.75734,5.3977 9.8769,9.36436 l 1.52243,2.84915 v 199.00794 199.00793 l -1.52243,2.84915 c -2.11956,3.96665 -5.82055,7.47559 -9.8769,9.36436 l -3.44194,1.60267 -62.44927,0.007 c -69.78764,0.008 -65.98231,0.26152 -71.72146,-4.79028 -1.69319,-1.4904 -3.87229,-4.37627 -4.9283,-6.52672 l -1.89304,-3.85513 -0.12602,-196.30953 c -0.0987,-153.67069 0.0544,-196.97613 0.70461,-199.37859 z m 77.67539,363.64446 H 318.4334 V 397.11858 231.5035 H 287.73896 257.0445 v 165.61508 165.61508 z"
|
||||
style="fill:#acacac;fill-opacity:1;stroke-width:0.674603" /><path
|
||||
style="fill:#acacac;stroke-width:0.85;fill-opacity:1"
|
||||
class="st0"
|
||||
d="m 533.78507,563.71023 h -60.775 v -330.565 h 60.775 v 330.565 m 48.195,27.88 v -386.24 c 0,-11.22 -9.095,-20.315 -20.315,-20.315 h -116.535 c -11.22,0 -20.315,9.095 -20.315,20.315 v 386.24 c 0,11.22 9.095,20.315 20.315,20.315 h 116.45 c 11.22,0 20.4,-9.095 20.4,-20.315 z"
|
||||
id="path4-3" /><path
|
||||
style="fill:#acacac;fill-opacity:1;stroke-width:0.674603"
|
||||
d="m 425.89691,200.43815 c 1.10499,-4.08272 3.30912,-7.53117 6.63124,-10.37477 5.42019,-4.63948 2.10678,-4.43387 71.33297,-4.42657 l 62.44927,0.007 3.44194,1.60268 c 4.05635,1.88877 7.75734,5.3977 9.8769,9.36436 l 1.52243,2.84915 v 199.00794 199.00793 l -1.52243,2.84915 c -2.11956,3.96665 -5.82055,7.47559 -9.8769,9.36436 l -3.44194,1.60267 -62.44927,0.007 c -69.78764,0.008 -65.98231,0.26152 -71.72146,-4.79028 -1.69319,-1.4904 -3.87229,-4.37627 -4.9283,-6.52672 l -1.89304,-3.85513 -0.12602,-196.30953 c -0.0987,-153.67069 0.0544,-196.97613 0.70461,-199.37859 z m 77.67539,363.64446 h 30.69444 V 398.46753 232.85245 H 503.5723 472.87784 v 165.61508 165.61508 z"
|
||||
id="path21-9" /></svg>
|
After Width: | Height: | Size: 3.1 KiB |
46
resources/images/notification_pause_dark.svg
Normal file
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xml:space="preserve"
|
||||
viewBox="0 0 800 800"
|
||||
y="0px"
|
||||
x="0px"
|
||||
id="Layer_1"
|
||||
version="1.1"
|
||||
style="enable-background:new 0 0 800 800;"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
||||
id="metadata15"><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><defs
|
||||
id="defs13" />
|
||||
<style
|
||||
id="style2"
|
||||
type="text/css">
|
||||
.st0{fill:#323a3e;}
|
||||
</style>
|
||||
<circle
|
||||
style="fill:#3e3e45;fill-opacity:1;stroke-width:7.75552"
|
||||
id="path1"
|
||||
cx="400"
|
||||
cy="400"
|
||||
r="322.39999" /><path
|
||||
id="path4"
|
||||
d="m 317.95173,562.36128 h -60.775 v -330.565 h 60.775 v 330.565 m 48.195,27.88 v -386.24 c 0,-11.22 -9.095,-20.315 -20.315,-20.315 h -116.535 c -11.22,0 -20.315,9.095 -20.315,20.315 v 386.24 c 0,11.22 9.095,20.315 20.315,20.315 h 116.45 c 11.22,0 20.4,-9.095 20.4,-20.315 z"
|
||||
class="st0"
|
||||
style="stroke-width:0.85;fill:#818183;fill-opacity:1" />
|
||||
|
||||
|
||||
<path
|
||||
id="path21"
|
||||
d="m 210.06357,199.0892 c 1.10499,-4.08272 3.30912,-7.53117 6.63124,-10.37477 5.42019,-4.63948 2.10678,-4.43387 71.33297,-4.42657 l 62.44927,0.007 3.44194,1.60268 c 4.05635,1.88877 7.75734,5.3977 9.8769,9.36436 l 1.52243,2.84915 v 199.00794 199.00793 l -1.52243,2.84915 c -2.11956,3.96665 -5.82055,7.47559 -9.8769,9.36436 l -3.44194,1.60267 -62.44927,0.007 c -69.78764,0.008 -65.98231,0.26152 -71.72146,-4.79028 -1.69319,-1.4904 -3.87229,-4.37627 -4.9283,-6.52672 l -1.89304,-3.85513 -0.12602,-196.30953 c -0.0987,-153.67069 0.0544,-196.97613 0.70461,-199.37859 z m 77.67539,363.64446 H 318.4334 V 397.11858 231.5035 H 287.73896 257.0445 v 165.61508 165.61508 z"
|
||||
style="fill:#818183;fill-opacity:1;stroke-width:0.674603" /><path
|
||||
style="fill:#818183;stroke-width:0.85;fill-opacity:1"
|
||||
class="st0"
|
||||
d="m 533.78507,563.71023 h -60.775 v -330.565 h 60.775 v 330.565 m 48.195,27.88 v -386.24 c 0,-11.22 -9.095,-20.315 -20.315,-20.315 h -116.535 c -11.22,0 -20.315,9.095 -20.315,20.315 v 386.24 c 0,11.22 9.095,20.315 20.315,20.315 h 116.45 c 11.22,0 20.4,-9.095 20.4,-20.315 z"
|
||||
id="path4-3" /><path
|
||||
style="fill:#818183;fill-opacity:1;stroke-width:0.674603"
|
||||
d="m 425.89691,200.43815 c 1.10499,-4.08272 3.30912,-7.53117 6.63124,-10.37477 5.42019,-4.63948 2.10678,-4.43387 71.33297,-4.42657 l 62.44927,0.007 3.44194,1.60268 c 4.05635,1.88877 7.75734,5.3977 9.8769,9.36436 l 1.52243,2.84915 v 199.00794 199.00793 l -1.52243,2.84915 c -2.11956,3.96665 -5.82055,7.47559 -9.8769,9.36436 l -3.44194,1.60267 -62.44927,0.007 c -69.78764,0.008 -65.98231,0.26152 -71.72146,-4.79028 -1.69319,-1.4904 -3.87229,-4.37627 -4.9283,-6.52672 l -1.89304,-3.85513 -0.12602,-196.30953 c -0.0987,-153.67069 0.0544,-196.97613 0.70461,-199.37859 z m 77.67539,363.64446 h 30.69444 V 398.46753 232.85245 H 503.5723 472.87784 v 165.61508 165.61508 z"
|
||||
id="path21-9" /></svg>
|
After Width: | Height: | Size: 3.1 KiB |
48
resources/images/notification_pause_hover.svg
Normal file
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xml:space="preserve"
|
||||
viewBox="0 0 800 800"
|
||||
y="0px"
|
||||
x="0px"
|
||||
id="Layer_1"
|
||||
version="1.1"
|
||||
style="enable-background:new 0 0 800 800;"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
||||
id="metadata15"><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><defs
|
||||
id="defs13" />
|
||||
<style
|
||||
id="style2"
|
||||
type="text/css">
|
||||
.st0{fill:#323a3e;}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
<g
|
||||
id="g1"
|
||||
transform="matrix(1.1836229,0,0,1.1836229,-73.44914,-73.44914)"><circle
|
||||
style="fill:#eeeeee;fill-opacity:1;stroke-width:7.75552"
|
||||
id="path1"
|
||||
cx="400"
|
||||
cy="400"
|
||||
r="322.39999" /><path
|
||||
id="path4"
|
||||
d="m 317.95173,562.36128 h -60.775 v -330.565 h 60.775 v 330.565 m 48.195,27.88 v -386.24 c 0,-11.22 -9.095,-20.315 -20.315,-20.315 h -116.535 c -11.22,0 -20.315,9.095 -20.315,20.315 v 386.24 c 0,11.22 9.095,20.315 20.315,20.315 h 116.45 c 11.22,0 20.4,-9.095 20.4,-20.315 z"
|
||||
class="st0"
|
||||
style="fill:#acacac;fill-opacity:1;stroke-width:0.85" /><path
|
||||
id="path21"
|
||||
d="m 210.06357,199.0892 c 1.10499,-4.08272 3.30912,-7.53117 6.63124,-10.37477 5.42019,-4.63948 2.10678,-4.43387 71.33297,-4.42657 l 62.44927,0.007 3.44194,1.60268 c 4.05635,1.88877 7.75734,5.3977 9.8769,9.36436 l 1.52243,2.84915 v 199.00794 199.00793 l -1.52243,2.84915 c -2.11956,3.96665 -5.82055,7.47559 -9.8769,9.36436 l -3.44194,1.60267 -62.44927,0.007 c -69.78764,0.008 -65.98231,0.26152 -71.72146,-4.79028 -1.69319,-1.4904 -3.87229,-4.37627 -4.9283,-6.52672 l -1.89304,-3.85513 -0.12602,-196.30953 c -0.0987,-153.67069 0.0544,-196.97613 0.70461,-199.37859 z m 77.67539,363.64446 H 318.4334 V 397.11858 231.5035 H 287.73896 257.0445 v 165.61508 165.61508 z"
|
||||
style="fill:#acacac;fill-opacity:1;stroke-width:0.674603" /><path
|
||||
style="fill:#acacac;fill-opacity:1;stroke-width:0.85"
|
||||
class="st0"
|
||||
d="m 533.78507,563.71023 h -60.775 v -330.565 h 60.775 v 330.565 m 48.195,27.88 v -386.24 c 0,-11.22 -9.095,-20.315 -20.315,-20.315 h -116.535 c -11.22,0 -20.315,9.095 -20.315,20.315 v 386.24 c 0,11.22 9.095,20.315 20.315,20.315 h 116.45 c 11.22,0 20.4,-9.095 20.4,-20.315 z"
|
||||
id="path4-3" /><path
|
||||
style="fill:#acacac;fill-opacity:1;stroke-width:0.674603"
|
||||
d="m 425.89691,200.43815 c 1.10499,-4.08272 3.30912,-7.53117 6.63124,-10.37477 5.42019,-4.63948 2.10678,-4.43387 71.33297,-4.42657 l 62.44927,0.007 3.44194,1.60268 c 4.05635,1.88877 7.75734,5.3977 9.8769,9.36436 l 1.52243,2.84915 v 199.00794 199.00793 l -1.52243,2.84915 c -2.11956,3.96665 -5.82055,7.47559 -9.8769,9.36436 l -3.44194,1.60267 -62.44927,0.007 c -69.78764,0.008 -65.98231,0.26152 -71.72146,-4.79028 -1.69319,-1.4904 -3.87229,-4.37627 -4.9283,-6.52672 l -1.89304,-3.85513 -0.12602,-196.30953 c -0.0987,-153.67069 0.0544,-196.97613 0.70461,-199.37859 z m 77.67539,363.64446 h 30.69444 V 398.46753 232.85245 H 503.5723 472.87784 v 165.61508 165.61508 z"
|
||||
id="path21-9" /></g></svg>
|
After Width: | Height: | Size: 3.2 KiB |
48
resources/images/notification_pause_hover_dark.svg
Normal file
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xml:space="preserve"
|
||||
viewBox="0 0 800 800"
|
||||
y="0px"
|
||||
x="0px"
|
||||
id="Layer_1"
|
||||
version="1.1"
|
||||
style="enable-background:new 0 0 800 800;"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
||||
id="metadata15"><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><defs
|
||||
id="defs13" />
|
||||
<style
|
||||
id="style2"
|
||||
type="text/css">
|
||||
.st0{fill:#323a3e;}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
<g
|
||||
id="g1"
|
||||
transform="matrix(1.1836229,0,0,1.1836229,-73.44914,-73.44914)"><circle
|
||||
style="fill:#3e3e45;fill-opacity:1;stroke-width:7.75552"
|
||||
id="path1"
|
||||
cx="400"
|
||||
cy="400"
|
||||
r="322.39999" /><path
|
||||
id="path4"
|
||||
d="m 317.95173,562.36128 h -60.775 v -330.565 h 60.775 v 330.565 m 48.195,27.88 v -386.24 c 0,-11.22 -9.095,-20.315 -20.315,-20.315 h -116.535 c -11.22,0 -20.315,9.095 -20.315,20.315 v 386.24 c 0,11.22 9.095,20.315 20.315,20.315 h 116.45 c 11.22,0 20.4,-9.095 20.4,-20.315 z"
|
||||
class="st0"
|
||||
style="fill:#818183;fill-opacity:1;stroke-width:0.85" /><path
|
||||
id="path21"
|
||||
d="m 210.06357,199.0892 c 1.10499,-4.08272 3.30912,-7.53117 6.63124,-10.37477 5.42019,-4.63948 2.10678,-4.43387 71.33297,-4.42657 l 62.44927,0.007 3.44194,1.60268 c 4.05635,1.88877 7.75734,5.3977 9.8769,9.36436 l 1.52243,2.84915 v 199.00794 199.00793 l -1.52243,2.84915 c -2.11956,3.96665 -5.82055,7.47559 -9.8769,9.36436 l -3.44194,1.60267 -62.44927,0.007 c -69.78764,0.008 -65.98231,0.26152 -71.72146,-4.79028 -1.69319,-1.4904 -3.87229,-4.37627 -4.9283,-6.52672 l -1.89304,-3.85513 -0.12602,-196.30953 c -0.0987,-153.67069 0.0544,-196.97613 0.70461,-199.37859 z m 77.67539,363.64446 H 318.4334 V 397.11858 231.5035 H 287.73896 257.0445 v 165.61508 165.61508 z"
|
||||
style="fill:#818183;fill-opacity:1;stroke-width:0.674603" /><path
|
||||
style="fill:#818183;fill-opacity:1;stroke-width:0.85"
|
||||
class="st0"
|
||||
d="m 533.78507,563.71023 h -60.775 v -330.565 h 60.775 v 330.565 m 48.195,27.88 v -386.24 c 0,-11.22 -9.095,-20.315 -20.315,-20.315 h -116.535 c -11.22,0 -20.315,9.095 -20.315,20.315 v 386.24 c 0,11.22 9.095,20.315 20.315,20.315 h 116.45 c 11.22,0 20.4,-9.095 20.4,-20.315 z"
|
||||
id="path4-3" /><path
|
||||
style="fill:#818183;fill-opacity:1;stroke-width:0.674603"
|
||||
d="m 425.89691,200.43815 c 1.10499,-4.08272 3.30912,-7.53117 6.63124,-10.37477 5.42019,-4.63948 2.10678,-4.43387 71.33297,-4.42657 l 62.44927,0.007 3.44194,1.60268 c 4.05635,1.88877 7.75734,5.3977 9.8769,9.36436 l 1.52243,2.84915 v 199.00794 199.00793 l -1.52243,2.84915 c -2.11956,3.96665 -5.82055,7.47559 -9.8769,9.36436 l -3.44194,1.60267 -62.44927,0.007 c -69.78764,0.008 -65.98231,0.26152 -71.72146,-4.79028 -1.69319,-1.4904 -3.87229,-4.37627 -4.9283,-6.52672 l -1.89304,-3.85513 -0.12602,-196.30953 c -0.0987,-153.67069 0.0544,-196.97613 0.70461,-199.37859 z m 77.67539,363.64446 h 30.69444 V 398.46753 232.85245 H 503.5723 472.87784 v 165.61508 165.61508 z"
|
||||
id="path21-9" /></g></svg>
|
After Width: | Height: | Size: 3.2 KiB |
46
resources/images/notification_play.svg
Normal file
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xml:space="preserve"
|
||||
style="enable-background:new 0 0 800 800;"
|
||||
viewBox="0 0 800 800"
|
||||
y="0px"
|
||||
x="0px"
|
||||
id="Layer_1"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
||||
id="metadata15"><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><defs
|
||||
id="defs13" />
|
||||
<style
|
||||
id="style2"
|
||||
type="text/css">
|
||||
.st0{fill:#323a3e;}
|
||||
</style>
|
||||
|
||||
<circle
|
||||
style="fill:#eeeeee;fill-opacity:1;stroke-width:7.75552"
|
||||
id="path1"
|
||||
cx="400"
|
||||
cy="400"
|
||||
r="322.39999" /><path
|
||||
id="path6"
|
||||
d="M 501.14231,400.1595 345.84732,517.11951 v -233.75 l 155.29499,116.79 m 66.63999,0 c 0,-6.12 -2.72,-12.155 -8.15999,-16.235 L 330.12233,211.2895 c -13.43,-10.115 -32.555,-0.51 -32.555,16.235 v 345.35501 c 0,16.745 19.125,26.35 32.555,16.235 l 229.49998,-172.805 c 5.44,-3.995 8.16,-10.03 8.15999,-16.15 z"
|
||||
class="st0"
|
||||
style="stroke-width:0.85;fill:#acacac;fill-opacity:1" />
|
||||
|
||||
<path
|
||||
id="path19"
|
||||
d="m 300.17824,218.44362 c 2.06443,-4.13128 4.83026,-6.86894 9.40917,-9.3133 3.43647,-1.83451 12.82007,-1.79344 16.52778,0.0724 3.22403,1.6224 232.7236,174.17375 235.79013,177.281 3.35767,3.4022 4.88323,7.0012 5.23218,12.34324 0.27168,4.159 0.0718,5.32914 -1.5138,8.86232 -1.00471,2.23886 -2.78731,4.97342 -3.96129,6.07682 -4.28583,4.02812 -232.27571,175.25818 -235.54572,176.9048 -4.6586,2.34584 -12.1025,2.3876 -16.52928,0.0928 -3.87486,-2.00878 -7.80538,-5.7435 -9.67029,-9.18862 l -1.46069,-2.69842 -0.1736,-178.1462 -0.1736,-178.14618 z m 45.66908,298.67589 c 1.00351,-0.0612 154.99009,-116.48775 154.99009,-117.18534 0,-0.71184 -154.02593,-116.84804 -154.99009,-116.86318 -0.27827,-0.004 -0.50595,52.66168 -0.50595,117.03568 0,64.37401 0.22768,117.0298 0.50595,117.01284 z"
|
||||
style="fill:#acacac;fill-opacity:1;stroke-width:0.6746" /><path
|
||||
style="fill:#acacac;stroke-width:0.85;fill-opacity:1"
|
||||
class="st0"
|
||||
d="M 501.14231,400.15949 345.84732,517.1195 v -233.75 l 155.29499,116.79 m 66.63999,0 c 0,-6.12 -2.72,-12.155 -8.15999,-16.235 L 330.12233,211.28949 c -13.43,-10.115 -32.555,-0.51 -32.555,16.235 V 572.8795 c 0,16.745 19.125,26.35 32.555,16.235 l 229.49998,-172.805 c 5.44,-3.995 8.16,-10.03 8.15999,-16.15 z"
|
||||
id="path6-2" /><path
|
||||
style="fill:#acacac;fill-opacity:1;stroke-width:0.6746"
|
||||
d="m 300.17824,218.44361 c 2.06443,-4.13128 4.83026,-6.86894 9.40917,-9.3133 3.43647,-1.83451 12.82007,-1.79344 16.52778,0.0724 3.22403,1.6224 232.7236,174.17375 235.79013,177.281 3.35767,3.4022 4.88323,7.0012 5.23218,12.34324 0.27168,4.159 0.0718,5.32914 -1.5138,8.86232 -1.00471,2.23886 -2.78731,4.97342 -3.96129,6.07682 -4.28583,4.02812 -232.27571,175.25818 -235.54572,176.9048 -4.6586,2.34584 -12.1025,2.3876 -16.52928,0.0928 -3.87486,-2.00878 -7.80538,-5.7435 -9.67029,-9.18862 l -1.46069,-2.69842 -0.1736,-178.1462 -0.1736,-178.14618 z m 45.66908,298.67589 c 1.00351,-0.0612 154.99009,-116.48775 154.99009,-117.18534 0,-0.71184 -154.02593,-116.84804 -154.99009,-116.86318 -0.27827,-0.004 -0.50595,52.66168 -0.50595,117.03568 0,64.37401 0.22768,117.0298 0.50595,117.01284 z"
|
||||
id="path19-3" /></svg>
|
After Width: | Height: | Size: 3.3 KiB |
46
resources/images/notification_play_dark.svg
Normal file
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xml:space="preserve"
|
||||
style="enable-background:new 0 0 800 800;"
|
||||
viewBox="0 0 800 800"
|
||||
y="0px"
|
||||
x="0px"
|
||||
id="Layer_1"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
||||
id="metadata15"><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><defs
|
||||
id="defs13" />
|
||||
<style
|
||||
id="style2"
|
||||
type="text/css">
|
||||
.st0{fill:#323a3e;}
|
||||
</style>
|
||||
|
||||
<circle
|
||||
style="fill:#3e3e45;fill-opacity:1;stroke-width:7.75552"
|
||||
id="path1"
|
||||
cx="400"
|
||||
cy="400"
|
||||
r="322.39999" /><path
|
||||
id="path6"
|
||||
d="M 501.14231,400.1595 345.84732,517.11951 v -233.75 l 155.29499,116.79 m 66.63999,0 c 0,-6.12 -2.72,-12.155 -8.15999,-16.235 L 330.12233,211.2895 c -13.43,-10.115 -32.555,-0.51 -32.555,16.235 v 345.35501 c 0,16.745 19.125,26.35 32.555,16.235 l 229.49998,-172.805 c 5.44,-3.995 8.16,-10.03 8.15999,-16.15 z"
|
||||
class="st0"
|
||||
style="stroke-width:0.85;fill:#818183;fill-opacity:1" />
|
||||
|
||||
<path
|
||||
id="path19"
|
||||
d="m 300.17824,218.44362 c 2.06443,-4.13128 4.83026,-6.86894 9.40917,-9.3133 3.43647,-1.83451 12.82007,-1.79344 16.52778,0.0724 3.22403,1.6224 232.7236,174.17375 235.79013,177.281 3.35767,3.4022 4.88323,7.0012 5.23218,12.34324 0.27168,4.159 0.0718,5.32914 -1.5138,8.86232 -1.00471,2.23886 -2.78731,4.97342 -3.96129,6.07682 -4.28583,4.02812 -232.27571,175.25818 -235.54572,176.9048 -4.6586,2.34584 -12.1025,2.3876 -16.52928,0.0928 -3.87486,-2.00878 -7.80538,-5.7435 -9.67029,-9.18862 l -1.46069,-2.69842 -0.1736,-178.1462 -0.1736,-178.14618 z m 45.66908,298.67589 c 1.00351,-0.0612 154.99009,-116.48775 154.99009,-117.18534 0,-0.71184 -154.02593,-116.84804 -154.99009,-116.86318 -0.27827,-0.004 -0.50595,52.66168 -0.50595,117.03568 0,64.37401 0.22768,117.0298 0.50595,117.01284 z"
|
||||
style="fill:#818183;fill-opacity:1;stroke-width:0.6746" /><path
|
||||
style="fill:#818183;stroke-width:0.85;fill-opacity:1"
|
||||
class="st0"
|
||||
d="M 501.14231,400.15949 345.84732,517.1195 v -233.75 l 155.29499,116.79 m 66.63999,0 c 0,-6.12 -2.72,-12.155 -8.15999,-16.235 L 330.12233,211.28949 c -13.43,-10.115 -32.555,-0.51 -32.555,16.235 V 572.8795 c 0,16.745 19.125,26.35 32.555,16.235 l 229.49998,-172.805 c 5.44,-3.995 8.16,-10.03 8.15999,-16.15 z"
|
||||
id="path6-2" /><path
|
||||
style="fill:#818183;fill-opacity:1;stroke-width:0.6746"
|
||||
d="m 300.17824,218.44361 c 2.06443,-4.13128 4.83026,-6.86894 9.40917,-9.3133 3.43647,-1.83451 12.82007,-1.79344 16.52778,0.0724 3.22403,1.6224 232.7236,174.17375 235.79013,177.281 3.35767,3.4022 4.88323,7.0012 5.23218,12.34324 0.27168,4.159 0.0718,5.32914 -1.5138,8.86232 -1.00471,2.23886 -2.78731,4.97342 -3.96129,6.07682 -4.28583,4.02812 -232.27571,175.25818 -235.54572,176.9048 -4.6586,2.34584 -12.1025,2.3876 -16.52928,0.0928 -3.87486,-2.00878 -7.80538,-5.7435 -9.67029,-9.18862 l -1.46069,-2.69842 -0.1736,-178.1462 -0.1736,-178.14618 z m 45.66908,298.67589 c 1.00351,-0.0612 154.99009,-116.48775 154.99009,-117.18534 0,-0.71184 -154.02593,-116.84804 -154.99009,-116.86318 -0.27827,-0.004 -0.50595,52.66168 -0.50595,117.03568 0,64.37401 0.22768,117.0298 0.50595,117.01284 z"
|
||||
id="path19-3" /></svg>
|
After Width: | Height: | Size: 3.3 KiB |
46
resources/images/notification_play_hover.svg
Normal file
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xml:space="preserve"
|
||||
style="enable-background:new 0 0 800 800;"
|
||||
viewBox="0 0 800 800"
|
||||
y="0px"
|
||||
x="0px"
|
||||
id="Layer_1"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
||||
id="metadata15"><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><defs
|
||||
id="defs13" />
|
||||
<style
|
||||
id="style2"
|
||||
type="text/css">
|
||||
.st0{fill:#323a3e;}
|
||||
</style>
|
||||
|
||||
<circle
|
||||
style="fill:#eeeeee;fill-opacity:1;stroke-width:9.17961"
|
||||
id="path1"
|
||||
cx="400"
|
||||
cy="400"
|
||||
r="381.60001" /><path
|
||||
id="path6"
|
||||
d="M 519.71435,400.1888 335.90365,538.62534 V 261.9535 l 183.8107,138.23531 m 78.87661,0 c 0,-7.24377 -3.21945,-14.38694 -9.65835,-19.21612 L 317.29119,176.63795 c -15.89606,-11.97235 -38.53284,-0.60365 -38.53284,19.21612 v 408.77008 c 0,19.81976 22.63678,31.18846 38.53284,19.21611 L 588.93261,419.30432 c 6.43891,-4.72858 9.65836,-11.87174 9.65835,-19.11551 z"
|
||||
class="st0"
|
||||
style="fill:#acacac;fill-opacity:1;stroke-width:1.00608" />
|
||||
|
||||
<path
|
||||
id="path19"
|
||||
d="m 281.84868,185.10573 c 2.44351,-4.88988 5.71721,-8.13024 11.13691,-11.02344 4.06748,-2.17136 15.17413,-2.12275 19.56266,0.0857 3.81603,1.92031 275.45697,206.15603 279.08658,209.83384 3.97422,4.02692 5.77991,8.28678 6.19293,14.60974 0.32157,4.92269 0.085,6.30769 -1.79177,10.48965 -1.1892,2.64996 -3.29912,5.88665 -4.68867,7.19266 -5.07281,4.76777 -274.92684,207.43959 -278.7973,209.38856 -5.51402,2.77659 -14.32479,2.82602 -19.56443,0.10984 -4.58637,-2.37764 -9.23863,-6.79814 -11.44598,-10.87586 l -1.7289,-3.19391 -0.20548,-210.85791 -0.20548,-210.85789 z m 54.05497,353.51961 c 1.18777,-0.0724 183.44981,-137.87757 183.44981,-138.70325 0,-0.84255 -182.30861,-138.30401 -183.44981,-138.32193 -0.32937,-0.005 -0.59886,62.33157 -0.59886,138.52611 0,76.19454 0.26949,138.51914 0.59886,138.49907 z"
|
||||
style="fill:#acacac;fill-opacity:1;stroke-width:0.798472" /><path
|
||||
style="fill:#acacac;fill-opacity:1;stroke-width:1.00608"
|
||||
class="st0"
|
||||
d="M 519.71435,400.18878 335.90365,538.62532 V 261.95348 l 183.8107,138.23532 m 78.87661,0 c 0,-7.24378 -3.21945,-14.38694 -9.65835,-19.21612 L 317.29119,176.63794 c -15.89606,-11.97235 -38.53284,-0.60365 -38.53284,19.21611 v 408.77009 c 0,19.81976 22.63678,31.18846 38.53284,19.21611 L 588.93261,419.30431 c 6.43891,-4.72858 9.65836,-11.87174 9.65835,-19.11551 z"
|
||||
id="path6-2" /><path
|
||||
style="fill:#acacac;fill-opacity:1;stroke-width:0.798472"
|
||||
d="m 281.84868,185.10572 c 2.44351,-4.88988 5.71721,-8.13024 11.13691,-11.02344 4.06748,-2.17137 15.17413,-2.12275 19.56266,0.0857 3.81603,1.92031 275.45697,206.15603 279.08658,209.83384 3.97422,4.02692 5.77991,8.28678 6.19293,14.60974 0.32157,4.92269 0.085,6.30769 -1.79177,10.48964 -1.1892,2.64997 -3.29912,5.88666 -4.68867,7.19267 -5.07281,4.76777 -274.92684,207.43958 -278.7973,209.38856 -5.51402,2.77659 -14.32479,2.82602 -19.56443,0.10984 -4.58637,-2.37764 -9.23863,-6.79814 -11.44598,-10.87586 l -1.7289,-3.19391 -0.20548,-210.85791 -0.20548,-210.85789 z m 54.05497,353.5196 c 1.18777,-0.0724 183.44981,-137.87756 183.44981,-138.70324 0,-0.84255 -182.30861,-138.30401 -183.44981,-138.32193 -0.32937,-0.005 -0.59886,62.33157 -0.59886,138.5261 0,76.19455 0.26949,138.51915 0.59886,138.49907 z"
|
||||
id="path19-3" /></svg>
|
After Width: | Height: | Size: 3.5 KiB |
46
resources/images/notification_play_hover_dark.svg
Normal file
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xml:space="preserve"
|
||||
style="enable-background:new 0 0 800 800;"
|
||||
viewBox="0 0 800 800"
|
||||
y="0px"
|
||||
x="0px"
|
||||
id="Layer_1"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
||||
id="metadata15"><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><defs
|
||||
id="defs13" />
|
||||
<style
|
||||
id="style2"
|
||||
type="text/css">
|
||||
.st0{fill:#323a3e;}
|
||||
</style>
|
||||
|
||||
<circle
|
||||
style="fill:#3e3e45;fill-opacity:1;stroke-width:9.17961"
|
||||
id="path1"
|
||||
cx="400"
|
||||
cy="400"
|
||||
r="381.60001" /><path
|
||||
id="path6"
|
||||
d="M 519.71435,400.1888 335.90365,538.62534 V 261.9535 l 183.8107,138.23531 m 78.87661,0 c 0,-7.24377 -3.21945,-14.38694 -9.65835,-19.21612 L 317.29119,176.63795 c -15.89606,-11.97235 -38.53284,-0.60365 -38.53284,19.21612 v 408.77008 c 0,19.81976 22.63678,31.18846 38.53284,19.21611 L 588.93261,419.30432 c 6.43891,-4.72858 9.65836,-11.87174 9.65835,-19.11551 z"
|
||||
class="st0"
|
||||
style="fill:#818183;fill-opacity:1;stroke-width:1.00608" />
|
||||
|
||||
<path
|
||||
id="path19"
|
||||
d="m 281.84868,185.10573 c 2.44351,-4.88988 5.71721,-8.13024 11.13691,-11.02344 4.06748,-2.17136 15.17413,-2.12275 19.56266,0.0857 3.81603,1.92031 275.45697,206.15603 279.08658,209.83384 3.97422,4.02692 5.77991,8.28678 6.19293,14.60974 0.32157,4.92269 0.085,6.30769 -1.79177,10.48965 -1.1892,2.64996 -3.29912,5.88665 -4.68867,7.19266 -5.07281,4.76777 -274.92684,207.43959 -278.7973,209.38856 -5.51402,2.77659 -14.32479,2.82602 -19.56443,0.10984 -4.58637,-2.37764 -9.23863,-6.79814 -11.44598,-10.87586 l -1.7289,-3.19391 -0.20548,-210.85791 -0.20548,-210.85789 z m 54.05497,353.51961 c 1.18777,-0.0724 183.44981,-137.87757 183.44981,-138.70325 0,-0.84255 -182.30861,-138.30401 -183.44981,-138.32193 -0.32937,-0.005 -0.59886,62.33157 -0.59886,138.52611 0,76.19454 0.26949,138.51914 0.59886,138.49907 z"
|
||||
style="fill:#818183;fill-opacity:1;stroke-width:0.798472" /><path
|
||||
style="fill:#818183;fill-opacity:1;stroke-width:1.00608"
|
||||
class="st0"
|
||||
d="M 519.71435,400.18878 335.90365,538.62532 V 261.95348 l 183.8107,138.23532 m 78.87661,0 c 0,-7.24378 -3.21945,-14.38694 -9.65835,-19.21612 L 317.29119,176.63794 c -15.89606,-11.97235 -38.53284,-0.60365 -38.53284,19.21611 v 408.77009 c 0,19.81976 22.63678,31.18846 38.53284,19.21611 L 588.93261,419.30431 c 6.43891,-4.72858 9.65836,-11.87174 9.65835,-19.11551 z"
|
||||
id="path6-2" /><path
|
||||
style="fill:#818183;fill-opacity:1;stroke-width:0.798472"
|
||||
d="m 281.84868,185.10572 c 2.44351,-4.88988 5.71721,-8.13024 11.13691,-11.02344 4.06748,-2.17137 15.17413,-2.12275 19.56266,0.0857 3.81603,1.92031 275.45697,206.15603 279.08658,209.83384 3.97422,4.02692 5.77991,8.28678 6.19293,14.60974 0.32157,4.92269 0.085,6.30769 -1.79177,10.48964 -1.1892,2.64997 -3.29912,5.88666 -4.68867,7.19267 -5.07281,4.76777 -274.92684,207.43958 -278.7973,209.38856 -5.51402,2.77659 -14.32479,2.82602 -19.56443,0.10984 -4.58637,-2.37764 -9.23863,-6.79814 -11.44598,-10.87586 l -1.7289,-3.19391 -0.20548,-210.85791 -0.20548,-210.85789 z m 54.05497,353.5196 c 1.18777,-0.0724 183.44981,-137.87756 183.44981,-138.70324 0,-0.84255 -182.30861,-138.30401 -183.44981,-138.32193 -0.32937,-0.005 -0.59886,62.33157 -0.59886,138.5261 0,76.19455 0.26949,138.51915 0.59886,138.49907 z"
|
||||
id="path19-3" /></svg>
|
After Width: | Height: | Size: 3.5 KiB |
@ -210,6 +210,20 @@ namespace ImGui
|
||||
const wchar_t ExpandArrowIcon = 0x0843;
|
||||
const wchar_t CompleteIcon = 0x0844;
|
||||
|
||||
// void MyFunction(const char* name, const MyMatrix44& v);
|
||||
// Orca
|
||||
const wchar_t PlayButton = 0x0850;
|
||||
const wchar_t PlayDarkButton = 0x0851;
|
||||
const wchar_t PlayHoverButton = 0x0852;
|
||||
const wchar_t PlayHoverDarkButton = 0x0853;
|
||||
const wchar_t PauseButton = 0x0854;
|
||||
const wchar_t PauseDarkButton = 0x0855;
|
||||
const wchar_t PauseHoverButton = 0x0856;
|
||||
const wchar_t PauseHoverDarkButton = 0x0857;
|
||||
const wchar_t OpenButton = 0x0858;
|
||||
const wchar_t OpenDarkButton = 0x0859;
|
||||
const wchar_t OpenHoverButton = 0x085A;
|
||||
const wchar_t OpenHoverDarkButton = 0x085B;
|
||||
|
||||
// void MyFunction(const char* name, const MyMatrix44& v);
|
||||
}
|
||||
|
||||
|
@ -333,6 +333,11 @@ void AppConfig::set_defaults()
|
||||
set("download_path", "");
|
||||
}
|
||||
|
||||
// Orca
|
||||
if (get("ps_url_registered").empty()) {
|
||||
set_bool("ps_url_registered", false);
|
||||
}
|
||||
|
||||
if (get("mouse_wheel").empty()) {
|
||||
set("mouse_wheel", "0");
|
||||
}
|
||||
|
@ -145,6 +145,8 @@ set(lisbslic3r_sources
|
||||
Format/SL1.cpp
|
||||
Format/svg.hpp
|
||||
Format/svg.cpp
|
||||
Format/ZipperArchiveImport.hpp
|
||||
Format/ZipperArchiveImport.cpp
|
||||
GCode/ThumbnailData.cpp
|
||||
GCode/ThumbnailData.hpp
|
||||
GCode/CoolingBuffer.cpp
|
||||
|
147
src/libslic3r/Format/ZipperArchiveImport.cpp
Normal file
@ -0,0 +1,147 @@
|
||||
///|/ Copyright (c) Prusa Research 2022 - 2023 Tomáš Mészáros @tamasmeszaros, David Kocík @kocikdav
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#include "ZipperArchiveImport.hpp"
|
||||
|
||||
#include "libslic3r/miniz_extension.hpp"
|
||||
#include "libslic3r/Exception.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace {
|
||||
|
||||
// Read an ini file into boost property tree
|
||||
boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry,
|
||||
MZ_Archive &zip)
|
||||
{
|
||||
std::string buf(size_t(entry.m_uncomp_size), '\0');
|
||||
|
||||
if (!mz_zip_reader_extract_to_mem(&zip.arch, entry.m_file_index,
|
||||
buf.data(), buf.size(), 0))
|
||||
throw Slic3r::FileIOError(zip.get_errorstr());
|
||||
|
||||
boost::property_tree::ptree tree;
|
||||
std::stringstream ss(buf);
|
||||
boost::property_tree::read_ini(ss, tree);
|
||||
return tree;
|
||||
}
|
||||
|
||||
// Read an arbitrary file into EntryBuffer
|
||||
EntryBuffer read_entry(const mz_zip_archive_file_stat &entry,
|
||||
MZ_Archive &zip,
|
||||
const std::string &name)
|
||||
{
|
||||
std::vector<uint8_t> buf(entry.m_uncomp_size);
|
||||
|
||||
if (!mz_zip_reader_extract_to_mem(&zip.arch, entry.m_file_index,
|
||||
buf.data(), buf.size(), 0))
|
||||
throw Slic3r::FileIOError(zip.get_errorstr());
|
||||
|
||||
return {std::move(buf), (name.empty() ? entry.m_filename : name)};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ZipperArchive read_zipper_archive(const std::string &zipfname,
|
||||
const std::vector<std::string> &includes,
|
||||
const std::vector<std::string> &excludes)
|
||||
{
|
||||
ZipperArchive arch;
|
||||
|
||||
// Little RAII
|
||||
struct Arch : public MZ_Archive
|
||||
{
|
||||
Arch(const std::string &fname)
|
||||
{
|
||||
if (!open_zip_reader(&arch, fname))
|
||||
throw Slic3r::FileIOError(get_errorstr());
|
||||
}
|
||||
|
||||
~Arch() { close_zip_reader(&arch); }
|
||||
} zip(zipfname);
|
||||
|
||||
mz_uint num_entries = mz_zip_reader_get_num_files(&zip.arch);
|
||||
|
||||
for (mz_uint i = 0; i < num_entries; ++i) {
|
||||
mz_zip_archive_file_stat entry;
|
||||
|
||||
if (mz_zip_reader_file_stat(&zip.arch, i, &entry)) {
|
||||
std::string name = entry.m_filename;
|
||||
boost::algorithm::to_lower(name);
|
||||
|
||||
if (!std::any_of(includes.begin(), includes.end(),
|
||||
[&name](const std::string &incl) {
|
||||
return boost::algorithm::contains(name, incl);
|
||||
}))
|
||||
continue;
|
||||
|
||||
if (std::any_of(excludes.begin(), excludes.end(),
|
||||
[&name](const std::string &excl) {
|
||||
return boost::algorithm::contains(name, excl);
|
||||
}))
|
||||
continue;
|
||||
|
||||
if (name == CONFIG_FNAME) {
|
||||
arch.config = read_ini(entry, zip);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name == PROFILE_FNAME) {
|
||||
arch.profile = read_ini(entry, zip);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto it = std::lower_bound(
|
||||
arch.entries.begin(), arch.entries.end(),
|
||||
EntryBuffer{{}, name},
|
||||
[](const EntryBuffer &r1, const EntryBuffer &r2) {
|
||||
return std::less<std::string>()(r1.fname, r2.fname);
|
||||
});
|
||||
|
||||
arch.entries.insert(it, read_entry(entry, zip, name));
|
||||
}
|
||||
}
|
||||
|
||||
return arch;
|
||||
}
|
||||
|
||||
std::pair<DynamicPrintConfig, ConfigSubstitutions> extract_profile(
|
||||
const ZipperArchive &arch, DynamicPrintConfig &profile_out)
|
||||
{
|
||||
DynamicPrintConfig profile_in, profile_use;
|
||||
ConfigSubstitutions config_substitutions =
|
||||
profile_in.load(arch.profile,
|
||||
ForwardCompatibilitySubstitutionRule::Enable);
|
||||
|
||||
if (profile_in.empty()) { // missing profile... do guess work
|
||||
// try to recover the layer height from the config.ini which was
|
||||
// present in all versions of sl1 files.
|
||||
if (auto lh_opt = arch.config.find("layerHeight");
|
||||
lh_opt != arch.config.not_found()) {
|
||||
auto lh_str = lh_opt->second.data();
|
||||
|
||||
size_t pos = 0;
|
||||
double lh = string_to_double_decimal_point(lh_str, &pos);
|
||||
if (pos) { // TODO: verify that pos is 0 when parsing fails
|
||||
profile_out.set("layer_height", lh);
|
||||
profile_out.set("initial_layer_height", lh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the archive contains an empty profile, use the one that was passed
|
||||
// as output argument then replace it with the readed profile to report
|
||||
// that it was empty.
|
||||
profile_use = profile_in.empty() ? profile_out : profile_in;
|
||||
profile_out = profile_in;
|
||||
|
||||
return {profile_use, std::move(config_substitutions)};
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
58
src/libslic3r/Format/ZipperArchiveImport.hpp
Normal file
@ -0,0 +1,58 @@
|
||||
///|/ Copyright (c) Prusa Research 2022 Tomáš Mészáros @tamasmeszaros
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#ifndef ZIPPERARCHIVEIMPORT_HPP
|
||||
#define ZIPPERARCHIVEIMPORT_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Buffer for arbitraryfiles inside a zipper archive.
|
||||
struct EntryBuffer
|
||||
{
|
||||
std::vector<uint8_t> buf;
|
||||
std::string fname;
|
||||
};
|
||||
|
||||
// Structure holding the data read from a zipper archive.
|
||||
struct ZipperArchive
|
||||
{
|
||||
boost::property_tree::ptree profile, config;
|
||||
std::vector<EntryBuffer> entries;
|
||||
};
|
||||
|
||||
// Names of the files containing metadata inside the archive.
|
||||
const constexpr char *CONFIG_FNAME = "config.ini";
|
||||
const constexpr char *PROFILE_FNAME = "prusaslicer.ini";
|
||||
|
||||
// Read an archive that was written using the Zipper class.
|
||||
// The includes parameter is a set of file name substrings that the entries
|
||||
// must contain to be included in ZipperArchive.
|
||||
// The excludes parameter may contain substrings that filenames must not
|
||||
// contain.
|
||||
// Every file in the archive is read into ZipperArchive::entries
|
||||
// except the files CONFIG_FNAME, and PROFILE_FNAME which are read into
|
||||
// ZipperArchive::config and ZipperArchive::profile structures.
|
||||
ZipperArchive read_zipper_archive(const std::string &zipfname,
|
||||
const std::vector<std::string> &includes,
|
||||
const std::vector<std::string> &excludes);
|
||||
|
||||
// Extract the print profile form the archive into 'out'.
|
||||
// Returns a profile that has correct parameters to use for model reconstruction
|
||||
// even if the needed parameters were not fully found in the archive's metadata.
|
||||
// The inout argument shall be a usable fallback profile if the archive
|
||||
// has totally corrupted metadata.
|
||||
std::pair<DynamicPrintConfig, ConfigSubstitutions> extract_profile(
|
||||
const ZipperArchive &arch, DynamicPrintConfig &inout);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // ZIPPERARCHIVEIMPORT_HPP
|
@ -8000,6 +8000,40 @@ mz_uint mz_zip_reader_get_extra(mz_zip_archive* pZip, mz_uint file_index, char*
|
||||
return ne + 1;
|
||||
}
|
||||
|
||||
mz_uint mz_zip_reader_get_filename_from_extra(mz_zip_archive* pZip, mz_uint file_index, char* buffer, mz_uint extra_buf_size)
|
||||
{
|
||||
if (extra_buf_size == 0)
|
||||
return 0;
|
||||
mz_uint nf;
|
||||
mz_uint ne;
|
||||
const mz_uint8* p = mz_zip_get_cdh(pZip, file_index);
|
||||
if (!p)
|
||||
{
|
||||
if (extra_buf_size)
|
||||
buffer[0] = '\0';
|
||||
mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
|
||||
return 0;
|
||||
}
|
||||
nf = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
|
||||
ne = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS);
|
||||
|
||||
int copy = 0;
|
||||
char const* p_nf = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + nf;
|
||||
char const* e = p_nf + ne + 1;
|
||||
while (p_nf + 4 < e) {
|
||||
mz_uint16 len = ((mz_uint16)p_nf[2]) | ((mz_uint16)p_nf[3] << 8);
|
||||
if (p_nf[0] == '\x75' && p_nf[1] == '\x70' && len >= 5 && p_nf + 4 + len < e && p_nf[4] == '\x01') {
|
||||
mz_uint length = MZ_MIN(len - 5, extra_buf_size - 1);
|
||||
memcpy(buffer, p_nf + 9, length);
|
||||
return length;
|
||||
}
|
||||
else {
|
||||
p_nf += 4 + len;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat)
|
||||
{
|
||||
return mz_zip_file_stat_internal(pZip, file_index, mz_zip_get_cdh(pZip, file_index), pStat, NULL);
|
||||
|
@ -1171,6 +1171,9 @@ mz_uint mz_zip_reader_get_extra(mz_zip_archive *pZip, mz_uint file_index, char *
|
||||
int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags);
|
||||
int mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *file_index);
|
||||
|
||||
/* Retrieves the filename of an archive file entry from EXTRA ID. */
|
||||
mz_uint mz_zip_reader_get_filename_from_extra(mz_zip_archive * pZip, mz_uint file_index, char* buffer, mz_uint extra_buf_size);
|
||||
|
||||
/* Returns detailed information about an archive file entry. */
|
||||
mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat);
|
||||
|
||||
|
@ -111,7 +111,7 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/GLCanvas3D.hpp
|
||||
GUI/GLCanvas3D.cpp
|
||||
GUI/SceneRaycaster.hpp
|
||||
GUI/SceneRaycaster.cpp
|
||||
GUI/SceneRaycaster.cpp
|
||||
GUI/OpenGLManager.hpp
|
||||
GUI/OpenGLManager.cpp
|
||||
GUI/Selection.hpp
|
||||
@ -290,6 +290,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/ConfigManipulation.hpp
|
||||
GUI/Field.cpp
|
||||
GUI/Field.hpp
|
||||
GUI/FileArchiveDialog.cpp
|
||||
GUI/FileArchiveDialog.hpp
|
||||
GUI/OptionsGroup.cpp
|
||||
GUI/OptionsGroup.hpp
|
||||
GUI/OG_CustomCtrl.cpp
|
||||
@ -342,6 +344,10 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/ConfigWizard_private.hpp
|
||||
GUI/MsgDialog.cpp
|
||||
GUI/MsgDialog.hpp
|
||||
GUI/Downloader.hpp
|
||||
GUI/Downloader.cpp
|
||||
GUI/DownloaderFileGet.hpp
|
||||
GUI/DownloaderFileGet.cpp
|
||||
GUI/DownloadProgressDialog.hpp
|
||||
GUI/DownloadProgressDialog.cpp
|
||||
GUI/UpdateDialogs.cpp
|
||||
|
@ -455,6 +455,171 @@ void DesktopIntegrationDialog::undo_desktop_intgration()
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::UndoDesktopIntegrationSuccess);
|
||||
}
|
||||
|
||||
void DesktopIntegrationDialog::perform_downloader_desktop_integration()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << "performing downloader desktop integration.";
|
||||
// Path to appimage
|
||||
const char* appimage_env = std::getenv("APPIMAGE");
|
||||
std::string excutable_path;
|
||||
if (appimage_env) {
|
||||
try {
|
||||
excutable_path = boost::filesystem::canonical(boost::filesystem::path(appimage_env)).string();
|
||||
}
|
||||
catch (std::exception&) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed - boost::filesystem::canonical did not return appimage path.";
|
||||
show_error(nullptr, _L("Performing downloader desktop integration failed - boost::filesystem::canonical did not return appimage path."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// not appimage - find executable
|
||||
excutable_path = boost::dll::program_location().string();
|
||||
//excutable_path = wxStandardPaths::Get().GetExecutablePath().string();
|
||||
BOOST_LOG_TRIVIAL(debug) << "non-appimage path to executable: " << excutable_path;
|
||||
if (excutable_path.empty())
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed - no executable found.";
|
||||
show_error(nullptr, _L("Performing downloader desktop integration failed - Could not find executable."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Escape ' characters in appimage, other special symbols will be esacaped in desktop file by 'excutable_path'
|
||||
//boost::replace_all(excutable_path, "'", "'\\''");
|
||||
excutable_path = escape_string(excutable_path);
|
||||
|
||||
// Find directories icons and applications
|
||||
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
|
||||
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
|
||||
// $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data files in addition to the $XDG_DATA_HOME base directory.
|
||||
// The directories in $XDG_DATA_DIRS should be seperated with a colon ':'.
|
||||
// If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used.
|
||||
std::vector<std::string>target_candidates;
|
||||
resolve_path_from_var("XDG_DATA_HOME", target_candidates);
|
||||
resolve_path_from_var("XDG_DATA_DIRS", target_candidates);
|
||||
|
||||
AppConfig* app_config = wxGetApp().app_config;
|
||||
// suffix string to create different desktop file for alpha, beta.
|
||||
|
||||
std::string version_suffix;
|
||||
std::string name_suffix;
|
||||
std::string version(SLIC3R_VERSION);
|
||||
if (version.find("alpha") != std::string::npos)
|
||||
{
|
||||
version_suffix = "-alpha";
|
||||
name_suffix = " - alpha";
|
||||
}
|
||||
else if (version.find("beta") != std::string::npos)
|
||||
{
|
||||
version_suffix = "-beta";
|
||||
name_suffix = " - beta";
|
||||
}
|
||||
|
||||
// theme path to icon destination
|
||||
std::string icon_theme_path;
|
||||
std::string icon_theme_dirs;
|
||||
|
||||
if (platform_flavor() == PlatformFlavor::LinuxOnChromium) {
|
||||
icon_theme_path = "hicolor/96x96/apps/";
|
||||
icon_theme_dirs = "/hicolor/96x96/apps";
|
||||
}
|
||||
|
||||
std::string target_dir_desktop;
|
||||
|
||||
// desktop file
|
||||
// iterate thru target_candidates to find applications folder
|
||||
|
||||
std::string desktop_file_downloader = GUI::format(
|
||||
"[Desktop Entry]\n"
|
||||
"Name=PrusaSlicer URL Protocol%1%\n"
|
||||
"Exec=\"%2%\" %%u\n"
|
||||
"Terminal=false\n"
|
||||
"Type=Application\n"
|
||||
"MimeType=x-scheme-handler/prusaslicer;\n"
|
||||
"StartupNotify=false\n"
|
||||
"NoDisplay=true\n"
|
||||
, name_suffix, excutable_path);
|
||||
|
||||
// desktop file for downloader as part of main app
|
||||
std::string desktop_path = GUI::format("%1%/applications/PrusaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
|
||||
if (create_desktop_file(desktop_path, desktop_file_downloader)) {
|
||||
// save path to desktop file
|
||||
app_config->set("desktop_integration_URL_path", desktop_path);
|
||||
// finish registration on mime type
|
||||
std::string command = GUI::format("xdg-mime default PrusaSlicerURLProtocol%1%.desktop x-scheme-handler/prusaslicer", version_suffix);
|
||||
BOOST_LOG_TRIVIAL(debug) << "system command: " << command;
|
||||
int r = system(command.c_str());
|
||||
BOOST_LOG_TRIVIAL(debug) << "system result: " << r;
|
||||
|
||||
}
|
||||
|
||||
bool candidate_found = false;
|
||||
for (size_t i = 0; i < target_candidates.size(); ++i) {
|
||||
if (contains_path_dir(target_candidates[i], "applications")) {
|
||||
target_dir_desktop = target_candidates[i];
|
||||
// Write slicer desktop file
|
||||
std::string path = GUI::format("%1%/applications/PrusaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
|
||||
if (create_desktop_file(path, desktop_file_downloader)) {
|
||||
app_config->set("desktop_integration_URL_path", path);
|
||||
candidate_found = true;
|
||||
BOOST_LOG_TRIVIAL(debug) << "PrusaSlicerURLProtocol.desktop file installation success.";
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// write failed - try another path
|
||||
BOOST_LOG_TRIVIAL(debug) << "Attempt to PrusaSlicerURLProtocol.desktop file installation failed. failed path: " << target_candidates[i];
|
||||
target_dir_desktop.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
// if all failed - try creating default home folder
|
||||
if (!candidate_found) {
|
||||
// create $HOME/.local/share
|
||||
create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications");
|
||||
// create desktop file
|
||||
target_dir_desktop = GUI::format("%1%/.local/share", wxFileName::GetHomeDir());
|
||||
std::string path = GUI::format("%1%/applications/PrusaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
|
||||
if (contains_path_dir(target_dir_desktop, "applications")) {
|
||||
if (!create_desktop_file(path, desktop_file_downloader)) {
|
||||
// Desktop file not written - end desktop integration
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed - could not create desktop file.";
|
||||
return;
|
||||
}
|
||||
app_config->set("desktop_integration_URL_path", path);
|
||||
}
|
||||
else {
|
||||
// Desktop file not written - end desktop integration
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed because the application directory was not found.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
assert(!target_dir_desktop.empty());
|
||||
if (target_dir_desktop.empty()) {
|
||||
// Desktop file not written - end desktop integration
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed because the application directory was not found.";
|
||||
show_error(nullptr, _L("Performing downloader desktop integration failed because the application directory was not found."));
|
||||
return;
|
||||
}
|
||||
|
||||
// finish registration on mime type
|
||||
std::string command = GUI::format("xdg-mime default PrusaSlicerURLProtocol%1%.desktop x-scheme-handler/prusaslicer", version_suffix);
|
||||
BOOST_LOG_TRIVIAL(debug) << "system command: " << command;
|
||||
int r = system(command.c_str());
|
||||
BOOST_LOG_TRIVIAL(debug) << "system result: " << r;
|
||||
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationSuccess);
|
||||
}
|
||||
void DesktopIntegrationDialog::undo_downloader_registration()
|
||||
{
|
||||
const AppConfig *app_config = wxGetApp().app_config;
|
||||
std::string path = std::string(app_config->get("desktop_integration_URL_path"));
|
||||
if (!path.empty()) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "removing " << path;
|
||||
std::remove(path.c_str());
|
||||
}
|
||||
// There is no need to undo xdg-mime default command. It is done automatically when desktop file is deleted.
|
||||
}
|
||||
|
||||
DesktopIntegrationDialog::DesktopIntegrationDialog(wxWindow *parent)
|
||||
: wxDialog(parent, wxID_ANY, _(L("Desktop Integration")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
|
||||
{
|
||||
|
@ -29,6 +29,9 @@ public:
|
||||
static void perform_desktop_integration();
|
||||
// Deletes Desktop files and icons for both PrusaSlicer and GcodeViewer at paths stored in App Config.
|
||||
static void undo_desktop_intgration();
|
||||
|
||||
static void perform_downloader_desktop_integration();
|
||||
static void undo_downloader_registration();
|
||||
private:
|
||||
|
||||
};
|
||||
|
270
src/slic3r/GUI/Downloader.cpp
Normal file
@ -0,0 +1,270 @@
|
||||
///|/ Copyright (c) Prusa Research 2023 David Kocík @kocikdav, Oleksandra Iushchenko @YuSanka
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#include "Downloader.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "NotificationManager.hpp"
|
||||
#include "format.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
namespace {
|
||||
void open_folder(const std::string& path)
|
||||
{
|
||||
// Code taken from NotificationManager.cpp
|
||||
|
||||
// Execute command to open a file explorer, platform dependent.
|
||||
// FIXME: The const_casts aren't needed in wxWidgets 3.1, remove them when we upgrade.
|
||||
|
||||
#ifdef _WIN32
|
||||
const wxString widepath = from_u8(path);
|
||||
const wchar_t* argv[] = { L"explorer", widepath.GetData(), nullptr };
|
||||
::wxExecute(const_cast<wchar_t**>(argv), wxEXEC_ASYNC, nullptr);
|
||||
#elif __APPLE__
|
||||
const char* argv[] = { "open", path.data(), nullptr };
|
||||
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr);
|
||||
#else
|
||||
const char* argv[] = { "xdg-open", path.data(), nullptr };
|
||||
|
||||
// Check if we're running in an AppImage container, if so, we need to remove AppImage's env vars,
|
||||
// because they may mess up the environment expected by the file manager.
|
||||
// Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure.
|
||||
if (wxGetEnv("APPIMAGE", nullptr)) {
|
||||
// We're running from AppImage
|
||||
wxEnvVariableHashMap env_vars;
|
||||
wxGetEnvMap(&env_vars);
|
||||
|
||||
env_vars.erase("APPIMAGE");
|
||||
env_vars.erase("APPDIR");
|
||||
env_vars.erase("LD_LIBRARY_PATH");
|
||||
env_vars.erase("LD_PRELOAD");
|
||||
env_vars.erase("UNION_PRELOAD");
|
||||
|
||||
wxExecuteEnv exec_env;
|
||||
exec_env.env = std::move(env_vars);
|
||||
|
||||
wxString owd;
|
||||
if (wxGetEnv("OWD", &owd)) {
|
||||
// This is the original work directory from which the AppImage image was run,
|
||||
// set it as CWD for the child process:
|
||||
exec_env.cwd = std::move(owd);
|
||||
}
|
||||
|
||||
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, &exec_env);
|
||||
}
|
||||
else {
|
||||
// Looks like we're NOT running from AppImage, we'll make no changes to the environment.
|
||||
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, nullptr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string filename_from_url(const std::string& url)
|
||||
{
|
||||
// TODO: can it be done with curl?
|
||||
size_t slash = url.find_last_of("/");
|
||||
if (slash == std::string::npos && slash != url.size() - 1)
|
||||
return {};
|
||||
return url.substr(slash + 1, url.size() - slash + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Download::Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
|
||||
: m_id(ID)
|
||||
, m_filename(filename_from_url(url))
|
||||
, m_dest_folder(dest_folder)
|
||||
{
|
||||
assert(boost::filesystem::is_directory(dest_folder));
|
||||
m_final_path = dest_folder / m_filename;
|
||||
m_file_get = std::make_shared<FileGet>(ID, std::move(url), m_filename, evt_handler, dest_folder);
|
||||
}
|
||||
|
||||
void Download::start()
|
||||
{
|
||||
m_state = DownloadState::DownloadOngoing;
|
||||
m_file_get->get();
|
||||
}
|
||||
void Download::cancel()
|
||||
{
|
||||
m_state = DownloadState::DownloadStopped;
|
||||
m_file_get->cancel();
|
||||
}
|
||||
void Download::pause()
|
||||
{
|
||||
//assert(m_state == DownloadState::DownloadOngoing);
|
||||
// if instead of assert - it can happen that user clicks on pause several times before the pause happens
|
||||
if (m_state != DownloadState::DownloadOngoing)
|
||||
return;
|
||||
m_state = DownloadState::DownloadPaused;
|
||||
m_file_get->pause();
|
||||
}
|
||||
void Download::resume()
|
||||
{
|
||||
//assert(m_state == DownloadState::DownloadPaused);
|
||||
if (m_state != DownloadState::DownloadPaused)
|
||||
return;
|
||||
m_state = DownloadState::DownloadOngoing;
|
||||
m_file_get->resume();
|
||||
}
|
||||
|
||||
|
||||
Downloader::Downloader()
|
||||
: wxEvtHandler()
|
||||
{
|
||||
//Bind(EVT_DWNLDR_FILE_COMPLETE, [](const wxCommandEvent& evt) {});
|
||||
//Bind(EVT_DWNLDR_FILE_PROGRESS, [](const wxCommandEvent& evt) {});
|
||||
//Bind(EVT_DWNLDR_FILE_ERROR, [](const wxCommandEvent& evt) {});
|
||||
//Bind(EVT_DWNLDR_FILE_NAME_CHANGE, [](const wxCommandEvent& evt) {});
|
||||
|
||||
Bind(EVT_DWNLDR_FILE_COMPLETE, &Downloader::on_complete, this);
|
||||
Bind(EVT_DWNLDR_FILE_PROGRESS, &Downloader::on_progress, this);
|
||||
Bind(EVT_DWNLDR_FILE_ERROR, &Downloader::on_error, this);
|
||||
Bind(EVT_DWNLDR_FILE_NAME_CHANGE, &Downloader::on_name_change, this);
|
||||
Bind(EVT_DWNLDR_FILE_PAUSED, &Downloader::on_paused, this);
|
||||
Bind(EVT_DWNLDR_FILE_CANCELED, &Downloader::on_canceled, this);
|
||||
}
|
||||
|
||||
void Downloader::start_download(const std::string& full_url)
|
||||
{
|
||||
assert(m_initialized);
|
||||
|
||||
// Orca: check if url is registered
|
||||
if (!wxGetApp().app_config->has("ps_url_registered") || !wxGetApp().app_config->get_bool("ps_url_registered")) {
|
||||
BOOST_LOG_TRIVIAL(error) << "PrusaSlicer links are not enabled. Download aborted: " << full_url;
|
||||
show_error(nullptr, "PrusaSlicer links are not enabled in preferences. Download aborted.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Orca: Move to the 3D view
|
||||
MainFrame* mainframe = wxGetApp().mainframe;
|
||||
Plater* plater = wxGetApp().plater();
|
||||
|
||||
mainframe->Freeze();
|
||||
mainframe->select_tab((size_t)MainFrame::TabPosition::tp3DEditor);
|
||||
plater->select_view_3D("3D");
|
||||
plater->select_view("plate");
|
||||
plater->get_current_canvas3D()->zoom_to_bed();
|
||||
mainframe->Thaw();
|
||||
|
||||
// Orca: Replace PS workaround for "mysterious slash" with a more dynamic approach
|
||||
// Windows seems to have fixed the issue and this provides backwards compatability for those it still affects
|
||||
boost::regex re(R"(^prusaslicer:\/\/open[\/]?\?file=)", boost::regbase::icase);
|
||||
boost::smatch results;
|
||||
|
||||
if (!boost::regex_search(full_url, results, re)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Could not start download due to wrong URL: " << full_url;
|
||||
// Orca: show error
|
||||
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
|
||||
ntf_mngr->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::ErrorNotificationLevel,
|
||||
"Could not start download due to malformed URL");
|
||||
return;
|
||||
}
|
||||
size_t id = get_next_id();
|
||||
std::string escaped_url = FileGet::escape_url(full_url.substr(results.length()));
|
||||
if (!boost::starts_with(escaped_url, "https://") || !FileGet::is_subdomain(escaped_url, "printables.com")) {
|
||||
std::string msg = format(_L("Download won't start. Download URL doesn't point to https://printables.com : %1%"), escaped_url);
|
||||
BOOST_LOG_TRIVIAL(error) << msg;
|
||||
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
|
||||
ntf_mngr->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::ErrorNotificationLevel,
|
||||
"Download failed. Download URL doesn't point to https://printables.com.");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string text(escaped_url);
|
||||
m_downloads.emplace_back(std::make_unique<Download>(id, std::move(escaped_url), this, m_dest_folder));
|
||||
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
|
||||
ntf_mngr->push_download_URL_progress_notification(id, m_downloads.back()->get_filename(), std::bind(&Downloader::user_action_callback, this, std::placeholders::_1, std::placeholders::_2));
|
||||
m_downloads.back()->start();
|
||||
BOOST_LOG_TRIVIAL(debug) << "started download";
|
||||
}
|
||||
|
||||
void Downloader::on_progress(wxCommandEvent& event)
|
||||
{
|
||||
size_t id = event.GetInt();
|
||||
float percent = (float)std::stoi(boost::nowide::narrow(event.GetString())) / 100.f;
|
||||
//BOOST_LOG_TRIVIAL(error) << "progress " << id << ": " << percent;
|
||||
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
|
||||
BOOST_LOG_TRIVIAL(trace) << "Download "<< id << ": " << percent;
|
||||
ntf_mngr->set_download_URL_progress(id, percent);
|
||||
}
|
||||
void Downloader::on_error(wxCommandEvent& event)
|
||||
{
|
||||
size_t id = event.GetInt();
|
||||
set_download_state(event.GetInt(), DownloadState::DownloadError);
|
||||
BOOST_LOG_TRIVIAL(error) << "Download error: " << event.GetString();
|
||||
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
|
||||
ntf_mngr->set_download_URL_error(id, boost::nowide::narrow(event.GetString()));
|
||||
show_error(nullptr, format_wxstr(L"%1%\n%2%", _L("The download has failed") + ":", event.GetString()));
|
||||
}
|
||||
void Downloader::on_complete(wxCommandEvent& event)
|
||||
{
|
||||
// TODO: is this always true? :
|
||||
// here we open the file itself, notification should get 1.f progress from on progress.
|
||||
set_download_state(event.GetInt(), DownloadState::DownloadDone);
|
||||
wxArrayString paths;
|
||||
paths.Add(event.GetString());
|
||||
wxGetApp().plater()->load_files(paths);
|
||||
}
|
||||
bool Downloader::user_action_callback(DownloaderUserAction action, int id)
|
||||
{
|
||||
for (size_t i = 0; i < m_downloads.size(); ++i) {
|
||||
if (m_downloads[i]->get_id() == id) {
|
||||
switch (action) {
|
||||
case DownloadUserCanceled:
|
||||
m_downloads[i]->cancel();
|
||||
return true;
|
||||
case DownloadUserPaused:
|
||||
m_downloads[i]->pause();
|
||||
return true;
|
||||
case DownloadUserContinued:
|
||||
m_downloads[i]->resume();
|
||||
return true;
|
||||
case DownloadUserOpenedFolder:
|
||||
open_folder(m_downloads[i]->get_dest_folder());
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Downloader::on_name_change(wxCommandEvent& event)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Downloader::on_paused(wxCommandEvent& event)
|
||||
{
|
||||
size_t id = event.GetInt();
|
||||
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
|
||||
ntf_mngr->set_download_URL_paused(id);
|
||||
}
|
||||
|
||||
void Downloader::on_canceled(wxCommandEvent& event)
|
||||
{
|
||||
size_t id = event.GetInt();
|
||||
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
|
||||
ntf_mngr->set_download_URL_canceled(id);
|
||||
}
|
||||
|
||||
void Downloader::set_download_state(int id, DownloadState state)
|
||||
{
|
||||
for (size_t i = 0; i < m_downloads.size(); ++i) {
|
||||
if (m_downloads[i]->get_id() == id) {
|
||||
m_downloads[i]->set_state(state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
103
src/slic3r/GUI/Downloader.hpp
Normal file
@ -0,0 +1,103 @@
|
||||
///|/ Copyright (c) Prusa Research 2023 David Kocík @kocikdav
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#ifndef slic3r_Downloader_hpp_
|
||||
#define slic3r_Downloader_hpp_
|
||||
|
||||
#include "DownloaderFileGet.hpp"
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <wx/wx.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class NotificationManager;
|
||||
|
||||
enum DownloadState
|
||||
{
|
||||
DownloadPending = 0,
|
||||
DownloadOngoing,
|
||||
DownloadStopped,
|
||||
DownloadDone,
|
||||
DownloadError,
|
||||
DownloadPaused,
|
||||
DownloadStateUnknown
|
||||
};
|
||||
|
||||
enum DownloaderUserAction
|
||||
{
|
||||
DownloadUserCanceled,
|
||||
DownloadUserPaused,
|
||||
DownloadUserContinued,
|
||||
DownloadUserOpenedFolder
|
||||
};
|
||||
|
||||
class Download {
|
||||
public:
|
||||
Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder);
|
||||
void start();
|
||||
void cancel();
|
||||
void pause();
|
||||
void resume();
|
||||
|
||||
int get_id() const { return m_id; }
|
||||
boost::filesystem::path get_final_path() const { return m_final_path; }
|
||||
std::string get_filename() const { return m_filename; }
|
||||
DownloadState get_state() const { return m_state; }
|
||||
void set_state(DownloadState state) { m_state = state; }
|
||||
std::string get_dest_folder() { return m_dest_folder.string(); }
|
||||
private:
|
||||
const int m_id;
|
||||
std::string m_filename;
|
||||
boost::filesystem::path m_final_path;
|
||||
boost::filesystem::path m_dest_folder;
|
||||
std::shared_ptr<FileGet> m_file_get;
|
||||
DownloadState m_state { DownloadState::DownloadPending };
|
||||
};
|
||||
|
||||
class Downloader : public wxEvtHandler {
|
||||
public:
|
||||
Downloader();
|
||||
|
||||
bool get_initialized() { return m_initialized; }
|
||||
void init(const boost::filesystem::path& dest_folder)
|
||||
{
|
||||
m_dest_folder = dest_folder;
|
||||
m_initialized = true;
|
||||
}
|
||||
void start_download(const std::string& full_url);
|
||||
// cancel = false -> just pause
|
||||
bool user_action_callback(DownloaderUserAction action, int id);
|
||||
private:
|
||||
bool m_initialized { false };
|
||||
|
||||
std::vector<std::unique_ptr<Download>> m_downloads;
|
||||
boost::filesystem::path m_dest_folder;
|
||||
|
||||
size_t m_next_id { 0 };
|
||||
size_t get_next_id() { return ++m_next_id; }
|
||||
|
||||
void on_progress(wxCommandEvent& event);
|
||||
void on_error(wxCommandEvent& event);
|
||||
void on_complete(wxCommandEvent& event);
|
||||
void on_name_change(wxCommandEvent& event);
|
||||
void on_paused(wxCommandEvent& event);
|
||||
void on_canceled(wxCommandEvent& event);
|
||||
|
||||
void set_download_state(int id, DownloadState state);
|
||||
/*
|
||||
bool is_in_state(int id, DownloadState state) const;
|
||||
DownloadState get_download_state(int id) const;
|
||||
bool cancel_download(int id);
|
||||
bool pause_download(int id);
|
||||
bool resume_download(int id);
|
||||
bool delete_download(int id);
|
||||
wxString get_path_of(int id) const;
|
||||
wxString get_folder_path_of(int id) const;
|
||||
*/
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
395
src/slic3r/GUI/DownloaderFileGet.cpp
Normal file
@ -0,0 +1,395 @@
|
||||
///|/ Copyright (c) Prusa Research 2023 Oleksandra Iushchenko @YuSanka, David Kocík @kocikdav
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#include "DownloaderFileGet.hpp"
|
||||
|
||||
#include <thread>
|
||||
#include <curl/curl.h>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#include "format.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "I18N.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
const size_t DOWNLOAD_MAX_CHUNK_SIZE = 10 * 1024 * 1024;
|
||||
const size_t DOWNLOAD_SIZE_LIMIT = 1024 * 1024 * 1024;
|
||||
|
||||
std::string FileGet::escape_url(const std::string& unescaped)
|
||||
{
|
||||
std::string ret_val;
|
||||
CURL* curl = curl_easy_init();
|
||||
if (curl) {
|
||||
int decodelen;
|
||||
char* decoded = curl_easy_unescape(curl, unescaped.c_str(), unescaped.size(), &decodelen);
|
||||
if (decoded) {
|
||||
ret_val = std::string(decoded);
|
||||
curl_free(decoded);
|
||||
}
|
||||
curl_easy_cleanup(curl);
|
||||
}
|
||||
return ret_val;
|
||||
}
|
||||
bool FileGet::is_subdomain(const std::string& url, const std::string& domain)
|
||||
{
|
||||
// domain should be f.e. printables.com (.com including)
|
||||
char* host;
|
||||
std::string host_string;
|
||||
CURLUcode rc;
|
||||
CURLU* curl = curl_url();
|
||||
if (!curl) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to init Curl library in function is_domain.";
|
||||
return false;
|
||||
}
|
||||
rc = curl_url_set(curl, CURLUPART_URL, url.c_str(), 0);
|
||||
if (rc != CURLUE_OK) {
|
||||
curl_url_cleanup(curl);
|
||||
return false;
|
||||
}
|
||||
rc = curl_url_get(curl, CURLUPART_HOST, &host, 0);
|
||||
if (rc != CURLUE_OK || !host) {
|
||||
curl_url_cleanup(curl);
|
||||
return false;
|
||||
}
|
||||
host_string = std::string(host);
|
||||
curl_free(host);
|
||||
// now host should be subdomain.domain or just domain
|
||||
if (domain == host_string) {
|
||||
curl_url_cleanup(curl);
|
||||
return true;
|
||||
}
|
||||
if(boost::ends_with(host_string, "." + domain)) {
|
||||
curl_url_cleanup(curl);
|
||||
return true;
|
||||
}
|
||||
curl_url_cleanup(curl);
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace {
|
||||
unsigned get_current_pid()
|
||||
{
|
||||
#ifdef WIN32
|
||||
return GetCurrentProcessId();
|
||||
#else
|
||||
return ::getpid();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// int = DOWNLOAD ID; string = file path
|
||||
wxDEFINE_EVENT(EVT_DWNLDR_FILE_COMPLETE, wxCommandEvent);
|
||||
// int = DOWNLOAD ID; string = error msg
|
||||
wxDEFINE_EVENT(EVT_DWNLDR_FILE_ERROR, wxCommandEvent);
|
||||
// int = DOWNLOAD ID; string = progress percent
|
||||
wxDEFINE_EVENT(EVT_DWNLDR_FILE_PROGRESS, wxCommandEvent);
|
||||
// int = DOWNLOAD ID; string = name
|
||||
wxDEFINE_EVENT(EVT_DWNLDR_FILE_NAME_CHANGE, wxCommandEvent);
|
||||
// int = DOWNLOAD ID;
|
||||
wxDEFINE_EVENT(EVT_DWNLDR_FILE_PAUSED, wxCommandEvent);
|
||||
// int = DOWNLOAD ID;
|
||||
wxDEFINE_EVENT(EVT_DWNLDR_FILE_CANCELED, wxCommandEvent);
|
||||
|
||||
struct FileGet::priv
|
||||
{
|
||||
const int m_id;
|
||||
std::string m_url;
|
||||
std::string m_filename;
|
||||
std::thread m_io_thread;
|
||||
wxEvtHandler* m_evt_handler;
|
||||
boost::filesystem::path m_dest_folder;
|
||||
boost::filesystem::path m_tmp_path; // path when ongoing download
|
||||
std::atomic_bool m_cancel { false };
|
||||
std::atomic_bool m_pause { false };
|
||||
std::atomic_bool m_stopped { false }; // either canceled or paused - download is not running
|
||||
size_t m_written { 0 };
|
||||
size_t m_absolute_size { 0 };
|
||||
priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder);
|
||||
|
||||
void get_perform();
|
||||
};
|
||||
|
||||
FileGet::priv::priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
|
||||
: m_id(ID)
|
||||
, m_url(std::move(url))
|
||||
, m_filename(filename)
|
||||
, m_evt_handler(evt_handler)
|
||||
, m_dest_folder(dest_folder)
|
||||
{
|
||||
}
|
||||
|
||||
void FileGet::priv::get_perform()
|
||||
{
|
||||
assert(m_evt_handler);
|
||||
assert(!m_url.empty());
|
||||
assert(!m_filename.empty());
|
||||
assert(boost::filesystem::is_directory(m_dest_folder));
|
||||
|
||||
m_stopped = false;
|
||||
|
||||
// open dest file
|
||||
if (m_written == 0)
|
||||
{
|
||||
boost::filesystem::path dest_path = m_dest_folder / m_filename;
|
||||
std::string extension = boost::filesystem::extension(dest_path);
|
||||
std::string just_filename = m_filename.substr(0, m_filename.size() - extension.size());
|
||||
std::string final_filename = just_filename;
|
||||
// Find unsed filename
|
||||
try {
|
||||
size_t version = 0;
|
||||
while (boost::filesystem::exists(m_dest_folder / (final_filename + extension)) || boost::filesystem::exists(m_dest_folder / (final_filename + extension + "." + std::to_string(get_current_pid()) + ".download")))
|
||||
{
|
||||
++version;
|
||||
if (version > 999) {
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
|
||||
evt->SetString(GUI::format_wxstr(L"Failed to find suitable filename. Last name: %1%." , (m_dest_folder / (final_filename + extension)).string()));
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
return;
|
||||
}
|
||||
final_filename = GUI::format("%1%(%2%)", just_filename, std::to_string(version));
|
||||
}
|
||||
} catch (const boost::filesystem::filesystem_error& e)
|
||||
{
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
|
||||
evt->SetString(e.what());
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
return;
|
||||
}
|
||||
|
||||
m_filename = final_filename + extension;
|
||||
|
||||
m_tmp_path = m_dest_folder / (m_filename + "." + std::to_string(get_current_pid()) + ".download");
|
||||
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_NAME_CHANGE);
|
||||
evt->SetString(boost::nowide::widen(m_filename));
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
}
|
||||
|
||||
boost::filesystem::path dest_path = m_dest_folder / m_filename;
|
||||
|
||||
wxString temp_path_wstring(m_tmp_path.wstring());
|
||||
|
||||
//std::cout << "dest_path: " << dest_path.string() << std::endl;
|
||||
//std::cout << "m_tmp_path: " << m_tmp_path.string() << std::endl;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << GUI::format("Starting download from %1% to %2%. Temp path is %3%",m_url, dest_path, m_tmp_path);
|
||||
|
||||
FILE* file;
|
||||
// open file for writting
|
||||
if (m_written == 0)
|
||||
file = fopen(temp_path_wstring.c_str(), "wb");
|
||||
else
|
||||
file = fopen(temp_path_wstring.c_str(), "ab");
|
||||
|
||||
//assert(file != NULL);
|
||||
if (file == NULL) {
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
|
||||
// TRN %1% = file path
|
||||
evt->SetString(GUI::format_wxstr(_L("Can't create file at %1%"), temp_path_wstring));
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
return;
|
||||
}
|
||||
|
||||
std:: string range_string = std::to_string(m_written) + "-";
|
||||
|
||||
size_t written_previously = m_written;
|
||||
size_t written_this_session = 0;
|
||||
Http::get(m_url)
|
||||
.size_limit(DOWNLOAD_SIZE_LIMIT) //more?
|
||||
.set_range(range_string)
|
||||
.on_progress([&](Http::Progress progress, bool& cancel) {
|
||||
// to prevent multiple calls into following ifs (m_cancel / m_pause)
|
||||
if (m_stopped){
|
||||
cancel = true;
|
||||
return;
|
||||
}
|
||||
if (m_cancel) {
|
||||
m_stopped = true;
|
||||
fclose(file);
|
||||
// remove canceled file
|
||||
std::remove(m_tmp_path.string().c_str());
|
||||
m_written = 0;
|
||||
cancel = true;
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_CANCELED);
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
return;
|
||||
// TODO: send canceled event?
|
||||
}
|
||||
if (m_pause) {
|
||||
m_stopped = true;
|
||||
fclose(file);
|
||||
cancel = true;
|
||||
if (m_written == 0)
|
||||
std::remove(m_tmp_path.string().c_str());
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_PAUSED);
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_absolute_size < progress.dltotal) {
|
||||
m_absolute_size = progress.dltotal;
|
||||
}
|
||||
|
||||
if (progress.dlnow != 0) {
|
||||
if (progress.dlnow - written_this_session > DOWNLOAD_MAX_CHUNK_SIZE || progress.dlnow == progress.dltotal) {
|
||||
try
|
||||
{
|
||||
std::string part_for_write = progress.buffer.substr(written_this_session, progress.dlnow);
|
||||
fwrite(part_for_write.c_str(), 1, part_for_write.size(), file);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
// fclose(file); do it?
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
|
||||
evt->SetString(e.what());
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
cancel = true;
|
||||
return;
|
||||
}
|
||||
written_this_session = progress.dlnow;
|
||||
m_written = written_previously + written_this_session;
|
||||
}
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_PROGRESS);
|
||||
int percent_total = (written_previously + progress.dlnow) * 100 / m_absolute_size;
|
||||
evt->SetString(std::to_string(percent_total));
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
}
|
||||
|
||||
})
|
||||
.on_error([&](std::string body, std::string error, unsigned http_status) {
|
||||
if (file != NULL)
|
||||
fclose(file);
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
|
||||
if (!error.empty())
|
||||
evt->SetString(GUI::from_u8(error));
|
||||
else
|
||||
evt->SetString(GUI::from_u8(body));
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned /* http_status */) {
|
||||
|
||||
// TODO: perform a body size check
|
||||
//
|
||||
//size_t body_size = body.size();
|
||||
//if (body_size != expected_size) {
|
||||
// return;
|
||||
//}
|
||||
try
|
||||
{
|
||||
/*
|
||||
if (m_written < body.size())
|
||||
{
|
||||
// this code should never be entered. As there should be on_progress call after last bit downloaded.
|
||||
std::string part_for_write = body.substr(m_written);
|
||||
fwrite(part_for_write.c_str(), 1, part_for_write.size(), file);
|
||||
}
|
||||
*/
|
||||
fclose(file);
|
||||
boost::filesystem::rename(m_tmp_path, dest_path);
|
||||
}
|
||||
catch (const std::exception& /*e*/)
|
||||
{
|
||||
//TODO: report?
|
||||
//error_message = GUI::format("Failed to write and move %1% to %2%", tmp_path, dest_path);
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
|
||||
evt->SetString("Failed to write and move.");
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
return;
|
||||
}
|
||||
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_COMPLETE);
|
||||
evt->SetString(dest_path.wstring());
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
}
|
||||
|
||||
FileGet::FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
|
||||
: p(new priv(ID, std::move(url), filename, evt_handler, dest_folder))
|
||||
{}
|
||||
|
||||
FileGet::FileGet(FileGet&& other) : p(std::move(other.p)) {}
|
||||
|
||||
FileGet::~FileGet()
|
||||
{
|
||||
if (p && p->m_io_thread.joinable()) {
|
||||
p->m_cancel = true;
|
||||
p->m_io_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void FileGet::get()
|
||||
{
|
||||
assert(p);
|
||||
if (p->m_io_thread.joinable()) {
|
||||
// This will stop transfers being done by the thread, if any.
|
||||
// Cancelling takes some time, but should complete soon enough.
|
||||
p->m_cancel = true;
|
||||
p->m_io_thread.join();
|
||||
}
|
||||
p->m_cancel = false;
|
||||
p->m_pause = false;
|
||||
p->m_io_thread = std::thread([this]() {
|
||||
p->get_perform();
|
||||
});
|
||||
}
|
||||
|
||||
void FileGet::cancel()
|
||||
{
|
||||
if(p && p->m_stopped) {
|
||||
if (p->m_io_thread.joinable()) {
|
||||
p->m_cancel = true;
|
||||
p->m_io_thread.join();
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_CANCELED);
|
||||
evt->SetInt(p->m_id);
|
||||
p->m_evt_handler->QueueEvent(evt);
|
||||
}
|
||||
}
|
||||
|
||||
if (p)
|
||||
p->m_cancel = true;
|
||||
|
||||
}
|
||||
|
||||
void FileGet::pause()
|
||||
{
|
||||
if (p) {
|
||||
p->m_pause = true;
|
||||
}
|
||||
}
|
||||
void FileGet::resume()
|
||||
{
|
||||
assert(p);
|
||||
if (p->m_io_thread.joinable()) {
|
||||
// This will stop transfers being done by the thread, if any.
|
||||
// Cancelling takes some time, but should complete soon enough.
|
||||
p->m_cancel = true;
|
||||
p->m_io_thread.join();
|
||||
}
|
||||
p->m_cancel = false;
|
||||
p->m_pause = false;
|
||||
p->m_io_thread = std::thread([this]() {
|
||||
p->get_perform();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
49
src/slic3r/GUI/DownloaderFileGet.hpp
Normal file
@ -0,0 +1,49 @@
|
||||
///|/ Copyright (c) Prusa Research 2023 David Kocík @kocikdav
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#ifndef slic3r_DownloaderFileGet_hpp_
|
||||
#define slic3r_DownloaderFileGet_hpp_
|
||||
|
||||
#include "../Utils/Http.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <wx/event.h>
|
||||
#include <wx/frame.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
class FileGet : public std::enable_shared_from_this<FileGet> {
|
||||
private:
|
||||
struct priv;
|
||||
public:
|
||||
FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler,const boost::filesystem::path& dest_folder);
|
||||
FileGet(FileGet&& other);
|
||||
~FileGet();
|
||||
|
||||
void get();
|
||||
void cancel();
|
||||
void pause();
|
||||
void resume();
|
||||
static std::string escape_url(const std::string& url);
|
||||
static bool is_subdomain(const std::string& url, const std::string& domain);
|
||||
private:
|
||||
std::unique_ptr<priv> p;
|
||||
};
|
||||
// int = DOWNLOAD ID; string = file path
|
||||
wxDECLARE_EVENT(EVT_DWNLDR_FILE_COMPLETE, wxCommandEvent);
|
||||
// int = DOWNLOAD ID; string = error msg
|
||||
wxDECLARE_EVENT(EVT_DWNLDR_FILE_PROGRESS, wxCommandEvent);
|
||||
// int = DOWNLOAD ID; string = progress percent
|
||||
wxDECLARE_EVENT(EVT_DWNLDR_FILE_ERROR, wxCommandEvent);
|
||||
// int = DOWNLOAD ID; string = name
|
||||
wxDECLARE_EVENT(EVT_DWNLDR_FILE_NAME_CHANGE, wxCommandEvent);
|
||||
// int = DOWNLOAD ID;
|
||||
wxDECLARE_EVENT(EVT_DWNLDR_FILE_PAUSED, wxCommandEvent);
|
||||
// int = DOWNLOAD ID;
|
||||
wxDECLARE_EVENT(EVT_DWNLDR_FILE_CANCELED, wxCommandEvent);
|
||||
}
|
||||
}
|
||||
#endif
|
447
src/slic3r/GUI/FileArchiveDialog.cpp
Normal file
@ -0,0 +1,447 @@
|
||||
///|/ Copyright (c) Prusa Research 2023 David Kocík @kocikdav
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#include "FileArchiveDialog.hpp"
|
||||
|
||||
#include "I18N.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "ExtraRenderers.hpp"
|
||||
#include "format.hpp"
|
||||
#include <regex>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
#define BTN_SIZE wxSize(FromDIP(58), FromDIP(24))
|
||||
#define BTN_GAP FromDIP(20)
|
||||
|
||||
ArchiveViewModel::ArchiveViewModel(wxWindow* parent)
|
||||
:m_parent(parent)
|
||||
{}
|
||||
ArchiveViewModel::~ArchiveViewModel()
|
||||
{}
|
||||
|
||||
std::shared_ptr<ArchiveViewNode> ArchiveViewModel::AddFile(std::shared_ptr<ArchiveViewNode> parent, const wxString& name, bool container)
|
||||
{
|
||||
std::shared_ptr<ArchiveViewNode> node = std::make_shared<ArchiveViewNode>(ArchiveViewNode(name));
|
||||
node->set_container(container);
|
||||
|
||||
if (parent.get() != nullptr) {
|
||||
parent->get_children().push_back(node);
|
||||
node->set_parent(parent);
|
||||
parent->set_is_folder(true);
|
||||
} else {
|
||||
m_top_children.emplace_back(node);
|
||||
}
|
||||
|
||||
wxDataViewItem child = wxDataViewItem((void*)node.get());
|
||||
wxDataViewItem parent_item= wxDataViewItem((void*)parent.get());
|
||||
ItemAdded(parent_item, child);
|
||||
|
||||
if (parent)
|
||||
m_ctrl->Expand(parent_item);
|
||||
return node;
|
||||
}
|
||||
|
||||
wxString ArchiveViewModel::GetColumnType(unsigned int col) const
|
||||
{
|
||||
if (col == 0)
|
||||
return "bool";
|
||||
return "string";//"DataViewBitmapText";
|
||||
}
|
||||
|
||||
void ArchiveViewModel::Rescale()
|
||||
{
|
||||
// There should be no pictures rendered
|
||||
}
|
||||
|
||||
void ArchiveViewModel::Delete(const wxDataViewItem& item)
|
||||
{
|
||||
assert(item.IsOk());
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
||||
assert(node->get_parent() != nullptr);
|
||||
for (std::shared_ptr<ArchiveViewNode> child : node->get_children())
|
||||
{
|
||||
Delete(wxDataViewItem((void*)child.get()));
|
||||
}
|
||||
delete [] node;
|
||||
}
|
||||
void ArchiveViewModel::Clear()
|
||||
{
|
||||
}
|
||||
|
||||
wxDataViewItem ArchiveViewModel::GetParent(const wxDataViewItem& item) const
|
||||
{
|
||||
assert(item.IsOk());
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
||||
return wxDataViewItem((void*)node->get_parent().get());
|
||||
}
|
||||
unsigned int ArchiveViewModel::GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const
|
||||
{
|
||||
if (!parent.IsOk()) {
|
||||
for (std::shared_ptr<ArchiveViewNode>child : m_top_children) {
|
||||
array.push_back(wxDataViewItem((void*)child.get()));
|
||||
}
|
||||
return m_top_children.size();
|
||||
}
|
||||
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(parent.GetID());
|
||||
for (std::shared_ptr<ArchiveViewNode> child : node->get_children()) {
|
||||
array.push_back(wxDataViewItem((void*)child.get()));
|
||||
}
|
||||
return node->get_children().size();
|
||||
}
|
||||
|
||||
void ArchiveViewModel::GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const
|
||||
{
|
||||
assert(item.IsOk());
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
||||
if (col == 0) {
|
||||
variant = node->get_toggle();
|
||||
} else {
|
||||
variant = node->get_name();
|
||||
}
|
||||
}
|
||||
|
||||
void ArchiveViewModel::untoggle_folders(const wxDataViewItem& item)
|
||||
{
|
||||
assert(item.IsOk());
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
||||
node->set_toggle(false);
|
||||
if (node->get_parent().get() != nullptr)
|
||||
untoggle_folders(wxDataViewItem((void*)node->get_parent().get()));
|
||||
}
|
||||
|
||||
bool ArchiveViewModel::SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col)
|
||||
{
|
||||
assert(item.IsOk());
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
||||
if (col == 0) {
|
||||
node->set_toggle(variant.GetBool());
|
||||
// if folder recursivelly check all children
|
||||
for (std::shared_ptr<ArchiveViewNode> child : node->get_children()) {
|
||||
SetValue(variant, wxDataViewItem((void*)child.get()), col);
|
||||
}
|
||||
if(!variant.GetBool() && node->get_parent())
|
||||
untoggle_folders(wxDataViewItem((void*)node->get_parent().get()));
|
||||
} else {
|
||||
node->set_name(variant.GetString());
|
||||
}
|
||||
m_parent->Refresh();
|
||||
return true;
|
||||
}
|
||||
bool ArchiveViewModel::IsEnabled(const wxDataViewItem& item, unsigned int col) const
|
||||
{
|
||||
// As of now, all items are always enabled.
|
||||
// Returning false for col 1 would gray out text.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArchiveViewModel::IsContainer(const wxDataViewItem& item) const
|
||||
{
|
||||
if(!item.IsOk())
|
||||
return true;
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
||||
return node->is_container();
|
||||
}
|
||||
|
||||
ArchiveViewCtrl::ArchiveViewCtrl(wxWindow* parent, wxSize size)
|
||||
: wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, size, wxDV_VARIABLE_LINE_HEIGHT | wxDV_ROW_LINES
|
||||
#ifdef _WIN32
|
||||
| wxBORDER_SIMPLE
|
||||
#endif
|
||||
)
|
||||
//, m_em_unit(em_unit(parent))
|
||||
{
|
||||
wxGetApp().UpdateDVCDarkUI(this);
|
||||
|
||||
m_model = new ArchiveViewModel(parent);
|
||||
this->AssociateModel(m_model);
|
||||
m_model->SetAssociatedControl(this);
|
||||
}
|
||||
|
||||
ArchiveViewCtrl::~ArchiveViewCtrl()
|
||||
{
|
||||
if (m_model) {
|
||||
m_model->Clear();
|
||||
m_model->DecRef();
|
||||
}
|
||||
}
|
||||
|
||||
FileArchiveDialog::FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* archive, std::vector<std::pair<boost::filesystem::path, size_t>>& selected_paths_w_size)
|
||||
: DPIDialog(parent_window, wxID_ANY, _(L("Archive preview")), wxDefaultPosition,
|
||||
wxSize(45 * wxGetApp().em_unit(), 40 * wxGetApp().em_unit()),
|
||||
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX)
|
||||
, m_selected_paths_w_size (selected_paths_w_size)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
wxGetApp().UpdateDarkUI(this);
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
#else
|
||||
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
#endif
|
||||
|
||||
int em = em_unit();
|
||||
|
||||
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_avc = new ArchiveViewCtrl(this, wxSize(45 * em, 30 * em));
|
||||
wxDataViewColumn* toggle_column = m_avc->AppendToggleColumn(L"\u2714", 0, wxDATAVIEW_CELL_ACTIVATABLE, 6 * em);
|
||||
m_avc->AppendTextColumn("filename", 1);
|
||||
|
||||
std::vector<std::shared_ptr<ArchiveViewNode>> stack;
|
||||
|
||||
std::function<void(std::vector<std::shared_ptr<ArchiveViewNode> >&, size_t)> reduce_stack = [] (std::vector<std::shared_ptr<ArchiveViewNode>>& stack, size_t size) {
|
||||
if (size == 0) {
|
||||
stack.clear();
|
||||
return;
|
||||
}
|
||||
while (stack.size() > size)
|
||||
stack.pop_back();
|
||||
};
|
||||
// recursively stores whole structure of file onto function stack and synchoronize with stack object.
|
||||
std::function<size_t(const boost::filesystem::path&, std::vector<std::shared_ptr<ArchiveViewNode>>&)> adjust_stack = [&adjust_stack, &reduce_stack, &avc = m_avc](const boost::filesystem::path& const_file, std::vector<std::shared_ptr<ArchiveViewNode>>& stack)->size_t {
|
||||
boost::filesystem::path file(const_file);
|
||||
size_t struct_size = file.has_parent_path() ? adjust_stack(file.parent_path(), stack) : 0;
|
||||
|
||||
if (stack.size() > struct_size && (file.has_extension() || file.filename().string() != stack[struct_size]->get_name()))
|
||||
{
|
||||
reduce_stack(stack, struct_size);
|
||||
}
|
||||
if (!file.has_extension() && stack.size() == struct_size)
|
||||
stack.push_back(avc->get_model()->AddFile((stack.empty() ? std::shared_ptr<ArchiveViewNode>(nullptr) : stack.back()), boost::nowide::widen(file.filename().string()), true)); // filename string to wstring?
|
||||
return struct_size + 1;
|
||||
};
|
||||
|
||||
const std::regex pattern_drop(".*[.](stl|obj|amf|3mf|step|stp)", std::regex::icase);
|
||||
mz_uint num_entries = mz_zip_reader_get_num_files(archive);
|
||||
mz_zip_archive_file_stat stat;
|
||||
std::vector<std::pair<boost::filesystem::path, size_t>> filtered_entries; // second is unzipped size
|
||||
for (mz_uint i = 0; i < num_entries; ++i) {
|
||||
if (mz_zip_reader_file_stat(archive, i, &stat)) {
|
||||
std::string extra(1024, 0);
|
||||
boost::filesystem::path path;
|
||||
size_t extra_size = mz_zip_reader_get_filename_from_extra(archive, i, extra.data(), extra.size());
|
||||
if (extra_size > 0) {
|
||||
path = boost::filesystem::path(extra.substr(0, extra_size));
|
||||
} else {
|
||||
wxString wname = boost::nowide::widen(stat.m_filename);
|
||||
std::string name = boost::nowide::narrow(wname);
|
||||
path = boost::filesystem::path(name);
|
||||
}
|
||||
assert(!path.empty());
|
||||
if (!path.has_extension())
|
||||
continue;
|
||||
// filter out MACOS specific hidden files
|
||||
if (boost::algorithm::starts_with(path.string(), "__MACOSX"))
|
||||
continue;
|
||||
filtered_entries.emplace_back(std::move(path), stat.m_uncomp_size);
|
||||
}
|
||||
}
|
||||
// sorting files will help adjust_stack function to not create multiple same folders
|
||||
std::sort(filtered_entries.begin(), filtered_entries.end(), [](const std::pair<boost::filesystem::path, size_t>& p1, const std::pair<boost::filesystem::path, size_t>& p2){ return p1.first.string() < p2.first.string(); });
|
||||
size_t entry_count = 0;
|
||||
size_t depth = 1;
|
||||
for (const auto& entry : filtered_entries)
|
||||
{
|
||||
const boost::filesystem::path& path = entry.first;
|
||||
std::shared_ptr<ArchiveViewNode> parent(nullptr);
|
||||
|
||||
depth = std::max(depth, adjust_stack(path, stack));
|
||||
if (!stack.empty())
|
||||
parent = stack.back();
|
||||
if (std::regex_match(path.extension().string(), pattern_drop)) { // this leaves out non-compatible files
|
||||
std::shared_ptr<ArchiveViewNode> new_node = m_avc->get_model()->AddFile(parent, boost::nowide::widen(path.filename().string()), false);
|
||||
new_node->set_fullpath(/*std::move(path)*/path); // filename string to wstring?
|
||||
new_node->set_size(entry.second);
|
||||
entry_count++;
|
||||
}
|
||||
}
|
||||
if (entry_count == 1)
|
||||
on_all_button();
|
||||
|
||||
toggle_column->SetWidth((4 + depth) * em);
|
||||
|
||||
wxBoxSizer* btn_sizer = create_btn_sizer();
|
||||
|
||||
topSizer->Add(m_avc, 1, wxEXPAND | wxALL, 10);
|
||||
topSizer->Add(btn_sizer, 0, wxEXPAND | wxALL, 10);
|
||||
this->SetSizer(topSizer);
|
||||
SetMinSize(wxSize(40 * em, 30 * em));
|
||||
|
||||
for (auto btn : m_button_list)
|
||||
wxGetApp().UpdateDarkUI(btn);
|
||||
}
|
||||
|
||||
void FileArchiveDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
{
|
||||
int em = em_unit();
|
||||
BOOST_LOG_TRIVIAL(error) << "on_dpi_changed";
|
||||
|
||||
for (auto btn : m_button_list) {
|
||||
btn->SetMinSize(BTN_SIZE);
|
||||
btn->SetCornerRadius(FromDIP(12));
|
||||
}
|
||||
|
||||
const wxSize& size = wxSize(45 * em, 40 * em);
|
||||
SetSize(size);
|
||||
//m_tree->Rescale(em);
|
||||
|
||||
Fit();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void FileArchiveDialog::on_open_button()
|
||||
{
|
||||
wxDataViewItemArray top_items;
|
||||
m_avc->get_model()->GetChildren(wxDataViewItem(nullptr), top_items);
|
||||
|
||||
std::function<void(ArchiveViewNode*)> deep_fill = [&paths = m_selected_paths_w_size, &deep_fill](ArchiveViewNode* node){
|
||||
if (node == nullptr)
|
||||
return;
|
||||
if (node->get_children().empty()) {
|
||||
if (node->get_toggle())
|
||||
paths.emplace_back(node->get_fullpath(), node->get_size());
|
||||
} else {
|
||||
for (std::shared_ptr<ArchiveViewNode> child : node->get_children())
|
||||
deep_fill(child.get());
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& item : top_items)
|
||||
{
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
||||
deep_fill(node);
|
||||
}
|
||||
this->EndModal(wxID_OK);
|
||||
}
|
||||
|
||||
void FileArchiveDialog::on_all_button()
|
||||
{
|
||||
|
||||
wxDataViewItemArray top_items;
|
||||
m_avc->get_model()->GetChildren(wxDataViewItem(nullptr), top_items);
|
||||
|
||||
std::function<void(ArchiveViewNode*)> deep_fill = [&deep_fill](ArchiveViewNode* node) {
|
||||
if (node == nullptr)
|
||||
return;
|
||||
node->set_toggle(true);
|
||||
if (!node->get_children().empty()) {
|
||||
for (std::shared_ptr<ArchiveViewNode> child : node->get_children())
|
||||
deep_fill(child.get());
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& item : top_items)
|
||||
{
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
||||
deep_fill(node);
|
||||
// Fix for linux, where Refresh or Update wont help to redraw toggle checkboxes.
|
||||
// It should be enough to call ValueChanged for top items.
|
||||
m_avc->get_model()->ValueChanged(item, 0);
|
||||
}
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void FileArchiveDialog::on_none_button()
|
||||
{
|
||||
wxDataViewItemArray top_items;
|
||||
m_avc->get_model()->GetChildren(wxDataViewItem(nullptr), top_items);
|
||||
|
||||
std::function<void(ArchiveViewNode*)> deep_fill = [&deep_fill](ArchiveViewNode* node) {
|
||||
if (node == nullptr)
|
||||
return;
|
||||
node->set_toggle(false);
|
||||
if (!node->get_children().empty()) {
|
||||
for (std::shared_ptr<ArchiveViewNode> child : node->get_children())
|
||||
deep_fill(child.get());
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& item : top_items)
|
||||
{
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
||||
deep_fill(node);
|
||||
// Fix for linux, where Refresh or Update wont help to redraw toggle checkboxes.
|
||||
// It should be enough to call ValueChanged for top items.
|
||||
m_avc->get_model()->ValueChanged(item, 0);
|
||||
}
|
||||
|
||||
this->Refresh();
|
||||
}
|
||||
|
||||
//Orca: Apply buttons style
|
||||
wxBoxSizer* FileArchiveDialog::create_btn_sizer()
|
||||
{
|
||||
auto btn_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
auto apply_highlighted_btn_colors = [](Button* btn) {
|
||||
btn->SetBackgroundColor(StateColor(std::pair<wxColour, int>(wxColour(0, 137, 123), StateColor::Pressed),
|
||||
std::pair<wxColour, int>(wxColour(38, 166, 154), StateColor::Hovered),
|
||||
std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal)));
|
||||
|
||||
btn->SetBorderColor(StateColor(std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal)));
|
||||
|
||||
btn->SetTextColor(StateColor(std::pair<wxColour, int>(wxColour(255, 255, 254), StateColor::Normal)));
|
||||
};
|
||||
|
||||
auto apply_std_btn_colors = [](Button* btn) {
|
||||
btn->SetBackgroundColor(StateColor(std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
|
||||
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
|
||||
std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Normal)));
|
||||
|
||||
btn->SetBorderColor(StateColor(std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Normal)));
|
||||
|
||||
btn->SetTextColor(StateColor(std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Normal)));
|
||||
};
|
||||
|
||||
auto style_btn = [this, apply_highlighted_btn_colors, apply_std_btn_colors](Button* btn, bool highlight) {
|
||||
btn->SetMinSize(BTN_SIZE);
|
||||
btn->SetCornerRadius(FromDIP(12));
|
||||
if (highlight)
|
||||
apply_highlighted_btn_colors(btn);
|
||||
else
|
||||
apply_std_btn_colors(btn);
|
||||
};
|
||||
|
||||
Button* all_btn = new Button(this, _L("All"));
|
||||
style_btn(all_btn, false);
|
||||
all_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_all_button(); });
|
||||
btn_sizer->Add(all_btn, 0, wxALIGN_CENTER_VERTICAL);
|
||||
m_button_list.push_back(all_btn);
|
||||
|
||||
Button* none_btn = new Button(this, _L("None"));
|
||||
style_btn(none_btn, false);
|
||||
none_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_none_button(); });
|
||||
btn_sizer->Add(none_btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, BTN_GAP);
|
||||
m_button_list.push_back(none_btn);
|
||||
|
||||
btn_sizer->AddStretchSpacer();
|
||||
|
||||
Button* open_btn = new Button(this, _L("Open"));
|
||||
style_btn(open_btn, true);
|
||||
open_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_open_button(); });
|
||||
open_btn->SetFocus();
|
||||
open_btn->SetId(wxID_OK);
|
||||
btn_sizer->Add(open_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, BTN_GAP);
|
||||
m_button_list.push_back(open_btn);
|
||||
|
||||
Button* cancel_btn = new Button(this, _L("Cancel"));
|
||||
style_btn(cancel_btn, false);
|
||||
cancel_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { this->EndModal(wxID_CANCEL); });
|
||||
cancel_btn->SetId(wxID_CANCEL);
|
||||
btn_sizer->Add(cancel_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, BTN_GAP);
|
||||
m_button_list.push_back(cancel_btn);
|
||||
|
||||
return btn_sizer;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
131
src/slic3r/GUI/FileArchiveDialog.hpp
Normal file
@ -0,0 +1,131 @@
|
||||
///|/ Copyright (c) Prusa Research 2023 David Kocík @kocikdav
|
||||
///|/
|
||||
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
|
||||
///|/
|
||||
#ifndef slic3r_GUI_FileArchiveDialog_hpp_
|
||||
#define slic3r_GUI_FileArchiveDialog_hpp_
|
||||
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "libslic3r/miniz_extension.hpp"
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <wx/wx.h>
|
||||
#include <wx/dataview.h>
|
||||
#include <slic3r/GUI/Widgets/Button.hpp>
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class ArchiveViewCtrl;
|
||||
|
||||
class ArchiveViewNode
|
||||
{
|
||||
public:
|
||||
ArchiveViewNode(const wxString& name) : m_name(name) {}
|
||||
|
||||
std::vector<std::shared_ptr<ArchiveViewNode>>& get_children() { return m_children; }
|
||||
void set_parent(std::shared_ptr<ArchiveViewNode> parent) { m_parent = parent; }
|
||||
// On Linux, get_parent cannot just return size of m_children. ItemAdded would than crash.
|
||||
std::shared_ptr<ArchiveViewNode> get_parent() const { return m_parent; }
|
||||
bool is_container() const { return m_container; }
|
||||
void set_container(bool is_container) { m_container = is_container; }
|
||||
wxString get_name() const { return m_name; }
|
||||
void set_name(const wxString& name) { m_name = name; }
|
||||
bool get_toggle() const { return m_toggle; }
|
||||
void set_toggle(bool toggle) { m_toggle = toggle; }
|
||||
bool get_is_folder() const { return m_folder; }
|
||||
void set_is_folder(bool is_folder) { m_folder = is_folder; }
|
||||
void set_fullpath(boost::filesystem::path path) { m_fullpath = path; }
|
||||
boost::filesystem::path get_fullpath() const { return m_fullpath; }
|
||||
void set_size(size_t size) { m_size = size; }
|
||||
size_t get_size() const { return m_size; }
|
||||
|
||||
private:
|
||||
wxString m_name;
|
||||
std::shared_ptr<ArchiveViewNode> m_parent { nullptr };
|
||||
std::vector<std::shared_ptr<ArchiveViewNode>> m_children;
|
||||
|
||||
bool m_toggle { false };
|
||||
bool m_folder { false };
|
||||
boost::filesystem::path m_fullpath;
|
||||
bool m_container { false };
|
||||
size_t m_size { 0 };
|
||||
};
|
||||
|
||||
class ArchiveViewModel : public wxDataViewModel
|
||||
{
|
||||
public:
|
||||
ArchiveViewModel(wxWindow* parent);
|
||||
~ArchiveViewModel();
|
||||
|
||||
/* wxDataViewItem AddFolder(wxDataViewItem& parent, wxString name);
|
||||
wxDataViewItem AddFile(wxDataViewItem& parent, wxString name);*/
|
||||
|
||||
std::shared_ptr<ArchiveViewNode> AddFile(std::shared_ptr<ArchiveViewNode> parent,const wxString& name, bool container);
|
||||
|
||||
wxString GetColumnType(unsigned int col) const override;
|
||||
unsigned int GetColumnCount() const override { return 2; }
|
||||
|
||||
void Rescale();
|
||||
void Delete(const wxDataViewItem& item);
|
||||
void Clear();
|
||||
|
||||
wxDataViewItem GetParent(const wxDataViewItem& item) const override;
|
||||
unsigned int GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const override;
|
||||
|
||||
void SetAssociatedControl(ArchiveViewCtrl* ctrl) { m_ctrl = ctrl; }
|
||||
|
||||
void GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const override;
|
||||
bool SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) override;
|
||||
|
||||
void untoggle_folders(const wxDataViewItem& item);
|
||||
|
||||
bool IsEnabled(const wxDataViewItem& item, unsigned int col) const override;
|
||||
bool IsContainer(const wxDataViewItem& item) const override;
|
||||
// Is the container just a header or an item with all columns
|
||||
// In our case it is an item with all columns
|
||||
bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; }
|
||||
|
||||
protected:
|
||||
wxWindow* m_parent { nullptr };
|
||||
ArchiveViewCtrl* m_ctrl { nullptr };
|
||||
std::vector<std::shared_ptr<ArchiveViewNode>> m_top_children;
|
||||
};
|
||||
|
||||
class ArchiveViewCtrl : public wxDataViewCtrl
|
||||
{
|
||||
public:
|
||||
ArchiveViewCtrl(wxWindow* parent, wxSize size);
|
||||
~ArchiveViewCtrl();
|
||||
|
||||
ArchiveViewModel* get_model() const {return m_model; }
|
||||
protected:
|
||||
ArchiveViewModel* m_model;
|
||||
};
|
||||
|
||||
|
||||
class FileArchiveDialog : public DPIDialog
|
||||
{
|
||||
public:
|
||||
FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* archive, std::vector<std::pair<boost::filesystem::path, size_t>>& selected_paths_w_size);
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
|
||||
void on_open_button();
|
||||
void on_all_button();
|
||||
void on_none_button();
|
||||
|
||||
wxBoxSizer* create_btn_sizer();
|
||||
|
||||
// chosen files are written into this vector and returned to caller via reference.
|
||||
// path in archive and decompressed size. The size can be used to distinguish between files with same path.
|
||||
std::vector<std::pair<boost::filesystem::path,size_t>>& m_selected_paths_w_size;
|
||||
ArchiveViewCtrl* m_avc;
|
||||
std::vector<Button*> m_button_list;
|
||||
};
|
||||
|
||||
} // namespace GU
|
||||
} // namespace Slic3r
|
||||
#endif // slic3r_GUI_FileArchiveDialog_hpp_
|
@ -7,6 +7,7 @@
|
||||
#include "slic3r/GUI/TaskManager.hpp"
|
||||
#include "format.hpp"
|
||||
#include "libslic3r_version.h"
|
||||
#include "Downloader.hpp"
|
||||
|
||||
// Localization headers: include libslic3r version first so everything in this file
|
||||
// uses the slic3r/GUI version (the macros will take precedence over the functions).
|
||||
@ -517,6 +518,7 @@ static const FileWildcards file_wildcards_by_type[FT_SIZE] = {
|
||||
/* FT_MODEL */
|
||||
{"Supported files"sv, {".3mf"sv, ".stl"sv, ".oltp"sv, ".stp"sv, ".step"sv, ".svg"sv, ".amf"sv, ".obj"sv}},
|
||||
#endif
|
||||
/* FT_ZIP */ { "ZIP files"sv, { ".zip"sv } },
|
||||
/* FT_PROJECT */ { "Project files"sv, { ".3mf"sv} },
|
||||
/* FT_GALLERY */ { "Known files"sv, { ".stl"sv, ".obj"sv } },
|
||||
|
||||
@ -816,18 +818,25 @@ void GUI_App::post_init()
|
||||
|
||||
|
||||
if (this->init_params->input_files.size() == 1 &&
|
||||
boost::starts_with(this->init_params->input_files.front(), "orcaslicer://open")) {
|
||||
auto input_str_arr = split_str(this->init_params->input_files.front(), "orcaslicer://open/?file=");
|
||||
(boost::starts_with(this->init_params->input_files.front(), "orcaslicer://open") ||
|
||||
boost::starts_with(this->init_params->input_files.front(), "prusaslicer://open"))) {
|
||||
|
||||
std::string download_origin_url;
|
||||
for (auto input_str:input_str_arr) {
|
||||
if (!input_str.empty()) download_origin_url = input_str;
|
||||
}
|
||||
if (boost::starts_with(this->init_params->input_files.front(), "prusaslicer://open")) {
|
||||
switch_to_3d = true;
|
||||
start_download(this->init_params->input_files.front());
|
||||
} else if (vector<string> input_str_arr = split_str(this->init_params->input_files.front(), "orcaslicer://open/?file="); input_str_arr.size() > 1) {
|
||||
std::string download_origin_url;
|
||||
for (auto input_str : input_str_arr) {
|
||||
if (!input_str.empty())
|
||||
download_origin_url = input_str;
|
||||
}
|
||||
|
||||
std::string download_file_url = url_decode(download_origin_url);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << download_file_url;
|
||||
if (!download_file_url.empty() && ( boost::starts_with(download_file_url, "http://") || boost::starts_with(download_file_url, "https://")) ) {
|
||||
request_model_download(download_origin_url);
|
||||
std::string download_file_url = url_decode(download_origin_url);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << download_file_url;
|
||||
if (!download_file_url.empty() &&
|
||||
(boost::starts_with(download_file_url, "http://") || boost::starts_with(download_file_url, "https://"))) {
|
||||
request_model_download(download_file_url);
|
||||
}
|
||||
}
|
||||
m_open_method = "makerworld";
|
||||
}
|
||||
@ -1089,6 +1098,7 @@ GUI_App::GUI_App()
|
||||
, m_em_unit(10)
|
||||
, m_imgui(new ImGuiWrapper())
|
||||
, m_removable_drive_manager(std::make_unique<RemovableDriveManager>())
|
||||
, m_downloader(std::make_unique<Downloader>())
|
||||
, m_other_instance_message_handler(std::make_unique<OtherInstanceMessageHandler>())
|
||||
{
|
||||
//app config initializes early becasuse it is used in instance checking in OrcaSlicer.cpp
|
||||
@ -2361,6 +2371,10 @@ bool GUI_App::on_init_inner()
|
||||
associate_files(L"step");
|
||||
associate_files(L"stp");
|
||||
}
|
||||
// Orca: add PS url functionality
|
||||
if (app_config->get_bool("ps_url_registered")) {
|
||||
associate_url(L"prusaslicer");
|
||||
}
|
||||
if (app_config->get("associate_gcode") == "true")
|
||||
associate_files(L"gcode");
|
||||
#endif // __WXMSW__
|
||||
@ -3576,6 +3590,17 @@ void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const
|
||||
dialog.GetPaths(input_files);
|
||||
}
|
||||
|
||||
void GUI_App::import_zip(wxWindow* parent, wxString& input_file) const
|
||||
{
|
||||
wxFileDialog dialog(parent ? parent : GetTopWindow(),
|
||||
_L("Choose ZIP file") + ":",
|
||||
from_u8(app_config->get_last_dir()), "",
|
||||
file_wildcards(FT_ZIP), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
|
||||
if (dialog.ShowModal() == wxID_OK)
|
||||
input_file = dialog.GetPath();
|
||||
}
|
||||
|
||||
void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const
|
||||
{
|
||||
input_file.Clear();
|
||||
@ -5508,6 +5533,10 @@ void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_
|
||||
associate_files(L"step");
|
||||
associate_files(L"stp");
|
||||
}
|
||||
// Orca: add PS url functionality
|
||||
if (app_config->get_bool("ps_url_registered")) {
|
||||
associate_url(L"prusaslicer");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (app_config->get("associate_gcode") == "true")
|
||||
@ -5861,7 +5890,10 @@ void GUI_App::MacOpenURL(const wxString& url)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << "get mac url " << url;
|
||||
|
||||
if (!url.empty() && boost::starts_with(url, "orcasliceropen://")) {
|
||||
if (url.empty())
|
||||
return;
|
||||
|
||||
if (boost::starts_with(url, "orcasliceropen://")) {
|
||||
auto input_str_arr = split_str(url.ToStdString(), "orcasliceropen://");
|
||||
|
||||
std::string download_origin_url;
|
||||
@ -5880,7 +5912,8 @@ void GUI_App::MacOpenURL(const wxString& url)
|
||||
m_download_file_url = download_file_url;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (boost::starts_with(url, "prusasliceropen://"))
|
||||
start_download(boost::nowide::narrow(url));
|
||||
}
|
||||
|
||||
// wxWidgets override to get an event on open files.
|
||||
@ -6004,6 +6037,11 @@ Model& GUI_App::model()
|
||||
return plater_->model();
|
||||
}
|
||||
|
||||
Downloader* GUI_App::downloader()
|
||||
{
|
||||
return m_downloader.get();
|
||||
}
|
||||
|
||||
void GUI_App::load_url(wxString url)
|
||||
{
|
||||
if (mainframe)
|
||||
@ -6593,9 +6631,60 @@ void GUI_App::disassociate_files(std::wstring extend)
|
||||
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
|
||||
}
|
||||
|
||||
void GUI_App::associate_url(std::wstring url_prefix)
|
||||
{
|
||||
// Registry key creation for "prusaslicer://" URL
|
||||
|
||||
boost::filesystem::path binary_path(boost::filesystem::canonical(boost::dll::program_location()));
|
||||
// the path to binary needs to be correctly saved in string with respect to localized characters
|
||||
wxString wbinary = wxString::FromUTF8(binary_path.string());
|
||||
std::string binary_string = (boost::format("%1%") % wbinary).str();
|
||||
BOOST_LOG_TRIVIAL(info) << "Downloader registration: Path of binary: " << binary_string;
|
||||
|
||||
std::string key_string = "\"" + binary_string + "\" \"%1\"";
|
||||
|
||||
wxRegKey key_first(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix);
|
||||
wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix + "\\shell\\open\\command");
|
||||
if (!key_first.Exists()) {
|
||||
key_first.Create(false);
|
||||
}
|
||||
key_first.SetValue("URL Protocol", "");
|
||||
|
||||
if (!key_full.Exists()) {
|
||||
key_full.Create(false);
|
||||
}
|
||||
key_full = key_string;
|
||||
}
|
||||
|
||||
void GUI_App::disassociate_url(std::wstring url_prefix)
|
||||
{
|
||||
wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix + "\\shell\\open\\command");
|
||||
if (!key_full.Exists()) {
|
||||
return;
|
||||
}
|
||||
key_full = "";
|
||||
}
|
||||
|
||||
#endif // __WXMSW__
|
||||
|
||||
void GUI_App::start_download(std::string url)
|
||||
{
|
||||
if (!plater_) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Could not start URL download: plater is nullptr.";
|
||||
return;
|
||||
}
|
||||
//lets always init so if the download dest folder was changed, new dest is used
|
||||
boost::filesystem::path dest_folder(app_config->get("download_path"));
|
||||
if (dest_folder.empty() || !boost::filesystem::is_directory(dest_folder)) {
|
||||
std::string msg = _u8L("Could not start URL download. Destination folder is not set. Please choose destination folder in Configuration Wizard.");
|
||||
BOOST_LOG_TRIVIAL(error) << msg;
|
||||
show_error(nullptr, msg);
|
||||
return;
|
||||
}
|
||||
m_downloader->init(dest_folder);
|
||||
m_downloader->start_download(url);
|
||||
}
|
||||
|
||||
bool is_support_filament(int extruder_id)
|
||||
{
|
||||
auto &filament_presets = Slic3r::GUI::wxGetApp().preset_bundle->filament_presets;
|
||||
|
@ -74,6 +74,7 @@ class ObjectLayers;
|
||||
class Plater;
|
||||
class ParamsPanel;
|
||||
class NotificationManager;
|
||||
class Downloader;
|
||||
struct GUI_InitParams;
|
||||
class ParamsDialog;
|
||||
class HMSQuery;
|
||||
@ -90,6 +91,7 @@ enum FileType
|
||||
FT_3MF,
|
||||
FT_GCODE,
|
||||
FT_MODEL,
|
||||
FT_ZIP,
|
||||
FT_PROJECT,
|
||||
FT_GALLERY,
|
||||
|
||||
@ -275,6 +277,8 @@ private:
|
||||
std::string m_instance_hash_string;
|
||||
size_t m_instance_hash_int;
|
||||
|
||||
std::unique_ptr<Downloader> m_downloader;
|
||||
|
||||
//BBS
|
||||
bool m_is_closing {false};
|
||||
Slic3r::DeviceManager* m_device_manager { nullptr };
|
||||
@ -423,6 +427,7 @@ private:
|
||||
void keyboard_shortcuts();
|
||||
void load_project(wxWindow *parent, wxString& input_file) const;
|
||||
void import_model(wxWindow *parent, wxArrayString& input_files) const;
|
||||
void import_zip(wxWindow* parent, wxString& input_file) const;
|
||||
void load_gcode(wxWindow* parent, wxString& input_file) const;
|
||||
|
||||
wxString transition_tridid(int trid_id);
|
||||
@ -559,6 +564,7 @@ private:
|
||||
ParamsDialog* params_dialog();
|
||||
Model& model();
|
||||
NotificationManager * notification_manager();
|
||||
Downloader* downloader();
|
||||
|
||||
|
||||
std::string m_mall_model_download_url;
|
||||
@ -644,7 +650,13 @@ private:
|
||||
// extend is stl/3mf/gcode/step etc
|
||||
void associate_files(std::wstring extend);
|
||||
void disassociate_files(std::wstring extend);
|
||||
void associate_url(std::wstring url_prefix);
|
||||
void disassociate_url(std::wstring url_prefix);
|
||||
#endif // __WXMSW__
|
||||
|
||||
// URL download - PrusaSlicer gets system call to open prusaslicer:// URL which should contain address of download
|
||||
void start_download(std::string url);
|
||||
|
||||
std::string get_plugin_url(std::string name, std::string country_code);
|
||||
int download_plugin(std::string name, std::string package_name, InstallProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr);
|
||||
int install_plugin(std::string name, std::string package_name, InstallProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr);
|
||||
|
@ -136,6 +136,19 @@ static const std::map<const wchar_t, std::string> font_icons_large = {
|
||||
{ImGui::PrevArrowBtnIcon, "notification_arrow_left" },
|
||||
{ImGui::NextArrowBtnIcon, "notification_arrow_right" },
|
||||
{ImGui::CompleteIcon, "notification_slicing_complete" },
|
||||
|
||||
{ImGui::PlayButton, "notification_play" },
|
||||
{ImGui::PlayDarkButton, "notification_play_dark" },
|
||||
{ImGui::PlayHoverButton, "notification_play_hover" },
|
||||
{ImGui::PlayHoverDarkButton, "notification_play_hover_dark" },
|
||||
{ImGui::PauseButton, "notification_pause" },
|
||||
{ImGui::PauseDarkButton, "notification_pause_dark" },
|
||||
{ImGui::PauseHoverButton, "notification_pause_hover" },
|
||||
{ImGui::PauseHoverDarkButton, "notification_pause_hover_dark" },
|
||||
{ImGui::OpenButton, "notification_open" },
|
||||
{ImGui::OpenDarkButton, "notification_open_dark" },
|
||||
{ImGui::OpenHoverButton, "notification_open_hover" },
|
||||
{ImGui::OpenHoverDarkButton, "notification_open_hover_dark" },
|
||||
};
|
||||
|
||||
static const std::map<const wchar_t, std::string> font_icons_extra_large = {
|
||||
|
@ -588,15 +588,9 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
||||
if (evt.CmdDown() && evt.GetKeyCode() == 'P')
|
||||
#endif
|
||||
{
|
||||
PreferencesDialog dlg(this);
|
||||
dlg.ShowModal();
|
||||
// Orca: Use GUI_App::open_preferences instead of direct call so windows associations are updated on exit
|
||||
wxGetApp().open_preferences();
|
||||
plater()->get_current_canvas3D()->force_set_focus();
|
||||
#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
|
||||
if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed())
|
||||
#else
|
||||
if (dlg.seq_top_layer_only_changed())
|
||||
#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
|
||||
plater()->refresh_print();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2314,6 +2308,9 @@ void MainFrame::init_menubar_as_editor()
|
||||
[this](wxCommandEvent&) { if (m_plater) { m_plater->add_model(); } }, "", nullptr,
|
||||
[this](){return can_add_models(); }, this);
|
||||
#endif
|
||||
append_menu_item(import_menu, wxID_ANY, _L("Import Zip Archive") + dots, _L("Load models contained within a zip archive"),
|
||||
[this](wxCommandEvent&) { if (m_plater) m_plater->import_zip_archive(); }, "menu_import", nullptr,
|
||||
[this]() { return can_add_models(); });
|
||||
append_menu_item(import_menu, wxID_ANY, _L("Import Configs") + dots /*+ "\tCtrl+I"*/, _L("Load configs"),
|
||||
[this](wxCommandEvent&) { load_config_file(); }, "menu_import", nullptr,
|
||||
[this](){return true; }, this);
|
||||
@ -2787,15 +2784,9 @@ void MainFrame::init_menubar_as_editor()
|
||||
append_menu_item(
|
||||
m_topbar->GetTopMenu(), wxID_ANY, _L("Preferences") + "\t" + ctrl + "P", "",
|
||||
[this](wxCommandEvent &) {
|
||||
PreferencesDialog dlg(this);
|
||||
dlg.ShowModal();
|
||||
// Orca: Use GUI_App::open_preferences instead of direct call so windows associations are updated on exit
|
||||
wxGetApp().open_preferences();
|
||||
plater()->get_current_canvas3D()->force_set_focus();
|
||||
#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
|
||||
if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed())
|
||||
#else
|
||||
if (dlg.seq_top_layer_only_changed())
|
||||
#endif
|
||||
plater()->refresh_print();
|
||||
},
|
||||
"", nullptr, []() { return true; }, this);
|
||||
//m_topbar->AddDropDownMenuItem(preference_item);
|
||||
|
@ -1283,6 +1283,219 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty
|
||||
update(data);
|
||||
}
|
||||
|
||||
//------URLDownloadNotification----------------
|
||||
|
||||
void NotificationManager::URLDownloadNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
if (m_percentage < 0.f || m_percentage >= 1.f) {
|
||||
render_close_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
if (m_percentage >= 1.f)
|
||||
render_open_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
} else
|
||||
render_pause_cancel_buttons_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
}
|
||||
void NotificationManager::URLDownloadNotification::render_close_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
ImVec2 win_size(win_size_x, win_size_y);
|
||||
ImVec2 win_pos(win_pos_x, win_pos_y);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||
|
||||
|
||||
std::string button_text;
|
||||
// Orca: Change based on dark mode
|
||||
button_text = m_is_dark ? ImGui::CloseNotifDarkButton : ImGui::CloseNotifButton;
|
||||
|
||||
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y),
|
||||
ImVec2(win_pos.x, win_pos.y + win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)),
|
||||
true))
|
||||
{
|
||||
// Orca: Change based on dark mode
|
||||
button_text = m_is_dark ? ImGui::CloseNotifHoverDarkButton : ImGui::CloseNotifHoverButton;
|
||||
}
|
||||
ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
|
||||
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
|
||||
ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f);
|
||||
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
|
||||
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
//invisible large button
|
||||
ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f);
|
||||
ImGui::SetCursorPosY(0);
|
||||
if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)))
|
||||
{
|
||||
close();
|
||||
}
|
||||
ImGui::PopStyleColor(5);
|
||||
|
||||
}
|
||||
|
||||
void NotificationManager::URLDownloadNotification::render_pause_cancel_buttons_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
|
||||
render_cancel_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
render_pause_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
}
|
||||
void NotificationManager::URLDownloadNotification::render_pause_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
ImVec2 win_size(win_size_x, win_size_y);
|
||||
ImVec2 win_pos(win_pos_x, win_pos_y);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||
|
||||
std::wstring button_text;
|
||||
// Orca: Change based on dark mode
|
||||
button_text = m_is_dark ? (m_download_paused ? ImGui::PlayDarkButton : ImGui::PauseDarkButton) : (m_download_paused ? ImGui::PlayButton : ImGui::PauseButton);
|
||||
|
||||
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - m_line_height * 5.f, win_pos.y),
|
||||
ImVec2(win_pos.x - m_line_height * 2.5f, win_pos.y + win_size.y),
|
||||
true))
|
||||
{
|
||||
// Orca: Change based on dark mode
|
||||
button_text = m_is_dark ? (m_download_paused ? ImGui::PlayHoverDarkButton : ImGui::PauseHoverDarkButton) : (m_download_paused ? ImGui::PlayHoverButton : ImGui::PauseHoverButton);
|
||||
}
|
||||
|
||||
ImVec2 button_pic_size = ImGui::CalcTextSize(boost::nowide::narrow(button_text).c_str());
|
||||
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
|
||||
ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f);
|
||||
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
|
||||
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
|
||||
{
|
||||
trigger_user_action_callback(m_download_paused ? DownloaderUserAction::DownloadUserContinued : DownloaderUserAction::DownloadUserPaused);
|
||||
}
|
||||
|
||||
//invisible large button
|
||||
ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f);
|
||||
ImGui::SetCursorPosY(0);
|
||||
if (imgui.button(" ", m_line_height * 2.f, win_size.y))
|
||||
{
|
||||
trigger_user_action_callback(m_download_paused ? DownloaderUserAction::DownloadUserContinued : DownloaderUserAction::DownloadUserPaused);
|
||||
}
|
||||
ImGui::PopStyleColor(5);
|
||||
}
|
||||
|
||||
void NotificationManager::URLDownloadNotification::render_open_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
ImVec2 win_size(win_size_x, win_size_y);
|
||||
ImVec2 win_pos(win_pos_x, win_pos_y);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||
|
||||
std::wstring button_text;
|
||||
// Orca: Change based on dark mode
|
||||
button_text = m_is_dark ? ImGui::OpenDarkButton : ImGui::OpenButton;
|
||||
|
||||
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - m_line_height * 5.f, win_pos.y),
|
||||
ImVec2(win_pos.x - m_line_height * 2.5f, win_pos.y + win_size.y),
|
||||
true))
|
||||
{
|
||||
// Orca: Change based on dark mode
|
||||
button_text = m_is_dark ? ImGui::OpenHoverDarkButton : ImGui::OpenHoverButton;
|
||||
}
|
||||
|
||||
ImVec2 button_pic_size = ImGui::CalcTextSize(boost::nowide::narrow(button_text).c_str());
|
||||
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
|
||||
ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f);
|
||||
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
|
||||
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
|
||||
{
|
||||
trigger_user_action_callback(DownloaderUserAction::DownloadUserOpenedFolder);
|
||||
}
|
||||
|
||||
//invisible large button
|
||||
ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f);
|
||||
ImGui::SetCursorPosY(0);
|
||||
if (imgui.button(" ", m_line_height * 2.f, win_size.y))
|
||||
{
|
||||
trigger_user_action_callback(DownloaderUserAction::DownloadUserOpenedFolder);
|
||||
}
|
||||
ImGui::PopStyleColor(5);
|
||||
}
|
||||
|
||||
void NotificationManager::URLDownloadNotification::render_cancel_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
ImVec2 win_size(win_size_x, win_size_y);
|
||||
ImVec2 win_pos(win_pos_x, win_pos_y);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||
|
||||
|
||||
std::string button_text;
|
||||
button_text = ImGui::CancelButton;
|
||||
|
||||
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y),
|
||||
ImVec2(win_pos.x, win_pos.y + win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)),
|
||||
true))
|
||||
{
|
||||
button_text = ImGui::CancelHoverButton;
|
||||
}
|
||||
ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
|
||||
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
|
||||
ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f);
|
||||
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
|
||||
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
|
||||
{
|
||||
trigger_user_action_callback(DownloaderUserAction::DownloadUserCanceled);
|
||||
}
|
||||
|
||||
//invisible large button
|
||||
ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f);
|
||||
ImGui::SetCursorPosY(0);
|
||||
if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)))
|
||||
{
|
||||
trigger_user_action_callback(DownloaderUserAction::DownloadUserCanceled);
|
||||
}
|
||||
ImGui::PopStyleColor(5);
|
||||
|
||||
}
|
||||
|
||||
void NotificationManager::URLDownloadNotification::trigger_user_action_callback(DownloaderUserAction action)
|
||||
{
|
||||
if (m_user_action_callback) {
|
||||
if (m_user_action_callback(action, m_download_id)) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void NotificationManager::URLDownloadNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
std::string text;
|
||||
if (m_percentage < 0.f) {
|
||||
text = _u8L("ERROR") + ": " + m_error_message;
|
||||
} else if (m_percentage >= 1.f) {
|
||||
text = _u8L("COMPLETED");
|
||||
} else {
|
||||
std::stringstream stream;
|
||||
stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "%";
|
||||
text = stream.str();
|
||||
}
|
||||
ImGui::SetCursorPosX(m_left_indentation);
|
||||
ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4));
|
||||
imgui.text(text.c_str());
|
||||
}
|
||||
|
||||
void NotificationManager::URLDownloadNotification::count_spaces()
|
||||
{
|
||||
ProgressBarNotification::count_spaces();
|
||||
m_window_width_offset = m_line_height * 6;
|
||||
}
|
||||
|
||||
//------PrintHostUploadNotification----------------
|
||||
void NotificationManager::PrintHostUploadNotification::init()
|
||||
{
|
||||
@ -1841,6 +2054,81 @@ void NotificationManager::push_import_finished_notification(const std::string& p
|
||||
set_slicing_progress_hidden();
|
||||
}
|
||||
|
||||
void NotificationManager::push_download_URL_progress_notification(size_t id, const std::string& text, std::function<bool(DownloaderUserAction, int)> user_action_callback)
|
||||
{
|
||||
// If already exists
|
||||
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::URLDownload && dynamic_cast<URLDownloadNotification*>(notification.get())->get_download_id() == id) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// push new one
|
||||
NotificationData data{ NotificationType::URLDownload, NotificationLevel::ProgressBarNotificationLevel, 5, _u8L("Download") + ": " + text };
|
||||
push_notification_data(std::make_unique<NotificationManager::URLDownloadNotification>(data, m_id_provider, m_evt_handler, id, user_action_callback), 0);
|
||||
}
|
||||
|
||||
void NotificationManager::set_download_URL_progress(size_t id, float percentage)
|
||||
{
|
||||
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::URLDownload) {
|
||||
URLDownloadNotification* ntf = dynamic_cast<URLDownloadNotification*>(notification.get());
|
||||
if (ntf->get_download_id() != id)
|
||||
continue;
|
||||
// if this changes the percentage, it should be shown now
|
||||
float percent_b4 = ntf->get_percentage();
|
||||
ntf->set_percentage(percentage);
|
||||
ntf->set_paused(false);
|
||||
if (ntf->get_percentage() != percent_b4)
|
||||
wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationManager::set_download_URL_paused(size_t id)
|
||||
{
|
||||
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::URLDownload) {
|
||||
URLDownloadNotification* ntf = dynamic_cast<URLDownloadNotification*>(notification.get());
|
||||
if (ntf->get_download_id() != id)
|
||||
continue;
|
||||
ntf->set_paused(true);
|
||||
wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationManager::set_download_URL_canceled(size_t id)
|
||||
{
|
||||
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::URLDownload) {
|
||||
URLDownloadNotification* ntf = dynamic_cast<URLDownloadNotification*>(notification.get());
|
||||
if (ntf->get_download_id() != id)
|
||||
continue;
|
||||
ntf->close();
|
||||
wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
void NotificationManager::set_download_URL_error(size_t id, const std::string& text)
|
||||
{
|
||||
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::URLDownload) {
|
||||
URLDownloadNotification* ntf = dynamic_cast<URLDownloadNotification*>(notification.get());
|
||||
if (ntf->get_download_id() != id)
|
||||
continue;
|
||||
float percent_b4 = ntf->get_percentage();
|
||||
ntf->set_percentage(-1.f);
|
||||
ntf->set_error_message(text);
|
||||
if (ntf->get_percentage() != percent_b4)
|
||||
wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationManager::push_upload_job_notification(int id, float filesize, const std::string& filename, const std::string& host, float percentage)
|
||||
{
|
||||
// find if upload with same id was not already in notification
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "Event.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "Jobs/ProgressIndicator.hpp"
|
||||
#include "Downloader.hpp"
|
||||
|
||||
#include <libslic3r/ObjectID.hpp>
|
||||
#include <libslic3r/Technologies.hpp>
|
||||
@ -129,6 +130,8 @@ enum class NotificationType
|
||||
NetfabbFinished,
|
||||
// Short meesage to fill space between start and finish of export
|
||||
ExportOngoing,
|
||||
// Progressbar of download from prusaslicer://url
|
||||
URLDownload,
|
||||
// BBS: Short meesage to fill space between start and finish of arranging
|
||||
ArrangeOngoing,
|
||||
// BBL: Plate Info ,Design For @YangLeDuo
|
||||
@ -247,6 +250,14 @@ public:
|
||||
// Exporting finished, show this information with path, button to open containing folder and if ejectable - eject button
|
||||
void push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable);
|
||||
void push_import_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable);
|
||||
|
||||
// Download URL progress notif
|
||||
void push_download_URL_progress_notification(size_t id, const std::string& text, std::function<bool(DownloaderUserAction, int)> user_action_callback);
|
||||
void set_download_URL_progress(size_t id, float percentage);
|
||||
void set_download_URL_paused(size_t id);
|
||||
void set_download_URL_canceled(size_t id);
|
||||
void set_download_URL_error(size_t id, const std::string& text);
|
||||
|
||||
// notifications with progress bar
|
||||
// slicing progress
|
||||
void init_slicing_progress_notification(std::function<bool()> cancel_callback);
|
||||
@ -593,6 +604,7 @@ private:
|
||||
|
||||
ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) : PopNotification(n, id_provider, evt_handler) { }
|
||||
virtual void set_percentage(float percent) { m_percentage = percent; }
|
||||
float get_percentage() const { return m_percentage; }
|
||||
protected:
|
||||
virtual void init() override;
|
||||
virtual void render_text(ImGuiWrapper& imgui,
|
||||
@ -615,6 +627,62 @@ private:
|
||||
|
||||
};
|
||||
|
||||
class URLDownloadNotification : public ProgressBarNotification
|
||||
{
|
||||
public:
|
||||
URLDownloadNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, size_t download_id, std::function<bool(DownloaderUserAction, int)> user_action_callback)
|
||||
//: ProgressBarWithCancelNotification(n, id_provider, evt_handler, cancel_callback)
|
||||
: ProgressBarNotification(n, id_provider, evt_handler)
|
||||
, m_download_id(download_id)
|
||||
, m_user_action_callback(user_action_callback)
|
||||
{
|
||||
}
|
||||
void set_percentage(float percent) override
|
||||
{
|
||||
m_percentage = percent;
|
||||
if (m_percentage >= 1.f) {
|
||||
m_notification_start = GLCanvas3D::timestamp_now();
|
||||
m_state = EState::Shown;
|
||||
} else
|
||||
m_state = EState::NotFading;
|
||||
}
|
||||
size_t get_download_id() { return m_download_id; }
|
||||
void set_user_action_callback(std::function<bool(DownloaderUserAction, int)> user_action_callback) { m_user_action_callback = user_action_callback; }
|
||||
void set_paused(bool paused) { m_download_paused = paused; }
|
||||
void set_error_message(const std::string& message) { m_error_message = message; }
|
||||
bool compare_text(const std::string& text) const override { return false; };
|
||||
protected:
|
||||
void render_close_button(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x, const float win_pos_y) override;
|
||||
void render_close_button_inner(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x, const float win_pos_y);
|
||||
void render_pause_cancel_buttons_inner(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x, const float win_pos_y);
|
||||
void render_open_button_inner(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x, const float win_pos_y);
|
||||
void render_cancel_button_inner(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x, const float win_pos_y);
|
||||
void render_pause_button_inner(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x, const float win_pos_y);
|
||||
void render_bar(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x, const float win_pos_y) override;
|
||||
void trigger_user_action_callback(DownloaderUserAction action);
|
||||
|
||||
void count_spaces() override;
|
||||
|
||||
size_t m_download_id;
|
||||
std::function<bool(DownloaderUserAction, int)> m_user_action_callback;
|
||||
bool m_download_paused {false};
|
||||
std::string m_error_message;
|
||||
};
|
||||
|
||||
class PrintHostUploadNotification : public ProgressBarNotification
|
||||
{
|
||||
public:
|
||||
|
@ -156,6 +156,7 @@
|
||||
|
||||
#include <libslic3r/CutUtils.hpp>
|
||||
#include <wx/glcanvas.h> // Needs to be last because reasons :-/
|
||||
#include <libslic3r/miniz_extension.hpp>
|
||||
#include "WipeTowerDialog.hpp"
|
||||
#include "ObjColorDialog.hpp"
|
||||
|
||||
@ -168,6 +169,7 @@
|
||||
#include "PlateSettingsDialog.hpp"
|
||||
#include "DailyTips.hpp"
|
||||
#include "CreatePresetsDialog.hpp"
|
||||
#include "FileArchiveDialog.hpp"
|
||||
|
||||
using boost::optional;
|
||||
namespace fs = boost::filesystem;
|
||||
@ -8996,8 +8998,9 @@ void Plater::import_model_id(wxString download_info)
|
||||
/* prepare project and profile */
|
||||
boost::thread import_thread = Slic3r::create_thread([&percent, &cont, &cancel, &retry_count, max_retries, &msg, &target_path, &download_ok, download_url, &filename] {
|
||||
|
||||
NetworkAgent* m_agent = Slic3r::GUI::wxGetApp().getAgent();
|
||||
if (!m_agent) return;
|
||||
// Orca: NetworkAgent is not needed and only prevents this from running
|
||||
// NetworkAgent* m_agent = Slic3r::GUI::wxGetApp().getAgent();
|
||||
// if (!m_agent) return;
|
||||
|
||||
int res = 0;
|
||||
unsigned int http_code;
|
||||
@ -9148,7 +9151,11 @@ void Plater::import_model_id(wxString download_info)
|
||||
if (download_ok) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "import_model_id: target_path = " << target_path.string();
|
||||
/* load project */
|
||||
this->load_project(target_path.wstring());
|
||||
// Orca: If download is a zip file, treat it as if file has been drag and dropped on the plater
|
||||
if (target_path.extension() == ".zip")
|
||||
this->load_files(wxArrayString(1, target_path.string()));
|
||||
else
|
||||
this->load_project(target_path.wstring());
|
||||
/*BBS set project info after load project, project info is reset in load project */
|
||||
//p->project.project_model_id = model_id;
|
||||
//p->project.project_design_id = design_id;
|
||||
@ -9818,6 +9825,19 @@ void Plater::calib_VFA(const Calib_Params& params)
|
||||
p->background_process.fff_print()->set_calib_params(params);
|
||||
}
|
||||
BuildVolume_Type Plater::get_build_volume_type() const { return p->bed.get_build_volume_type(); }
|
||||
|
||||
void Plater::import_zip_archive()
|
||||
{
|
||||
wxString input_file;
|
||||
wxGetApp().import_zip(this, input_file);
|
||||
if (input_file.empty())
|
||||
return;
|
||||
|
||||
wxArrayString arr;
|
||||
arr.Add(input_file);
|
||||
load_files(arr);
|
||||
}
|
||||
|
||||
void Plater::import_sl1_archive()
|
||||
{
|
||||
auto &w = get_ui_job_worker();
|
||||
@ -10003,6 +10023,186 @@ std::vector<size_t> Plater::load_files(const std::vector<std::string>& input_fil
|
||||
return p->load_files(paths, strategy, ask_multi);
|
||||
}
|
||||
|
||||
bool Plater::preview_zip_archive(const boost::filesystem::path& archive_path)
|
||||
{
|
||||
//std::vector<fs::path> unzipped_paths;
|
||||
std::vector<fs::path> non_project_paths;
|
||||
std::vector<fs::path> project_paths;
|
||||
try
|
||||
{
|
||||
mz_zip_archive archive;
|
||||
mz_zip_zero_struct(&archive);
|
||||
|
||||
if (!open_zip_reader(&archive, archive_path.string())) {
|
||||
// TRN %1% is archive path
|
||||
std::string err_msg = GUI::format(_u8L("Loading of a ZIP archive on path %1% has failed."), archive_path.string());
|
||||
throw Slic3r::FileIOError(err_msg);
|
||||
}
|
||||
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
|
||||
mz_zip_archive_file_stat stat;
|
||||
// selected_paths contains paths and its uncompressed size. The size is used to distinguish between files with same path.
|
||||
std::vector<std::pair<fs::path, size_t>> selected_paths;
|
||||
FileArchiveDialog dlg(static_cast<wxWindow*>(wxGetApp().mainframe), &archive, selected_paths);
|
||||
if (dlg.ShowModal() == wxID_OK)
|
||||
{
|
||||
std::string archive_path_string = archive_path.string();
|
||||
archive_path_string = archive_path_string.substr(0, archive_path_string.size() - 4);
|
||||
fs::path archive_dir(wxStandardPaths::Get().GetTempDir().utf8_str().data());
|
||||
|
||||
for (auto& path_w_size : selected_paths) {
|
||||
const fs::path& path = path_w_size.first;
|
||||
size_t size = path_w_size.second;
|
||||
// find path in zip archive
|
||||
for (mz_uint i = 0; i < num_entries; ++i) {
|
||||
if (mz_zip_reader_file_stat(&archive, i, &stat)) {
|
||||
if (size != stat.m_uncomp_size) // size must fit
|
||||
continue;
|
||||
wxString wname = boost::nowide::widen(stat.m_filename);
|
||||
std::string name = boost::nowide::narrow(wname);
|
||||
fs::path archive_path(name);
|
||||
|
||||
std::string extra(1024, 0);
|
||||
size_t extra_size = mz_zip_reader_get_filename_from_extra(&archive, i, extra.data(), extra.size());
|
||||
if (extra_size > 0) {
|
||||
archive_path = fs::path(extra.substr(0, extra_size));
|
||||
name = archive_path.string();
|
||||
}
|
||||
|
||||
if (archive_path.empty())
|
||||
continue;
|
||||
if (path != archive_path)
|
||||
continue;
|
||||
// decompressing
|
||||
try
|
||||
{
|
||||
std::replace(name.begin(), name.end(), '\\', '/');
|
||||
// rename if file exists
|
||||
std::string filename = path.filename().string();
|
||||
std::string extension = boost::filesystem::extension(path);
|
||||
std::string just_filename = filename.substr(0, filename.size() - extension.size());
|
||||
std::string final_filename = just_filename;
|
||||
|
||||
size_t version = 0;
|
||||
while (fs::exists(archive_dir / (final_filename + extension)))
|
||||
{
|
||||
++version;
|
||||
final_filename = just_filename + "(" + std::to_string(version) + ")";
|
||||
}
|
||||
filename = final_filename + extension;
|
||||
fs::path final_path = archive_dir / filename;
|
||||
std::string buffer((size_t)stat.m_uncomp_size, 0);
|
||||
// Decompress action. We already has correct file index in stat structure.
|
||||
mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
|
||||
if (res == 0) {
|
||||
// TRN: First argument = path to file, second argument = error description
|
||||
wxString error_log = GUI::format_wxstr(_L("Failed to unzip file to %1%: %2%"), final_path.string(), mz_zip_get_error_string(mz_zip_get_last_error(&archive)));
|
||||
BOOST_LOG_TRIVIAL(error) << error_log;
|
||||
show_error(nullptr, error_log);
|
||||
break;
|
||||
}
|
||||
// write buffer to file
|
||||
fs::fstream file(final_path, std::ios::out | std::ios::binary | std::ios::trunc);
|
||||
file.write(buffer.c_str(), buffer.size());
|
||||
file.close();
|
||||
if (!fs::exists(final_path)) {
|
||||
wxString error_log = GUI::format_wxstr(_L("Failed to find unzipped file at %1%. Unzipping of file has failed."), final_path.string());
|
||||
BOOST_LOG_TRIVIAL(error) << error_log;
|
||||
show_error(nullptr, error_log);
|
||||
break;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "Unzipped " << final_path;
|
||||
if (!boost::algorithm::iends_with(filename, ".3mf") && !boost::algorithm::iends_with(filename, ".amf")) {
|
||||
non_project_paths.emplace_back(final_path);
|
||||
break;
|
||||
}
|
||||
// if 3mf - read archive headers to find project file
|
||||
if (/*(boost::algorithm::iends_with(filename, ".3mf") && !is_project_3mf(final_path.string())) ||*/
|
||||
(boost::algorithm::iends_with(filename, ".amf") && !boost::algorithm::iends_with(filename, ".zip.amf"))) {
|
||||
non_project_paths.emplace_back(final_path);
|
||||
break;
|
||||
}
|
||||
|
||||
project_paths.emplace_back(final_path);
|
||||
break;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
// ensure the zip archive is closed and rethrow the exception
|
||||
close_zip_reader(&archive);
|
||||
throw Slic3r::FileIOError(e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
close_zip_reader(&archive);
|
||||
if (non_project_paths.size() + project_paths.size() != selected_paths.size())
|
||||
BOOST_LOG_TRIVIAL(error) << "Decompresing of archive did not retrieve all files. Expected files: "
|
||||
<< selected_paths.size()
|
||||
<< " Decopressed files: "
|
||||
<< non_project_paths.size() + project_paths.size();
|
||||
} else {
|
||||
close_zip_reader(&archive);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
catch (const Slic3r::FileIOError& e) {
|
||||
// zip reader should be already closed or not even opened
|
||||
GUI::show_error(this, e.what());
|
||||
return false;
|
||||
}
|
||||
// none selected
|
||||
if (project_paths.empty() && non_project_paths.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1 project file and some models - behave like drag n drop of 3mf and then load models
|
||||
if (project_paths.size() == 1)
|
||||
{
|
||||
wxArrayString aux;
|
||||
aux.Add(from_u8(project_paths.front().string()));
|
||||
bool loaded3mf = load_files(aux);
|
||||
load_files(non_project_paths, LoadStrategy::LoadModel);
|
||||
boost::system::error_code ec;
|
||||
if (loaded3mf) {
|
||||
fs::remove(project_paths.front(), ec);
|
||||
if (ec)
|
||||
BOOST_LOG_TRIVIAL(error) << ec.message();
|
||||
}
|
||||
for (const fs::path& path : non_project_paths) {
|
||||
// Delete file from temp file (path variable), it will stay only in app memory.
|
||||
boost::system::error_code ec;
|
||||
fs::remove(path, ec);
|
||||
if (ec)
|
||||
BOOST_LOG_TRIVIAL(error) << ec.message();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// load all projects and all models as geometry
|
||||
load_files(project_paths, LoadStrategy::LoadModel);
|
||||
load_files(non_project_paths, LoadStrategy::LoadModel);
|
||||
|
||||
|
||||
for (const fs::path& path : project_paths) {
|
||||
// Delete file from temp file (path variable), it will stay only in app memory.
|
||||
boost::system::error_code ec;
|
||||
fs::remove(path, ec);
|
||||
if (ec)
|
||||
BOOST_LOG_TRIVIAL(error) << ec.message();
|
||||
}
|
||||
for (const fs::path& path : non_project_paths) {
|
||||
// Delete file from temp file (path variable), it will stay only in app memory.
|
||||
boost::system::error_code ec;
|
||||
fs::remove(path, ec);
|
||||
if (ec)
|
||||
BOOST_LOG_TRIVIAL(error) << ec.message();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class RadioBox;
|
||||
class RadioSelector
|
||||
{
|
||||
@ -10341,7 +10541,7 @@ void ProjectDropDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
//BBS: remove GCodeViewer as seperate APP logic
|
||||
bool Plater::load_files(const wxArrayString& filenames)
|
||||
{
|
||||
const std::regex pattern_drop(".*[.](stp|step|stl|oltp|obj|amf|3mf|svg)", std::regex::icase);
|
||||
const std::regex pattern_drop(".*[.](stp|step|stl|oltp|obj|amf|3mf|svg|zip)", std::regex::icase);
|
||||
const std::regex pattern_gcode_drop(".*[.](gcode|g)", std::regex::icase);
|
||||
|
||||
std::vector<fs::path> normal_paths;
|
||||
@ -10431,6 +10631,21 @@ bool Plater::load_files(const wxArrayString& filenames)
|
||||
}
|
||||
}
|
||||
|
||||
// Orca: Iters through given paths and imports files from zip then remove zip from paths
|
||||
// returns true if zip files were found
|
||||
auto handle_zips = [this](vector<fs::path>& paths) { // NOLINT(*-no-recursion) - Recursion is intended and should be managed properly
|
||||
bool res = false;
|
||||
for (auto it = paths.begin(); it != paths.end();) {
|
||||
if (boost::algorithm::iends_with(it->string(), ".zip")) {
|
||||
res = true;
|
||||
preview_zip_archive(*it);
|
||||
it = paths.erase(it);
|
||||
} else
|
||||
it++;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
switch (loadfiles_type) {
|
||||
case LoadFilesType::Single3MF:
|
||||
open_3mf_file(normal_paths[0]);
|
||||
@ -10438,6 +10653,7 @@ bool Plater::load_files(const wxArrayString& filenames)
|
||||
|
||||
case LoadFilesType::SingleOther: {
|
||||
Plater::TakeSnapshot snapshot(this, snapshot_label);
|
||||
if (handle_zips(normal_paths)) return true;
|
||||
if (load_files(normal_paths, LoadStrategy::LoadModel, false).empty()) { res = false; }
|
||||
break;
|
||||
}
|
||||
@ -10453,6 +10669,9 @@ bool Plater::load_files(const wxArrayString& filenames)
|
||||
|
||||
case LoadFilesType::MultipleOther: {
|
||||
Plater::TakeSnapshot snapshot(this, snapshot_label);
|
||||
if (handle_zips(normal_paths)) {
|
||||
if (normal_paths.empty()) return true;
|
||||
}
|
||||
if (load_files(normal_paths, LoadStrategy::LoadModel, true).empty()) { res = false; }
|
||||
break;
|
||||
}
|
||||
@ -10471,6 +10690,9 @@ bool Plater::load_files(const wxArrayString& filenames)
|
||||
|
||||
open_3mf_file(first_file[0]);
|
||||
if (load_files(tmf_file, LoadStrategy::LoadModel).empty()) { res = false; }
|
||||
if (res && handle_zips(other_file)) {
|
||||
if (normal_paths.empty()) return true;
|
||||
}
|
||||
if (load_files(other_file, LoadStrategy::LoadModel, false).empty()) { res = false; }
|
||||
break;
|
||||
default: break;
|
||||
|
@ -262,6 +262,7 @@ public:
|
||||
int get_3mf_file_count(std::vector<fs::path> paths);
|
||||
void add_file();
|
||||
void add_model(bool imperial_units = false, std::string fname = "");
|
||||
void import_zip_archive();
|
||||
void import_sl1_archive();
|
||||
void extract_config_from_project();
|
||||
void load_gcode();
|
||||
@ -298,6 +299,8 @@ public:
|
||||
static wxColour get_next_color_for_filament();
|
||||
static wxString get_slice_warning_string(GCodeProcessorResult::SliceWarning& warning);
|
||||
|
||||
bool preview_zip_archive(const boost::filesystem::path& archive_path);
|
||||
|
||||
// BBS: restore
|
||||
std::vector<size_t> load_files(const std::vector<boost::filesystem::path>& input_files, LoadStrategy strategy = LoadStrategy::LoadModel | LoadStrategy::LoadConfig, bool ask_multi = false);
|
||||
// To be called when providing a list of files to the GUI slic3r on command line.
|
||||
@ -803,7 +806,7 @@ private:
|
||||
bool m_only_gcode { false };
|
||||
bool m_exported_file { false };
|
||||
bool skip_thumbnail_invalid { false };
|
||||
bool m_loading_project {false };
|
||||
bool m_loading_project { false };
|
||||
std::string m_preview_only_filename;
|
||||
int m_valid_plates_count { 0 };
|
||||
|
||||
|
@ -1088,6 +1088,8 @@ wxWindow* PreferencesDialog::create_general_page()
|
||||
//downloads
|
||||
auto title_downloads = create_item_title(_L("Downloads"), page, _L("Downloads"));
|
||||
auto item_downloads = create_item_downloads(page,50,"download_path");
|
||||
auto ps_download_url_registered = create_item_checkbox(_L("Allow downloads from Printables.com"), page,
|
||||
_L("Allow downloads from Printables.com"), 50, "ps_url_registered");
|
||||
|
||||
//dark mode
|
||||
#ifdef _WIN32
|
||||
@ -1150,6 +1152,7 @@ wxWindow* PreferencesDialog::create_general_page()
|
||||
|
||||
sizer_page->Add(title_downloads, 0, wxTOP| wxEXPAND, FromDIP(20));
|
||||
sizer_page->Add(item_downloads, 0, wxEXPAND, FromDIP(3));
|
||||
sizer_page->Add(ps_download_url_registered, 0, wxTOP | wxEXPAND, FromDIP(20));
|
||||
|
||||
#ifdef _WIN32
|
||||
sizer_page->Add(title_darkmode, 0, wxTOP | wxEXPAND, FromDIP(20));
|
||||
|
@ -138,6 +138,7 @@ struct Http::priv
|
||||
void set_post_body(const std::string &body);
|
||||
void set_put_body(const fs::path &path);
|
||||
void set_del_body(const std::string& body);
|
||||
void set_range(const std::string &range);
|
||||
|
||||
std::string curl_error(CURLcode curlcode);
|
||||
std::string body_size_error();
|
||||
@ -237,7 +238,7 @@ int Http::priv::xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_o
|
||||
curl_easy_getinfo(self->curl, CURLINFO_SPEED_UPLOAD, &speed);
|
||||
if (speed > 0.01)
|
||||
speed = speed;
|
||||
Progress progress(dltotal, dlnow, ultotal, ulnow, speed);
|
||||
Progress progress(dltotal, dlnow, ultotal, ulnow, self->buffer, speed);
|
||||
self->progressfn(progress, cb_cancel);
|
||||
}
|
||||
|
||||
@ -370,6 +371,11 @@ void Http::priv::set_del_body(const std::string& body)
|
||||
postfields = body;
|
||||
}
|
||||
|
||||
void Http::priv::set_range(const std::string& range)
|
||||
{
|
||||
::curl_easy_setopt(curl, CURLOPT_RANGE, range.c_str());
|
||||
}
|
||||
|
||||
std::string Http::priv::curl_error(CURLcode curlcode)
|
||||
{
|
||||
return (boost::format("curl:%1%:\n%2%\n[Error %3%]")
|
||||
@ -434,7 +440,7 @@ void Http::priv::http_perform()
|
||||
if (res == CURLE_ABORTED_BY_CALLBACK) {
|
||||
if (cancel) {
|
||||
// The abort comes from the request being cancelled programatically
|
||||
Progress dummyprogress(0, 0, 0, 0);
|
||||
Progress dummyprogress(0, 0, 0, 0, std::string());
|
||||
bool cancel = true;
|
||||
if (progressfn) { progressfn(dummyprogress, cancel); }
|
||||
} else {
|
||||
@ -510,6 +516,12 @@ Http& Http::size_limit(size_t sizeLimit)
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::set_range(const std::string& range)
|
||||
{
|
||||
if (p) { p->set_range(range); }
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::header(std::string name, const std::string &value)
|
||||
{
|
||||
if (!p) { return * this; }
|
||||
|
@ -42,14 +42,15 @@ public:
|
||||
size_t dlnow; // Bytes downloaded so far
|
||||
size_t ultotal; // Total bytes to upload
|
||||
size_t ulnow; // Bytes uploaded so far
|
||||
const std::string& buffer; // reference to buffer containing all data
|
||||
double upload_spd{0.0f};
|
||||
|
||||
Progress(size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow) :
|
||||
dltotal(dltotal), dlnow(dlnow), ultotal(ultotal), ulnow(ulnow)
|
||||
Progress(size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow, const std::string& buffer) :
|
||||
dltotal(dltotal), dlnow(dlnow), ultotal(ultotal), ulnow(ulnow), buffer(buffer)
|
||||
{}
|
||||
|
||||
Progress(size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow, double ulspd) :
|
||||
dltotal(dltotal), dlnow(dlnow), ultotal(ultotal), ulnow(ulnow), upload_spd(ulspd)
|
||||
Progress(size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow, const std::string& buffer, double ulspd) :
|
||||
dltotal(dltotal), dlnow(dlnow), ultotal(ultotal), ulnow(ulnow), buffer(buffer), upload_spd(ulspd)
|
||||
{}
|
||||
};
|
||||
|
||||
@ -102,6 +103,8 @@ public:
|
||||
// Sets a maximum size of the data that can be received.
|
||||
// A value of zero sets the default limit, which is is 5MB.
|
||||
Http& size_limit(size_t sizeLimit);
|
||||
// range of donloaded bytes. example: curl_easy_setopt(curl, CURLOPT_RANGE, "0-199");
|
||||
Http& set_range(const std::string& range);
|
||||
// Sets a HTTP header field.
|
||||
Http& header(std::string name, const std::string &value);
|
||||
// Removes a header field.
|
||||
|