Merge remote-tracking branch 'private/dk_280'

This commit is contained in:
Lukas Matena 2024-03-26 13:41:50 +01:00
commit e15c235089
85 changed files with 5985 additions and 458 deletions

View File

@ -41,8 +41,8 @@ option(SLIC3R_MSVC_COMPILE_PARALLEL "Compile on Visual Studio in parallel" 1)
option(SLIC3R_MSVC_PDB "Generate PDB files on MSVC in Release mode" 1)
option(SLIC3R_ASAN "Enable ASan on Clang and GCC" 0)
option(SLIC3R_UBSAN "Enable UBSan on Clang and GCC" 0)
option(SLIC3R_ENABLE_FORMAT_STEP "Enable compilation of STEP file support" ON)
# If SLIC3R_FHS is 1 -> SLIC3R_DESKTOP_INTEGRATION is always 0, othrewise variable.
option(SLIC3R_ENABLE_FORMAT_STEP "Enable compilation of STEP file support" 1)
# If SLIC3R_FHS is 1 -> SLIC3R_DESKTOP_INTEGRATION is always 0, otherwise variable.
CMAKE_DEPENDENT_OPTION(SLIC3R_DESKTOP_INTEGRATION "Allow perfoming desktop integration during runtime" 1 "NOT SLIC3R_FHS" 0)
set(OPENVDB_FIND_MODULE_PATH "" CACHE PATH "Path to OpenVDB installation's find modules.")
@ -606,6 +606,13 @@ function(prusaslicer_copy_dlls target)
COMMENT "Copy mpfr runtime to build tree"
VERBATIM)
if(DEFINED DESTDIR)
add_custom_command(TARGET ${target} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${DESTDIR}/usr/local/bin/WebView2Loader.dll ${_out_dir}
COMMENT "Copy WebView2Loader runtime to build tree"
VERBATIM)
endif ()
endfunction()

60
deps/+WebView2/WebView2.cmake vendored Normal file
View File

@ -0,0 +1,60 @@
if (MSVC)
# Update the following variables if updating WebView2 SDK
set(WEBVIEW2_VERSION "1.0.705.50")
set(WEBVIEW2_URL "https://www.nuget.org/api/v2/package/Microsoft.Web.WebView2/${WEBVIEW2_VERSION}")
set(WEBVIEW2_SHA256 "6a34bb553e18cfac7297b4031f3eac2558e439f8d16a45945c22945ac404105d")
set(WEBVIEW2_DEFAULT_PACKAGE_DIR "${CMAKE_CURRENT_BINARY_DIR}/dep_WebView2-prefix/packages/Microsoft.Web.WebView2.${WEBVIEW2_VERSION}")
set(WEBVIEW2_DOWNLOAD_DIR "${CMAKE_CURRENT_BINARY_DIR}/dep_WebView2-prefix/download")
#message(STATUS "WEBVIEW2_DEFAULT_PACKAGE_DIR = ${WEBVIEW2_DEFAULT_PACKAGE_DIR}")
if(NOT EXISTS ${WEBVIEW2_PACKAGE_DIR})
unset(WEBVIEW2_PACKAGE_DIR CACHE)
endif()
set(WEBVIEW2_PACKAGE_DIR ${WEBVIEW2_DEFAULT_PACKAGE_DIR} CACHE PATH "WebView2 SDK PATH" FORCE)
#file(MAKE_DIRECTORY ${DEP_DOWNLOAD_DIR}/WebView2)
message(STATUS "WEBVIEW2_URL = ${WEBVIEW2_URL}")
message(STATUS "WEBVIEW2_DOWNLOAD_DIR = ${WEBVIEW2_DOWNLOAD_DIR}")
file(DOWNLOAD
${WEBVIEW2_URL}
${WEBVIEW2_DOWNLOAD_DIR}/WebView2.nuget
EXPECTED_HASH SHA256=${WEBVIEW2_SHA256})
file(MAKE_DIRECTORY ${WEBVIEW2_PACKAGE_DIR})
execute_process(COMMAND
${CMAKE_COMMAND} -E tar x ${WEBVIEW2_DOWNLOAD_DIR}/WebView2.nuget
WORKING_DIRECTORY ${WEBVIEW2_PACKAGE_DIR}
)
set(_srcdir ${WEBVIEW2_PACKAGE_DIR}/build/native)
set(_dstdir ${${PROJECT_NAME}_DEP_INSTALL_PREFIX})
set(_output ${_dstdir}/include/WebView2.h
${_dstdir}/bin/WebView2Loader.dll)
if(NOT EXISTS ${_dstdir}/include)
file(MAKE_DIRECTORY ${_dstdir}/include)
endif()
if(NOT EXISTS ${_dstdir}/bin)
file(MAKE_DIRECTORY ${_dstdir}/bin)
endif()
add_custom_command(
OUTPUT ${_output}
COMMAND ${CMAKE_COMMAND} -E copy ${_srcdir}/include/WebView2.h ${_dstdir}/include/
COMMAND ${CMAKE_COMMAND} -E copy ${_srcdir}/x${DEPS_BITS}/WebView2Loader.dll ${_dstdir}/bin/
)
add_custom_target(dep_WebView2 SOURCES ${_output})
set(WEBVIEW2_PACKAGE_DIR ${WEBVIEW2_PACKAGE_DIR} CACHE INTERNAL "" FORCE)
endif ()

View File

@ -14,6 +14,18 @@ if (UNIX AND NOT APPLE) # wxWidgets will not use char as the underlying type for
set (_unicode_utf8 ON)
endif()
if (MSVC)
set(_wx_webview "-DwxUSE_WEBVIEW_EDGE=ON")
else ()
set(_wx_webview "-DwxUSE_WEBVIEW=ON")
endif ()
if (UNIX AND NOT APPLE)
set(_wx_secretstore "-DwxUSE_SECRETSTORE=OFF")
else ()
set(_wx_secretstore "-DwxUSE_SECRETSTORE=ON")
endif ()
add_cmake_project(wxWidgets
URL https://github.com/prusa3d/wxWidgets/archive/78aa2dc0ea7ce99dc19adc1140f74c3e2e3f3a26.zip
URL_HASH SHA256=94b7d972373503e380e5a8b0ca63b1ccb956da4006402298dd89a0c5c7041b1e
@ -39,6 +51,8 @@ add_cmake_project(wxWidgets
-DwxUSE_XTEST=OFF
-DwxUSE_GLCANVAS_EGL=OFF
-DwxUSE_WEBREQUEST=OFF
${_wx_webview}
${_wx_secretstore}
)
set(DEP_wxWidgets_DEPENDS ZLIB PNG EXPAT TIFF JPEG NanoSVG)

8
deps/CMakeLists.txt vendored
View File

@ -180,7 +180,7 @@ foreach (pkg ${FOUND_PACKAGES})
if (${pkg} IN_LIST SYSTEM_PROVIDED_PACKAGES)
check_system_package(${pkg} _checked_list)
else ()
elseif (TARGET dep_${pkg})
get_target_property(_is_excluded_from_all dep_${pkg} EXCLUDE_FROM_ALL)
if (NOT _is_excluded_from_all)
list(APPEND DEPS_TO_BUILD ${pkg})
@ -189,6 +189,12 @@ foreach (pkg ${FOUND_PACKAGES})
endif ()
endforeach()
# This ugly append ensures that WebView2 was appended no matter what EXCLUDE_FROM_ALL is.
# (Webview2 is not added by add_cmake_project)
if (MSVC)
list(APPEND DEPS_TO_BUILD WebView2)
endif()
# Establish dependency graph
foreach (pkg ${SUPPORTED_PACKAGES})
if (${pkg} IN_LIST DEPS_TO_BUILD)

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
sodipodi:docname="connect_gcode.svg"
xml:space="preserve"
enable-background="new 0 0 16 16"
viewBox="0 0 16 16"
y="0px"
x="0px"
id="Layer_1"
version="1.0"><metadata
id="metadata14"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs12" /><sodipodi:namedview
inkscape:current-layer="Layer_1"
inkscape:window-maximized="1"
inkscape:window-y="-8"
inkscape:window-x="-8"
inkscape:cy="9.9832196"
inkscape:cx="2.8306806"
inkscape:zoom="63"
showgrid="false"
id="namedview10"
inkscape:window-height="1369"
inkscape:window-width="2560"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
bordercolor="#666666"
pagecolor="#ffffff" />
<g
id="export_x5F_gcode">
<g
id="g4">
<path
id="path2"
d="M5.02,7.17H9v3.08c0,2.6-1.23,3.72-4.05,3.72S1,12.85,1,10.29V5.54C1,3.12,2.09,2,4.95,2S9,3,9,5.54H6.88 c0-1.11-0.28-1.66-1.92-1.66c-1.54,0-1.83,0.69-1.83,1.77v4.65c0,1.12,0.29,1.77,1.83,1.77c1.54,0,2.08-0.65,2.08-1.82V9.09H5.02 V7.17z"
fill="#808080" />
</g>
<path
id="path6"
d="M14.65,8.35c0.19-0.19,0.19-0.51,0-0.71l-4.29-4.29C10.16,3.16,10,3.22,10,3.5v9 c0,0.27,0.16,0.34,0.35,0.15L14.65,8.35z"
fill="#ED6B21" />
</g>
<rect
y="6.3492064"
x="4.5714288"
height="3.8888888"
width="4.84127"
id="rect839"
style="opacity:1;fill:#ffffff;stroke:#000000;stroke-width:0;paint-order:stroke fill markers" /><path
id="path843"
d="M 4.4179822,13.94859 C 4.24194,13.938614 3.8494525,13.893852 3.6656343,13.862788 2.6463755,13.690539 1.9294944,13.259684 1.5119275,12.568381 1.2915335,12.203508 1.1443351,11.754069 1.0683445,11.213995 1.0075089,10.781631 1.0030069,10.514112 1.009567,7.7213149 1.0163053,4.8525799 1.0140278,4.9409472 1.09282,4.4911561 1.1721297,4.0384109 1.3400774,3.603152 1.5628355,3.273048 1.6996314,3.070331 1.9990973,2.7735968 2.2036965,2.6380331 c 0.4996563,-0.3310634 1.1299827,-0.5208788 2,-0.6022766 0.3329619,-0.031152 1.2762046,-0.02595 1.6269841,0.00897 1.0025526,0.09981 1.6897099,0.3415114 2.2006895,0.7740742 0.5945383,0.5032982 0.9122989,1.316207 0.9530867,2.4382265 l 0.00995,0.2738096 H 7.9454336 6.896457 L 6.888047,5.4475038 C 6.8834276,5.401672 6.8758412,5.2874261 6.8711922,5.1936255 6.8327427,4.4178406 6.4819809,4.0454122 5.6682163,3.9163417 5.5241512,3.8934917 5.3922646,3.8879842 4.9894108,3.8879953 4.5342425,3.888008 4.4736803,3.8911995 4.3139727,3.9235896 3.5744524,4.0735704 3.2458347,4.4270969 3.1445693,5.1816323 3.1145528,5.4052872 3.1152149,10.546576 3.14529,10.782671 c 0.096441,0.757064 0.4318519,1.110873 1.18539,1.250412 0.132096,0.02446 0.2386324,0.02937 0.6349206,0.02922 0.4192767,-1.59e-4 0.498956,-0.0043 0.6666667,-0.03466 0.4378344,-0.07927 0.7498309,-0.22283 0.9761905,-0.449189 C 6.8784859,11.30843 7.004833,10.965643 7.0457901,10.39195 l 0.010482,-0.146826 h 0.9665688 0.9665689 v 0.155558 c 0,0.369715 -0.062469,0.881516 -0.1500308,1.229175 -0.2786577,1.106402 -0.9463917,1.790119 -2.0659801,2.115432 -0.3180264,0.09241 -0.7781945,0.166505 -1.2284335,0.197806 -0.2080052,0.01446 -0.908506,0.01788 -1.1269841,0.0055 z"
style="opacity:1;fill:#ed6b21;fill-opacity:1;stroke:#000000;stroke-width:0;paint-order:stroke fill markers" /></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

18
resources/icons/login.svg Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 499.1 499.1" xml:space="preserve">
<g>
<g>
<g>
<path fill="#ED6B21" d="M0,249.6c0,9.5,7.7,17.2,17.2,17.2h327.6l-63.9,63.8c-6.7,6.7-6.7,17.6,0,24.3c3.3,3.3,7.7,5,12.1,5s8.8-1.7,12.1-5
l93.1-93.1c6.7-6.7,6.7-17.6,0-24.3l-93.1-93.1c-6.7-6.7-17.6-6.7-24.3,0c-6.7,6.7-6.7,17.6,0,24.3l63.8,63.8H17.2
C7.7,232.5,0,240.1,0,249.6z"/>
<path fill="#808080" d="M396.4,494.2c56.7,0,102.7-46.1,102.7-102.8V107.7C499.1,51,453,4.9,396.4,4.9H112.7C56,4.9,10,51,10,107.7V166
c0,9.5,7.7,17.1,17.1,17.1c9.5,0,17.2-7.7,17.2-17.1v-58.3c0-37.7,30.7-68.5,68.4-68.5h283.7c37.7,0,68.4,30.7,68.4,68.5v283.7
c0,37.7-30.7,68.5-68.4,68.5H112.7c-37.7,0-68.4-30.7-68.4-68.5v-57.6c0-9.5-7.7-17.2-17.2-17.2S10,324.3,10,333.8v57.6
c0,56.7,46.1,102.8,102.7,102.8H396.4L396.4,494.2z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 490.3 490.3" xml:space="preserve">
<g>
<g>
<path fill="#808080" d="M0,121.05v248.2c0,34.2,27.9,62.1,62.1,62.1h200.6c34.2,0,62.1-27.9,62.1-62.1v-40.2c0-6.8-5.5-12.3-12.3-12.3
s-12.3,5.5-12.3,12.3v40.2c0,20.7-16.9,37.6-37.6,37.6H62.1c-20.7,0-37.6-16.9-37.6-37.6v-248.2c0-20.7,16.9-37.6,37.6-37.6h200.6
c20.7,0,37.6,16.9,37.6,37.6v40.2c0,6.8,5.5,12.3,12.3,12.3s12.3-5.5,12.3-12.3v-40.2c0-34.2-27.9-62.1-62.1-62.1H62.1
C27.9,58.95,0,86.75,0,121.05z"/>
<path fill="#ED6B21" d="M385.4,337.65c2.4,2.4,5.5,3.6,8.7,3.6s6.3-1.2,8.7-3.6l83.9-83.9c4.8-4.8,4.8-12.5,0-17.3l-83.9-83.9
c-4.8-4.8-12.5-4.8-17.3,0s-4.8,12.5,0,17.3l63,63H218.6c-6.8,0-12.3,5.5-12.3,12.3c0,6.8,5.5,12.3,12.3,12.3h229.8l-63,63
C380.6,325.15,380.6,332.95,385.4,337.65z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="printer">
<rect x="1" y="1" fill="#808080" width="1" height="14"/>
<rect x="14" y="1" fill="#808080" width="1" height="14"/>
<rect x="7.5" y="-1.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 13.5 -2.5)" fill="#808080" width="1" height="14"/>
<rect x="7.5" y="-5.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 9.5 -6.5)" fill="#808080" width="1" height="14"/>
<rect x="7" y="7" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 22 6)" fill="#808080" width="2" height="14"/>
<rect x="3" y="4" fill="#ED6B21" width="4" height="4"/>
<polygon fill="#ED6B21" points="5,9 4,8 6,8 "/>
<circle fill="#808180" cx="12" cy="12" r="4"/>
<circle fill="#7DF028" cx="12" cy="12" r="3.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="printer">
<rect x="1" y="1" fill="#808080" width="1" height="14"/>
<rect x="14" y="1" fill="#808080" width="1" height="14"/>
<rect x="7.5" y="-1.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 13.5 -2.5)" fill="#808080" width="1" height="14"/>
<rect x="7.5" y="-5.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 9.5 -6.5)" fill="#808080" width="1" height="14"/>
<rect x="7" y="7" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 22 6)" fill="#808080" width="2" height="14"/>
<rect x="3" y="4" fill="#ED6B21" width="4" height="4"/>
<polygon fill="#ED6B21" points="5,9 4,8 6,8 "/>
<circle fill="#808180" cx="12" cy="12" r="4"/>
<circle fill="#FFDC00" cx="12" cy="12" r="3.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="printer">
<rect x="1" y="1" fill="#808080" width="1" height="14"/>
<rect x="14" y="1" fill="#808080" width="1" height="14"/>
<rect x="7.5" y="-1.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 13.5 -2.5)" fill="#808080" width="1" height="14"/>
<rect x="7.5" y="-5.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 9.5 -6.5)" fill="#808080" width="1" height="14"/>
<rect x="7" y="7" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 22 6)" fill="#808080" width="2" height="14"/>
<rect x="3" y="4" fill="#ED6B21" width="4" height="4"/>
<polygon fill="#ED6B21" points="5,9 4,8 6,8 "/>
<circle fill="#808180" cx="12" cy="12" r="4"/>
<circle fill="#D30000" cx="12" cy="12" r="3.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="sla">
<rect x="3" y="11" fill="#808080" width="1" height="4"/>
<rect x="12" y="11" fill="#808080" width="1" height="4"/>
<rect x="7.5" y="6.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 19.5 3.5)" fill="#808080" width="1" height="10"/>
<rect x="7.5" y="9.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 22.5 6.5)" fill="#808080" width="1" height="10"/>
<rect x="10.5" y="11.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 24.5 1.5)" fill="#808080" width="2" height="3"/>
<rect x="3.5" y="11.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 17.5 8.5)" fill="#808080" width="2" height="3"/>
<rect x="3" y="1" fill="#ED6B21" width="10" height="10"/>
<circle fill="#808180" cx="12" cy="12" r="4"/>
<circle fill="#7DF028" cx="12" cy="12" r="3.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="sla">
<rect x="3" y="11" fill="#808080" width="1" height="4"/>
<rect x="12" y="11" fill="#808080" width="1" height="4"/>
<rect x="7.5" y="6.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 19.5 3.5)" fill="#808080" width="1" height="10"/>
<rect x="7.5" y="9.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 22.5 6.5)" fill="#808080" width="1" height="10"/>
<rect x="10.5" y="11.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 24.5 1.5)" fill="#808080" width="2" height="3"/>
<rect x="3.5" y="11.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 17.5 8.5)" fill="#808080" width="2" height="3"/>
<rect x="3" y="1" fill="#ED6B21" width="10" height="10"/>
<circle fill="#808180" cx="12" cy="12" r="4"/>
<circle fill="#FFDC00" cx="12" cy="12" r="3.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="sla">
<rect x="3" y="11" fill="#808080" width="1" height="4"/>
<rect x="12" y="11" fill="#808080" width="1" height="4"/>
<rect x="7.5" y="6.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 19.5 3.5)" fill="#808080" width="1" height="10"/>
<rect x="7.5" y="9.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 22.5 6.5)" fill="#808080" width="1" height="10"/>
<rect x="10.5" y="11.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 24.5 1.5)" fill="#808080" width="2" height="3"/>
<rect x="3.5" y="11.5" transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 17.5 8.5)" fill="#808080" width="2" height="3"/>
<rect x="3" y="1" fill="#ED6B21" width="10" height="10"/>
<circle fill="#808180" cx="12" cy="12" r="4"/>
<circle fill="#D30000" cx="12" cy="12" r="3.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

18
resources/icons/user.svg Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Written by Treer (gitlab.com/Treer) -->
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="600"
height="600"
stroke="grey"
stroke-width="30"
fill="none">
<title>Abstract user icon</title>
<circle cx="300" cy="300" r="265" />
<circle cx="300" cy="230" r="115" />
<path d="M106.81863443903,481.4 a205,205 1 0,1 386.36273112194,0" stroke-linecap="butt" />
</svg>

After

Width:  |  Height:  |  Size: 497 B

View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Connection failed</title>
<style>
body {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
}
.container {
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<h1>Connection failed</h1>
<p>Something went wrong.</p>
</div>
</body>
</html>

View File

@ -48,7 +48,7 @@ if (SLIC3R_GUI)
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
set (wxWidgets_CONFIG_OPTIONS "--toolkit=gtk${SLIC3R_GTK}")
endif ()
find_package(wxWidgets 3.2 MODULE REQUIRED COMPONENTS base core adv html gl)
find_package(wxWidgets 3.2 MODULE REQUIRED COMPONENTS base core adv html gl webview)
include(${wxWidgets_USE_FILE})

View File

@ -215,6 +215,12 @@ void AppConfig::set_defaults()
if (get("wifi_config_dialog_declined").empty())
set("wifi_config_dialog_declined", "0");
if (get("connect_polling").empty())
set("connect_polling", "1");
if (get("auth_login_dialog_confirmed").empty())
set("auth_login_dialog_confirmed", "0");
#ifdef _WIN32
if (get("use_legacy_3DConnexion").empty())
set("use_legacy_3DConnexion", "0");

View File

@ -39,6 +39,7 @@
#include <boost/nowide/cstdlib.hpp>
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/nowide/cstdio.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/format.hpp>
#include <string.h>

View File

@ -1990,6 +1990,8 @@ public:
one_string,
// Close parameter, string value could be one of the list values.
select_close,
// Password, string vaule is hidden by asterisk.
password,
};
static bool is_gui_type_enum_open(const GUIType gui_type)
{ return gui_type == ConfigOptionDef::GUIType::i_enum_open || gui_type == ConfigOptionDef::GUIType::f_enum_open || gui_type == ConfigOptionDef::GUIType::select_open; }

View File

@ -1211,6 +1211,12 @@ Preset* PresetCollection::find_preset(const std::string &name, bool first_visibl
first_visible_if_not_found ? &this->first_visible() : nullptr;
}
size_t PresetCollection::get_preset_idx_by_name(const std::string name) const
{
auto it = this->find_preset_internal(name);
return it != m_presets.end() ? it - m_presets.begin() : size_t(-1);
}
// Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible.
size_t PresetCollection::first_visible_idx() const
{

View File

@ -125,6 +125,7 @@ public:
TYPE_PHYSICAL_PRINTER,
// This type is here to support search through the Preferences
TYPE_PREFERENCES,
TYPE_WEBVIEW,
};
Type type = TYPE_INVALID;
@ -436,6 +437,8 @@ public:
const Preset* find_preset(const std::string &name, bool first_visible_if_not_found = false, bool respect_active_preset = true) const
{ return const_cast<PresetCollection*>(this)->find_preset(name, first_visible_if_not_found, respect_active_preset); }
size_t get_preset_idx_by_name(const std::string preset_name) const;
size_t first_visible_idx() const;
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
// If one of the prefered_alternates is compatible, select it.

View File

@ -1947,7 +1947,7 @@ void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_pri
}
}
void PresetBundle::export_configbundle(const std::string &path, bool export_system_settings, bool export_physical_printers/* = false*/)
void PresetBundle::export_configbundle(const std::string &path, bool export_system_settings, bool export_physical_printers/* = false*/, std::function<bool(const std::string&, const std::string&, std::string&)> secret_callback)
{
boost::nowide::ofstream c;
c.open(path, std::ios::out | std::ios::trunc);
@ -1974,8 +1974,14 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst
if (export_physical_printers) {
for (const PhysicalPrinter& ph_printer : this->physical_printers) {
c << std::endl << "[physical_printer:" << ph_printer.name << "]" << std::endl;
for (const std::string& opt_key : ph_printer.config.keys())
c << opt_key << " = " << ph_printer.config.opt_serialize(opt_key) << std::endl;
for (const std::string& opt_key : ph_printer.config.keys()) {
std::string opt_val = ph_printer.config.opt_serialize(opt_key);
if (opt_val == "stored") {
secret_callback(ph_printer.name, opt_key, opt_val);
}
c << opt_key << " = " << opt_val << std::endl;
}
}
}

View File

@ -129,7 +129,7 @@ public:
const std::string &path, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule);
// Export a config bundle file containing all the presets and the names of the active presets.
void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false);
void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false, std::function<bool(const std::string&, const std::string&, std::string&)> secret_callback = nullptr);
// Enable / disable the "- default -" preset.
void set_default_suppressed(bool default_suppressed);

View File

@ -102,7 +102,9 @@ static const t_config_enum_values s_keys_map_PrintHostType {
{ "flashair", htFlashAir },
{ "astrobox", htAstroBox },
{ "repetier", htRepetier },
{ "mks", htMKS }
{ "mks", htMKS },
{ "prusaconnectnew", htPrusaConnectNew },
};
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType)
@ -393,6 +395,7 @@ void PrintConfigDef::init_common_params()
def = this->add("printhost_password", coString);
def->label = L("Password");
// def->tooltip = L("");
def->gui_type = ConfigOptionDef::GUIType::password;
def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli;
def->set_default_value(new ConfigOptionString(""));

View File

@ -65,7 +65,7 @@ enum class MachineLimitsUsage {
};
enum PrintHostType {
htPrusaLink, htPrusaConnect, htOctoPrint, htMoonraker, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS
htPrusaLink, htPrusaConnect, htOctoPrint, htMoonraker, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS, htPrusaConnectNew
};
enum AuthorizationType {

View File

@ -19,6 +19,16 @@ set(SLIC3R_GUI_SOURCES
GUI/AboutDialog.hpp
GUI/ArrangeSettingsDialogImgui.hpp
GUI/ArrangeSettingsDialogImgui.cpp
GUI/UserAccountCommunication.cpp
GUI/UserAccountCommunication.hpp
GUI/UserAccountSession.cpp
GUI/UserAccountSession.hpp
GUI/UserAccount.cpp
GUI/UserAccount.hpp
GUI/WebViewDialog.cpp
GUI/WebViewDialog.hpp
GUI/WebView.cpp
GUI/WebView.hpp
GUI/SysInfoDialog.cpp
GUI/SysInfoDialog.hpp
GUI/KBShortcutsDialog.cpp
@ -242,6 +252,8 @@ set(SLIC3R_GUI_SOURCES
GUI/DoubleSlider.hpp
GUI/Notebook.cpp
GUI/Notebook.hpp
GUI/TopBar.cpp
GUI/TopBar.hpp
GUI/ObjectDataViewModel.cpp
GUI/ObjectDataViewModel.hpp
GUI/InstanceCheck.cpp
@ -289,6 +301,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Downloader.hpp
GUI/DownloaderFileGet.cpp
GUI/DownloaderFileGet.hpp
GUI/LoginDialog.cpp
GUI/LoginDialog.hpp
Utils/AppUpdater.cpp
Utils/AppUpdater.hpp
Utils/Http.cpp
@ -335,6 +349,10 @@ set(SLIC3R_GUI_SOURCES
Utils/WxFontUtils.hpp
Utils/WifiScanner.hpp
Utils/WifiScanner.cpp
Utils/Secrets.hpp
Utils/Secrets.cpp
Utils/PrusaConnect.hpp
Utils/PrusaConnect.cpp
)
find_package(NanoSVG REQUIRED)

View File

@ -588,7 +588,7 @@ PageWelcome::PageWelcome(ConfigWizard *parent)
{
welcome_text->Hide();
cbox_reset->Hide();
cbox_integrate->Hide();
cbox_integrate->Hide();
}
void PageWelcome::set_run_reason(ConfigWizard::RunReason run_reason)
@ -1536,11 +1536,13 @@ bool PageDownloader::on_finish_downloader() const
return m_downloader->on_finish();
}
bool DownloaderUtils::Worker::perform_register(const std::string& path_override/* = {}*/)
#ifdef __linux__
bool DownloaderUtils::Worker::perform_registration_linux = false;
#endif // __linux__
bool DownloaderUtils::Worker::perform_register(const std::string& path)
{
boost::filesystem::path aux_dest (GUI::into_u8(path_name()));
if (!path_override.empty())
aux_dest = boost::filesystem::path(path_override);
boost::filesystem::path aux_dest (path);
boost::system::error_code ec;
boost::filesystem::path chosen_dest = boost::filesystem::absolute(aux_dest, ec);
if(ec)
@ -1549,7 +1551,7 @@ bool DownloaderUtils::Worker::perform_register(const std::string& path_override/
if (chosen_dest.empty() || !boost::filesystem::is_directory(chosen_dest, ec) || ec) {
std::string err_msg = GUI::format("%1%\n\n%2%",_L("Chosen directory for downloads does not exist.") ,chosen_dest.string());
BOOST_LOG_TRIVIAL(error) << err_msg;
show_error(m_parent, err_msg);
show_error(/*m_parent*/ nullptr, err_msg);
return false;
}
BOOST_LOG_TRIVIAL(info) << "Downloader registration: Directory for downloads: " << chosen_dest.string();
@ -1613,12 +1615,12 @@ bool DownloaderUtils::Worker::on_finish() {
BOOST_LOG_TRIVIAL(debug) << "PageDownloader::on_finish_downloader ac_value " << ac_value << " downloader_checked " << downloader_checked;
if (ac_value && downloader_checked) {
// already registered but we need to do it again
if (!perform_register())
if (!perform_register(GUI::into_u8(path_name())))
return false;
app_config->set("downloader_url_registered", "1");
} else if (!ac_value && downloader_checked) {
// register
if (!perform_register())
if (!perform_register(GUI::into_u8(path_name())))
return false;
app_config->set("downloader_url_registered", "1");
} else if (ac_value && !downloader_checked) {
@ -2883,7 +2885,6 @@ bool ConfigWizard::priv::check_and_install_missing_materials(Technology technolo
const auto ask_and_select_default_materials = [this](const wxString &message, const std::set<const VendorProfile::PrinterModel*> &printer_models, Technology technology)
{
//wxMessageDialog msg(q, message, _L("Notice"), wxYES_NO);
MessageDialog msg(q, message, _L("Notice"), wxYES_NO);
if (msg.ShowModal() == wxID_YES)
select_default_materials_for_printer_models(technology, printer_models);
@ -3065,10 +3066,10 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
#ifdef __linux__
// Desktop integration on Linux
BOOST_LOG_TRIVIAL(debug) << "ConfigWizard::priv::apply_config integrate_desktop" << page_welcome->integrate_desktop() << " perform_registration_linux " << page_downloader->m_downloader->get_perform_registration_linux();
BOOST_LOG_TRIVIAL(debug) << "ConfigWizard::priv::apply_config integrate_desktop" << page_welcome->integrate_desktop() << " perform_registration_linux " << DownloaderUtils::Worker::perform_registration_linux;
if (page_welcome->integrate_desktop())
DesktopIntegrationDialog::perform_desktop_integration();
if (page_downloader->m_downloader->get_perform_registration_linux())
if (DownloaderUtils::Worker::perform_registration_linux)
DesktopIntegrationDialog::perform_downloader_desktop_integration();
#endif

View File

@ -31,9 +31,6 @@ namespace DownloaderUtils {
wxWindow* m_parent{ nullptr };
wxTextCtrl* m_input_path{ nullptr };
bool downloader_checked{ false };
#ifdef __linux__
bool perform_registration_linux{ false };
#endif // __linux__
void deregister();
@ -49,9 +46,9 @@ namespace DownloaderUtils {
void set_path_name(const std::string& name);
bool on_finish();
bool perform_register(const std::string& path_override = {});
static bool perform_register(const std::string& path);
#ifdef __linux__
bool get_perform_registration_linux() { return perform_registration_linux; }
static bool perform_registration_linux;
#endif // __linux__
};
}

View File

@ -170,7 +170,7 @@ void FileGet::priv::get_perform()
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->SetString(from_u8(m_filename));
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
}

View File

@ -17,6 +17,8 @@
#ifndef slic3r_FreqChangedParams_hpp_
#define slic3r_FreqChangedParams_hpp_
#include <memory>
#include "Event.hpp"
class wxButton;

View File

@ -2858,7 +2858,7 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
int keyCode = evt.GetKeyCode();
int ctrlMask = wxMOD_CONTROL;
int shiftMask = wxMOD_SHIFT;
if (keyCode == WXK_ESCAPE && (_deactivate_undo_redo_toolbar_items() || _deactivate_search_toolbar_item() || _deactivate_arrange_menu()))
if (keyCode == WXK_ESCAPE && (_deactivate_undo_redo_toolbar_items() || _deactivate_arrange_menu()))
return;
if (m_gizmos.on_char(evt)) {
@ -2890,14 +2890,6 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
#endif /* __APPLE__ */
post_event(SimpleEvent(EVT_GLTOOLBAR_COPY));
break;
#ifdef __APPLE__
case 'f':
case 'F':
#else /* __APPLE__ */
case WXK_CONTROL_F:
#endif /* __APPLE__ */
_activate_search_toolbar_item();
break;
#ifdef __APPLE__
case 'm':
case 'M':
@ -3354,10 +3346,9 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
}
}
// If the Search window or Undo/Redo list is opened,
// If Undo/Redo list is opened,
// update them according to the event
if (m_main_toolbar.is_item_pressed("search") ||
m_undoredo_toolbar.is_item_pressed("undo") ||
if (m_undoredo_toolbar.is_item_pressed("undo") ||
m_undoredo_toolbar.is_item_pressed("redo")) {
m_mouse_wheel = int((double)evt.GetWheelRotation() / (double)evt.GetWheelDelta());
return;
@ -3664,7 +3655,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
m_dirty = true;
}
else if (evt.LeftDown() || evt.RightDown() || evt.MiddleDown()) {
if (_deactivate_undo_redo_toolbar_items() || _deactivate_search_toolbar_item() || _deactivate_arrange_menu())
if (_deactivate_undo_redo_toolbar_items() || _deactivate_arrange_menu())
return;
// If user pressed left or right button we first check whether this happened
@ -4765,73 +4756,6 @@ bool GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x)
return action_taken;
}
// Getter for the const char*[] for the search list
static bool search_string_getter(int idx, const char** label, const char** tooltip)
{
const Search::OptionsSearcher& search_list = wxGetApp().searcher();
if (0 <= idx && (size_t)idx < search_list.size()) {
search_list[idx].get_marked_label_and_tooltip(label, tooltip);
return true;
}
return false;
}
bool GLCanvas3D::_render_search_list(float pos_x)
{
bool action_taken = false;
ImGuiWrapper* imgui = wxGetApp().imgui();
imgui->set_next_window_pos(pos_x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f);
std::string title = L("Search");
imgui->begin(_(title), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
int selected = -1;
bool edited = false;
float em = static_cast<float>(wxGetApp().em_unit());
#if ENABLE_RETINA_GL
em *= m_retina_helper->get_scale_factor();
#endif // ENABLE_RETINA_GL
// update searcher before show imGui search dialog on the plater, if printer technology or mode was changed
wxGetApp().check_and_update_searcher(wxGetApp().get_mode());
Search::OptionsSearcher& searcher = wxGetApp().searcher();
std::string& search_line = searcher.search_string();
char *s = new char[255];
strcpy(s, search_line.empty() ? _u8L("Enter a search term").c_str() : search_line.c_str());
imgui->search_list(ImVec2(45 * em, 30 * em), &search_string_getter, s,
wxGetApp().searcher().view_params,
selected, edited, m_mouse_wheel, wxGetApp().is_localized());
search_line = s;
delete [] s;
if (search_line == _u8L("Enter a search term"))
search_line.clear();
if (edited)
searcher.search();
if (selected >= 0) {
// selected == 9999 means that Esc kye was pressed
/*// revert commit https://github.com/prusa3d/PrusaSlicer/commit/91897589928789b261ca0dc735ffd46f2b0b99f2
if (selected == 9999)
action_taken = true;
else
sidebar.jump_to_option(selected);*/
if (selected != 9999) {
imgui->end(); // end imgui before the jump to option
wxGetApp().jump_to_option(selected);
return true;
}
action_taken = true;
}
imgui->end();
return action_taken;
}
bool GLCanvas3D::_render_arrange_menu(float pos_x)
{
m_arrange_settings_dialog.render(pos_x, m_main_toolbar.get_height());
@ -5395,30 +5319,6 @@ bool GLCanvas3D::_init_main_toolbar()
if (!m_main_toolbar.add_item(item))
return false;
/*
if (!m_main_toolbar.add_separator())
return false;
*/
item.name = "search";
item.icon_filename = "search_.svg";
item.tooltip = _u8L("Search") + " [" + GUI::shortkey_ctrl_prefix() + "F]";
item.sprite_id = 11;
item.left.toggable = true;
item.left.render_callback = [this](float left, float right, float, float) {
if (m_canvas != nullptr) {
if (!m_canvas->HasFocus())
m_canvas->SetFocus();
if (_render_search_list(0.5f * (left + right)))
_deactivate_search_toolbar_item();
}
};
item.left.action_callback = GLToolbarItem::Default_Action_Callback;
item.visibility_callback = GLToolbarItem::Default_Visibility_Callback;
item.enabling_callback = [this]()->bool { return m_gizmos.get_current_type() == GLGizmosManager::Undefined; };
if (!m_main_toolbar.add_item(item))
return false;
if (!m_main_toolbar.add_separator())
return false;
@ -7653,11 +7553,6 @@ bool GLCanvas3D::_deactivate_undo_redo_toolbar_items()
return false;
}
bool GLCanvas3D::is_search_pressed() const
{
return m_main_toolbar.is_item_pressed("search");
}
bool GLCanvas3D::_deactivate_arrange_menu()
{
if (m_main_toolbar.is_item_pressed("arrange")) {
@ -7668,26 +7563,6 @@ bool GLCanvas3D::_deactivate_arrange_menu()
return false;
}
bool GLCanvas3D::_deactivate_search_toolbar_item()
{
if (is_search_pressed()) {
m_main_toolbar.force_left_action(m_main_toolbar.get_item_id("search"), *this);
return true;
}
return false;
}
bool GLCanvas3D::_activate_search_toolbar_item()
{
if (!m_main_toolbar.is_item_pressed("search")) {
m_main_toolbar.force_left_action(m_main_toolbar.get_item_id("search"), *this);
return true;
}
return false;
}
bool GLCanvas3D::_deactivate_collapse_toolbar_items()
{
GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar();

View File

@ -780,7 +780,6 @@ public:
bool is_layers_editing_enabled() const;
bool is_layers_editing_allowed() const;
bool is_search_pressed() const;
void reset_layer_height_profile();
void adaptive_layer_height_profile(float quality_factor);
@ -1064,7 +1063,6 @@ private:
void _render_sla_slices();
void _render_selection_sidebar_hints();
bool _render_undo_redo_stack(const bool is_undo, float pos_x);
bool _render_search_list(float pos_x);
bool _render_arrange_menu(float pos_x);
void _render_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type);
// render thumbnail using an off-screen framebuffer
@ -1113,8 +1111,6 @@ private:
void _update_selection_from_hover();
bool _deactivate_undo_redo_toolbar_items();
bool _deactivate_search_toolbar_item();
bool _activate_search_toolbar_item();
bool _deactivate_collapse_toolbar_items();
bool _deactivate_arrange_menu();

View File

@ -98,9 +98,13 @@
#include "Downloader.hpp"
#include "PhysicalPrinterDialog.hpp"
#include "WifiConfigDialog.hpp"
#include "UserAccount.hpp"
#include "WebViewDialog.hpp"
#include "LoginDialog.hpp"
#include "BitmapCache.hpp"
#include "Notebook.hpp"
//#include "Notebook.hpp"
#include "TopBar.hpp"
#ifdef __WXMSW__
#include <dbt.h>
@ -1109,6 +1113,10 @@ void GUI_App::jump_to_option(const std::string& composite_key)
void GUI_App::show_search_dialog()
{
// To avoid endless loop caused by mutual lose focuses from serch_input and search_dialog
// invoke killFocus for serch_input by set focus to tab_panel
GUI::wxGetApp().tab_panel()->SetFocus();
check_and_update_searcher(get_mode());
m_searcher->show_dialog();
}
@ -1401,6 +1409,8 @@ bool GUI_App::on_init_inner()
update_mode(); // update view mode after fix of the object_list size
show_printer_webview_tab();
#ifdef __APPLE__
other_instance_message_handler()->bring_instance_forward();
#endif //__APPLE__
@ -1516,16 +1526,16 @@ void GUI_App::init_ui_colours()
m_mode_palette = get_mode_default_palette();
bool is_dark_mode = dark_mode();
#ifdef _WIN32
//#ifdef _WIN32
m_color_label_default = is_dark_mode ? wxColour(250, 250, 250): wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT);
m_color_highlight_default = is_dark_mode ? wxColour(78, 78, 78) : wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT);
m_color_hovered_btn_label = is_dark_mode ? wxColour(253, 111, 40) : wxColour(252, 77, 1);
m_color_default_btn_label = is_dark_mode ? wxColour(255, 181, 100): wxColour(203, 61, 0);
m_color_selected_btn_bg = is_dark_mode ? wxColour(95, 73, 62) : wxColour(228, 220, 216);
#else
m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
#endif
//#else
// m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
//#endif
m_color_window_default = is_dark_mode ? wxColour(43, 43, 43) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
}
@ -1561,12 +1571,13 @@ void GUI_App::update_label_colours()
tab->update_label_colours();
}
#ifdef _WIN32
#if 0//def _WIN32
static bool is_focused(HWND hWnd)
{
HWND hFocusedWnd = ::GetFocus();
return hFocusedWnd && hWnd == hFocusedWnd;
}
#endif
static bool is_default(wxWindow* win)
{
@ -1576,7 +1587,6 @@ static bool is_default(wxWindow* win)
return win == tlw->GetDefaultItem();
}
#endif
void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool just_font/* = false*/)
{
@ -1589,6 +1599,7 @@ void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool ju
highlited = true;
}
// button marking
if (!dynamic_cast<TopBarItemsCtrl*>(window->GetParent())) // don't marking the button if it is from TopBar
{
auto mark_button = [this, btn, highlited](const bool mark) {
if (btn->GetLabel().IsEmpty())
@ -1601,12 +1612,12 @@ void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool ju
// hovering
btn->Bind(wxEVT_ENTER_WINDOW, [mark_button](wxMouseEvent& event) { mark_button(true); event.Skip(); });
btn->Bind(wxEVT_LEAVE_WINDOW, [mark_button, btn](wxMouseEvent& event) { mark_button(is_focused(btn->GetHWND())); event.Skip(); });
btn->Bind(wxEVT_LEAVE_WINDOW, [mark_button, btn](wxMouseEvent& event) { mark_button(btn->HasFocus()); event.Skip(); });
// focusing
btn->Bind(wxEVT_SET_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(true); event.Skip(); });
btn->Bind(wxEVT_KILL_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(false); event.Skip(); });
is_focused_button = is_focused(btn->GetHWND());
is_focused_button = btn->HasFocus();// is_focused(btn->GetHWND());
is_default_button = is_default(btn);
if (is_focused_button || is_default_button)
mark_button(is_focused_button);
@ -1824,7 +1835,7 @@ bool GUI_App::suppress_round_corners() const
wxSize GUI_App::get_min_size(wxWindow* display_win) const
{
wxSize min_size(76*m_em_unit, 49 * m_em_unit);
wxSize min_size(120 * m_em_unit, 49 * m_em_unit);
const wxDisplay display = wxDisplay(display_win);
wxRect display_rect = display.GetGeometry();
@ -2468,10 +2479,8 @@ void GUI_App::update_mode()
{
sidebar().update_mode();
#ifdef _WIN32 //_MSW_DARK_MODE
if (!wxGetApp().tabs_as_menu())
dynamic_cast<Notebook*>(mainframe->m_tabpanel)->UpdateMode();
#endif
dynamic_cast<TopBar*>(mainframe->m_tabpanel)->UpdateMode();
for (auto tab : tabs_list)
tab->update_mode();
@ -2480,7 +2489,7 @@ void GUI_App::update_mode()
plater()->canvas3D()->update_gizmos_on_off_state();
}
void GUI_App::add_config_menu(wxMenuBar *menu)
wxMenu* GUI_App::get_config_menu()
{
auto local_menu = new wxMenu();
wxWindowID config_id_base = wxWindow::NewControlId(int(ConfigMenuCnt));
@ -2507,20 +2516,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
"\tCtrl+P",
#endif
_L("Application preferences"));
wxMenu* mode_menu = nullptr;
if (is_editor()) {
local_menu->AppendSeparator();
mode_menu = new wxMenu();
mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _L("Simple"), _L("Simple View Mode"));
// mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _L("Advanced"), _L("Advanced View Mode"));
mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _CTX("Advanced", "Mode"), _L("Advanced View Mode"));
mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _L("Expert"), _L("Expert View Mode"));
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comSimple) evt.Check(true); }, config_id_base + ConfigMenuModeSimple);
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comAdvanced) evt.Check(true); }, config_id_base + ConfigMenuModeAdvanced);
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { if (get_mode() == comExpert) evt.Check(true); }, config_id_base + ConfigMenuModeExpert);
local_menu->AppendSubMenu(mode_menu, _L("Mode"), wxString::Format(_L("%s View Mode"), SLIC3R_APP_NAME));
}
local_menu->AppendSeparator();
local_menu->Append(config_id_base + ConfigMenuLanguage, _L("&Language"));
if (is_editor()) {
@ -2646,16 +2642,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
}
});
using std::placeholders::_1;
if (mode_menu != nullptr) {
auto modfn = [this](int mode, wxCommandEvent&) { if (get_mode() != mode) save_mode(mode); };
mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comSimple, _1), config_id_base + ConfigMenuModeSimple);
mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comAdvanced, _1), config_id_base + ConfigMenuModeAdvanced);
mode_menu->Bind(wxEVT_MENU, std::bind(modfn, comExpert, _1), config_id_base + ConfigMenuModeExpert);
}
menu->Append(local_menu, _L("&Configuration"));
return local_menu;
}
void GUI_App::open_preferences(const std::string& highlight_option /*= std::string()*/, const std::string& tab_name/*= std::string()*/)
@ -2690,7 +2677,7 @@ void GUI_App::open_preferences(const std::string& highlight_option /*= std::stri
if (mainframe->preferences_dialog->settings_layout_changed()) {
// hide full main_sizer for mainFrame
mainframe->GetSizer()->Show(false);
mainframe->update_layout();
mainframe->update_layout();
mainframe->select_tab(size_t(0));
}
}
@ -3016,7 +3003,15 @@ void GUI_App::MacOpenURL(const wxString& url)
BOOST_LOG_TRIVIAL(error) << "Recieved command to open URL, but it is not allowed in app configuration. URL: " << url;
return;
}
start_download(into_u8(url));
std::string narrow_url = into_u8(url);
if (boost::starts_with(narrow_url, "prusaslicer://open?file=")) {
start_download(std::move(narrow_url));
} else if (boost::starts_with(narrow_url, "prusaslicer://login")) {
plater()->get_user_account()->on_login_code_recieved(std::move(narrow_url));
} else {
BOOST_LOG_TRIVIAL(error) << "MacOpenURL recieved improper URL: " << url;
}
}
#endif /* __APPLE */
@ -3147,6 +3142,15 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage
{
wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
if (!plater()->get_user_account()->is_logged()) {
m_login_dialog = std::make_unique<LoginDialog>(mainframe, plater()->get_user_account());
m_login_dialog->ShowModal();
mainframe->RemoveChild(m_login_dialog.get());
m_login_dialog->Destroy();
// Destructor does not call Destroy
m_login_dialog.reset();
}
if (reason == ConfigWizard::RR_USER) {
// Cancel sync before starting wizard to prevent two downloads at same time
preset_updater->cancel_sync();
@ -3176,6 +3180,14 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage
return res;
}
void GUI_App::update_login_dialog()
{
if (!m_login_dialog) {
return;
}
m_login_dialog->update_account();
}
void GUI_App::show_desktop_integration_dialog()
{
#ifdef __linux__
@ -3199,7 +3211,7 @@ void GUI_App::show_downloader_registration_dialog()
auto downloader_worker = new DownloaderUtils::Worker(nullptr);
downloader_worker->perform_register(app_config->get("url_downloader_dest"));
#ifdef __linux__
if (downloader_worker->get_perform_registration_linux())
if (DownloaderUtils::Worker::perform_registration_linux)
DesktopIntegrationDialog::perform_downloader_desktop_integration();
#endif // __linux__
} else {
@ -3431,6 +3443,18 @@ bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* pa
return launch && wxLaunchDefaultBrowser(url, flags);
}
bool GUI_App::open_login_browser_with_dialog(const wxString& url, wxWindow* parent/* = nullptr*/, int flags/* = 0*/)
{
bool auth_login_dialog_confirmed = app_config->get_bool("auth_login_dialog_confirmed");
if (!auth_login_dialog_confirmed) {
RichMessageDialog dialog(parent, _L("Open default browser with Prusa Account Log in page?\n(On Yes, You will not be asked again.)"), _L("PrusaSlicer: Open Log in page"), wxICON_QUESTION | wxYES_NO);
if (dialog.ShowModal() != wxID_YES)
return false;
app_config->set("auth_login_dialog_confirmed", "1");
}
return wxLaunchDefaultBrowser(url, flags);
}
// static method accepting a wxWindow object as first parameter
// void warning_catcher{
// my($self, $message_dialog) = @_;
@ -3626,5 +3650,92 @@ void GUI_App::open_wifi_config_dialog(bool forced, const wxString& drive_path/*
m_wifi_config_dialog_shown = false;
}
bool GUI_App::select_printer_from_connect(const Preset* preset)
{
assert(preset);
bool is_installed{ false };
// When physical printer is selected, it somehow remains selected in printer tab
// TabPresetComboBox::update() looks at physical_printers and if some has selected = true, it overrides the selection.
// This might be, because OnSelect event callback is not triggered
if(preset_bundle->physical_printers.get_selected_printer_config()) {
preset_bundle->physical_printers.unselect_printer();
}
if (!preset->is_visible) {
size_t preset_id = preset_bundle->printers.get_preset_idx_by_name(preset->name);
assert(preset_id != size_t(-1));
preset_bundle->printers.select_preset(preset_id);
is_installed = true;
}
get_tab(Preset::Type::TYPE_PRINTER)->select_preset(preset->name);
return is_installed;
}
void GUI_App::handle_connect_request_printer_pick(std::string msg)
{
BOOST_LOG_TRIVIAL(error) << "Handling web request: " << msg;
// return to plater
this->mainframe->select_tab(size_t(0));
// parse message
std::vector<std::string> compatible_printers;
plater()->get_user_account()->fill_compatible_printers_from_json(msg, compatible_printers);
std::string model_name;
if (compatible_printers.empty()) {
// TODO: This should go away when compatible printers gives right information.
model_name = plater()->get_user_account()->get_model_from_json(msg);
} else {
model_name = compatible_printers.front();
}
std::string nozzle = plater()->get_user_account()->get_nozzle_from_json(msg);
assert(!model_name.empty());
if (model_name.empty())
return;
// select printer
const Preset* preset = preset_bundle->printers.find_system_preset_by_model_and_variant(model_name, nozzle);
bool is_installed = preset && select_printer_from_connect(preset);
// notification
std::string out = preset ?
(is_installed ? GUI::format(_L("Installed and Select Printer:\n%1%"), preset->name) :
GUI::format(_L("Select Printer:\n%1%"), preset->name) ):
GUI::format(_L("Printer not found:\n%1%"), model_name);
this->plater()->get_notification_manager()->close_notification_of_type(NotificationType::UserAccountID);
this->plater()->get_notification_manager()->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::ImportantNotificationLevel, out);
}
void GUI_App::show_printer_webview_tab()
{
//bool show, const DynamicPrintConfig& dpc
if (DynamicPrintConfig* dpc = preset_bundle->physical_printers.get_selected_printer_config(); dpc == nullptr) {
this->mainframe->select_tab(size_t(0));
mainframe->remove_printer_webview_tab();
} else {
std::string url = dpc->opt_string("print_host");
if (url.find("http://") != 0 && url.find("https://") != 0) {
url = "http://" + url;
}
// set password / api key
if (dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(dpc->option("printhost_authorization_type"))->value == AuthorizationType::atKeyPassword) {
mainframe->set_printer_webview_api_key(dpc->opt_string("printhost_apikey"));
}
#if 0 // The user password authentication is not working in prusa link as of now.
else {
mainframe->set_printer_webview_credentials(dpc->opt_string("printhost_user"), dpc->opt_string("printhost_password"));
}
#endif // 0
// add printer or change url
if (mainframe->get_printer_webview_tab_added()) {
mainframe->set_printer_webview_tab_url(from_u8(url));
} else {
mainframe->add_printer_webview_tab(from_u8(url));
}
}
}
} // GUI
} //Slic3r

View File

@ -59,7 +59,7 @@ class NotificationManager;
class Downloader;
struct GUI_InitParams;
class GalleryDialog;
class LoginDialog;
enum FileType
@ -142,12 +142,13 @@ private:
wxColour m_color_label_sys;
wxColour m_color_label_default;
wxColour m_color_window_default;
#ifdef _WIN32
//#ifdef _WIN32
wxColour m_color_highlight_label_default;
wxColour m_color_hovered_btn_label;
wxColour m_color_default_btn_label;
wxColour m_color_highlight_default;
wxColour m_color_selected_btn_bg;
#ifdef _WIN32
bool m_force_colors_update { false };
#endif
std::vector<std::string> m_mode_palette;
@ -247,7 +248,7 @@ public:
std::vector<wxColour> get_mode_palette();
void set_mode_palette(const std::vector<wxColour> &palette);
#ifdef _WIN32
//#ifdef _WIN32
const wxColour& get_label_highlight_clr() { return m_color_highlight_label_default; }
const wxColour& get_highlight_default_clr() { return m_color_highlight_default; }
const wxColour& get_color_hovered_btn_label() { return m_color_hovered_btn_label; }
@ -256,7 +257,7 @@ public:
#ifdef _MSW_DARK_MODE
void force_menu_update();
#endif //_MSW_DARK_MODE
#endif
//#endif
const wxFont& small_font() { return m_small_font; }
const wxFont& bold_font() { return m_bold_font; }
@ -293,7 +294,7 @@ public:
bool save_mode(const /*ConfigOptionMode*/int mode) ;
void update_mode();
void add_config_menu(wxMenuBar *menu);
wxMenu* get_config_menu();
bool has_unsaved_preset_changes() const;
bool has_current_preset_changes() const;
void update_saved_preset_from_current_preset();
@ -317,6 +318,7 @@ public:
// Calls wxLaunchDefaultBrowser if user confirms in dialog.
// Add "Rememeber my choice" checkbox to question dialog, when it is forced or a "suppress_hyperlinks" option has empty value
bool open_browser_with_warning_dialog(const wxString& url, wxWindow* parent = nullptr, bool force_remember_choice = true, int flags = 0);
bool open_login_browser_with_dialog(const wxString& url, wxWindow* parent = nullptr, int flags = 0);
#ifdef __APPLE__
void OSXStoreOpenFiles(const wxArrayString &files) override;
// wxWidgets override to get an event on open files.
@ -369,6 +371,7 @@ public:
void open_web_page_localized(const std::string &http_address);
bool may_switch_to_SLA_preset(const wxString& caption);
bool run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME);
void update_login_dialog();
void show_desktop_integration_dialog();
void show_downloader_registration_dialog();
@ -397,6 +400,26 @@ public:
void open_wifi_config_dialog(bool forced, const wxString& drive_path = {});
bool get_wifi_config_dialog_shown() const { return m_wifi_config_dialog_shown; }
void request_login(bool show_user_info = false) {}
bool check_login() { return false; }
void get_login_info() {}
bool is_user_login() { return true; }
void request_user_login(int online_login) {}
void request_user_logout() {}
int request_user_unbind(std::string dev_id) { return 0; }
void handle_connect_request_printer_pick(std::string cmd);
void show_printer_webview_tab();
// return true if preset vas invisible and we have to installed it to make it selectable
bool select_printer_from_connect(const Preset* printer_preset);
void handle_script_message(std::string msg) {}
void request_model_download(std::string import_json) {}
void download_project(std::string project_id) {}
void request_project_download(std::string project_id) {}
void request_open_project(std::string project_id) {}
void request_remove_project(std::string project_id) {}
private:
bool on_init_inner();
void init_app_config();
@ -419,6 +442,12 @@ private:
void app_version_check(bool from_user);
bool m_wifi_config_dialog_shown { false };
bool m_wifi_config_dialog_was_declined { false };
// change to vector of items when adding more items that require update
//wxMenuItem* m_login_config_menu_item { nullptr };
std::map< ConfigMenuIDs, wxMenuItem*> m_config_menu_updatable_items;
std::unique_ptr<LoginDialog> m_login_dialog;
};
DECLARE_APP(GUI_App)

View File

@ -1463,6 +1463,8 @@ void MenuFactory::sys_color_changed()
void MenuFactory::sys_color_changed(wxMenuBar* menubar)
{
if (!menubar)
return;
for (size_t id = 0; id < menubar->GetMenuCount(); id++) {
wxMenu* menu = menubar->GetMenu(id);
sys_color_changed_menu(menu);

View File

@ -378,6 +378,7 @@ namespace GUI {
wxDEFINE_EVENT(EVT_LOAD_MODEL_OTHER_INSTANCE, LoadFromOtherInstanceEvent);
wxDEFINE_EVENT(EVT_START_DOWNLOAD_OTHER_INSTANCE, StartDownloadOtherInstanceEvent);
wxDEFINE_EVENT(EVT_LOGIN_OTHER_INSTANCE, LoginOtherInstanceEvent);
wxDEFINE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent);
void OtherInstanceMessageHandler::init(wxEvtHandler* callback_evt_handler)
@ -520,6 +521,9 @@ void OtherInstanceMessageHandler::handle_message(const std::string& message)
else if (it->rfind("prusaslicer://open?file=", 0) == 0)
#endif
downloads.emplace_back(*it);
else if (it->rfind("prusaslicer://login", 0) == 0) {
wxPostEvent(m_callback_evt_handler, LoginOtherInstanceEvent(GUI::EVT_LOGIN_OTHER_INSTANCE, std::string(*it)));
}
}
if (! paths.empty()) {
//wxEvtHandler* evt_handler = wxGetApp().plater(); //assert here?

View File

@ -48,8 +48,10 @@ class MainFrame;
using LoadFromOtherInstanceEvent = Event<std::vector<boost::filesystem::path>>;
using StartDownloadOtherInstanceEvent = Event<std::vector<std::string>>;
using LoginOtherInstanceEvent = Event<std::string>;
wxDECLARE_EVENT(EVT_LOAD_MODEL_OTHER_INSTANCE, LoadFromOtherInstanceEvent);
wxDECLARE_EVENT(EVT_START_DOWNLOAD_OTHER_INSTANCE, StartDownloadOtherInstanceEvent);
wxDECLARE_EVENT(EVT_LOGIN_OTHER_INSTANCE, LoginOtherInstanceEvent);
using InstanceGoToFrontEvent = SimpleEvent;
wxDECLARE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent);

View File

@ -0,0 +1,95 @@
#include "LoginDialog.hpp"
#include "GUI_App.hpp"
#include "wxExtensions.hpp"
#include "GUI.hpp"
#include "I18N.hpp"
#include "format.hpp"
namespace Slic3r {
namespace GUI {
LoginDialog::LoginDialog(wxWindow* parent, UserAccount* user_account)
// TRN: This is the dialog title.
: DPIDialog(parent, wxID_ANY, _L("Prusa Account"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
, p_user_account(user_account)
{
const int em = wxGetApp().em_unit();
bool logged = p_user_account->is_logged();
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
// sizer with black border
wxStaticBoxSizer* static_box_sizer = new wxStaticBoxSizer(wxVERTICAL, this, _L("Log into your Prusa Account"));
static_box_sizer->SetMinSize(wxSize(em * 30, em * 15));
// avatar
boost::filesystem::path path = p_user_account->get_avatar_path(logged);
ScalableBitmap logo(this, path, wxSize(em * 10, em * 10));
m_avatar_bitmap = new wxStaticBitmap(this, wxID_ANY, logo.bmp(), wxDefaultPosition, wxDefaultSize);
static_box_sizer->Add(m_avatar_bitmap, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 10);
// username
const wxString username = GUI::format_wxstr("%1%", logged ? from_u8(p_user_account->get_username()) : _L("Anonymous"));
m_username_label = new wxStaticText(this, wxID_ANY, username, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER);
m_username_label->SetFont(m_username_label->GetFont().Bold());
static_box_sizer->Add(m_username_label, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 5);
// login button
m_login_button_id = NewControlId();
m_login_button = new wxButton(this, m_login_button_id, logged ? _L("Log out") : _L("Log in"));
static_box_sizer->Add(m_login_button, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 10);
// TODO: why is m_login_button always hovered?
main_sizer->Add(static_box_sizer, 1, wxEXPAND | wxALL, 10);
// continue button
m_continue_button = new wxButton(this, wxID_OK, logged ? _L("Continue") : _L("Continue without Prusa Account"));
main_sizer->Add(m_continue_button, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 10);
SetSizerAndFit(main_sizer);
m_login_button->Bind(wxEVT_BUTTON, [user_account = p_user_account](wxCommandEvent& event) {
if (!user_account->is_logged())
user_account->do_login();
else
user_account->do_logout();
});
wxGetApp().UpdateDlgDarkUI(this);
SetFocus();
}
LoginDialog::~LoginDialog()
{
}
void LoginDialog::update_account()
{
bool logged = p_user_account->is_logged();
const wxString username = GUI::format_wxstr("%1%", logged ? from_u8(p_user_account->get_username()) : _L("Anonymous"));
m_username_label->SetLabel(username);
boost::filesystem::path path = p_user_account->get_avatar_path(logged);
if (boost::filesystem::exists(path)) {
const int em = wxGetApp().em_unit();
ScalableBitmap logo(this, path, wxSize(em * 10, em * 10));
m_avatar_bitmap->SetBitmap(logo.bmp());
}
m_login_button->SetLabel(logged ? _L("Log out") : _L("Log in"));
m_continue_button->SetLabel(logged ? _L("Continue") : _L("Continue without Prusa Account"));
// TODO: resize correctly m_continue_button
//m_continue_button->Fit();
Fit();
Refresh();
}
void LoginDialog::on_dpi_changed(const wxRect& suggested_rect)
{
SetFont(wxGetApp().normal_font());
const int em = em_unit();
msw_buttons_rescale(this, em, { wxID_OK, m_login_button_id});
Fit();
Refresh();
}
}}// Slicer::GUI

View File

@ -0,0 +1,38 @@
#ifndef slic3r_LoginDialog_hpp_
#define slic3r_LoginDialog_hpp_
#include "UserAccount.hpp"
#include "GUI_Utils.hpp"
#include <wx/button.h>
#include <wx/textctrl.h>
#include <wx/stattext.h>
#include <wx/statbmp.h>
namespace Slic3r {
namespace GUI {
class RemovableDriveManager;
class LoginDialog : public DPIDialog
{
public:
LoginDialog(wxWindow* parent, UserAccount* user_account);
~LoginDialog();
void update_account();
private:
UserAccount* p_user_account;
wxStaticText* m_username_label;
wxStaticBitmap* m_avatar_bitmap;
wxButton* m_login_button;
int m_login_button_id{ wxID_ANY };
wxButton* m_continue_button;
protected:
void on_dpi_changed(const wxRect& suggested_rect) override;
void on_sys_color_changed() override {}
};
}} // Slicer::GUI
#endif

View File

@ -20,9 +20,13 @@
#include <wx/menu.h>
#include <wx/progdlg.h>
#include <wx/tooltip.h>
#include <wx/accel.h>
//#include <wx/glcanvas.h>
#include <wx/filename.h>
#include <wx/debug.h>
#if wxUSE_SECRETSTORE
#include <wx/secretstore.h>
#endif
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/replace.hpp>
@ -53,12 +57,14 @@
#include "GUI_App.hpp"
#include "UnsavedChangesDialog.hpp"
#include "MsgDialog.hpp"
#include "Notebook.hpp"
//#include "Notebook.hpp"
#include "TopBar.hpp"
#include "GUI_Factories.hpp"
#include "GUI_ObjectList.hpp"
#include "GalleryDialog.hpp"
#include "NotificationManager.hpp"
#include "Preferences.hpp"
#include "WebViewDialog.hpp"
#ifdef _WIN32
#include <dbt.h>
@ -175,19 +181,34 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
else
init_menubar_as_editor();
#ifndef __APPLE__
std::vector<wxAcceleratorEntry*>& entries_cache = accelerator_entries_cache();
assert(entries_cache.size() + 6 < 100);
wxAcceleratorEntry entries[100];
int id = 0;
for (const auto* entry : entries_cache)
entries[id++].Set(entry->GetFlags(), entry->GetKeyCode(), entry->GetMenuItem()->GetId());
#if _WIN32
// This is needed on Windows to fake the CTRL+# of the window menu when using the numpad
wxAcceleratorEntry entries[6];
entries[0].Set(wxACCEL_CTRL, WXK_NUMPAD1, wxID_HIGHEST + 1);
entries[1].Set(wxACCEL_CTRL, WXK_NUMPAD2, wxID_HIGHEST + 2);
entries[2].Set(wxACCEL_CTRL, WXK_NUMPAD3, wxID_HIGHEST + 3);
entries[3].Set(wxACCEL_CTRL, WXK_NUMPAD4, wxID_HIGHEST + 4);
entries[4].Set(wxACCEL_CTRL, WXK_NUMPAD5, wxID_HIGHEST + 5);
entries[5].Set(wxACCEL_CTRL, WXK_NUMPAD6, wxID_HIGHEST + 6);
wxAcceleratorTable accel(6, entries);
SetAcceleratorTable(accel);
entries[id++].Set(wxACCEL_CTRL, WXK_NUMPAD1, wxID_HIGHEST + 1);
entries[id++].Set(wxACCEL_CTRL, WXK_NUMPAD2, wxID_HIGHEST + 2);
entries[id++].Set(wxACCEL_CTRL, WXK_NUMPAD3, wxID_HIGHEST + 3);
entries[id++].Set(wxACCEL_CTRL, WXK_NUMPAD4, wxID_HIGHEST + 4);
entries[id++].Set(wxACCEL_CTRL, WXK_NUMPAD5, wxID_HIGHEST + 5);
entries[id++].Set(wxACCEL_CTRL, WXK_NUMPAD6, wxID_HIGHEST + 6);
#endif // _WIN32
wxAcceleratorTable accel(id, entries);
SetAcceleratorTable(accel);
// clear cache with wxAcceleratorEntry, because it's no need anymore
for (auto entry : entries_cache)
delete entry;
entries_cache.clear();
#endif
// set default tooltip timer in msec
// SetAutoPop supposedly accepts long integers but some bug doesn't allow for larger values
// (SetAutoPop is not available on GTK.)
@ -360,6 +381,8 @@ static void add_tabs_as_menu(wxMenuBar* bar, MainFrame* main_frame, wxWindow* ba
void MainFrame::show_tabs_menu(bool show)
{
if (!m_menubar)
return;
if (show)
append_tab_menu_items_to_menubar(m_menubar, plater() ? plater()->printer_technology() : ptFFF, true);
else
@ -451,13 +474,15 @@ void MainFrame::update_layout()
case ESettingsLayout::Old:
{
m_plater->Reparent(m_tabpanel);
#ifdef _MSW_DARK_MODE
m_plater->Layout();
#ifdef _WIN32
if (!wxGetApp().tabs_as_menu())
dynamic_cast<Notebook*>(m_tabpanel)->InsertPage(0, m_plater, _L("Plater"), std::string("plater"), true);
else
#endif
dynamic_cast<TopBar*>(m_tabpanel)->InsertPage(0, m_plater, _L("Plater"), std::string("plater"), true);
#ifdef _WIN32
else
m_tabpanel->InsertPage(0, m_plater, _L("Plater"));
#endif
m_main_sizer->Add(m_tabpanel, 1, wxEXPAND | wxTOP, 1);
m_plater->Show();
m_tabpanel->Show();
@ -477,12 +502,14 @@ void MainFrame::update_layout()
m_tabpanel->Hide();
m_main_sizer->Add(m_tabpanel, 1, wxEXPAND);
m_plater_page = new wxPanel(m_tabpanel);
#ifdef _MSW_DARK_MODE
#ifdef _WIN32
if (!wxGetApp().tabs_as_menu())
dynamic_cast<Notebook*>(m_tabpanel)->InsertPage(0, m_plater_page, _L("Plater"), std::string("plater"), true);
else
#endif
dynamic_cast<TopBar*>(m_tabpanel)->InsertPage(0, m_plater_page, _L("Plater"), std::string("plater"), true);
#ifdef _WIN32
else
m_tabpanel->InsertPage(0, m_plater_page, _L("Plater")); // empty panel just for Plater tab */
#endif
m_plater->Show();
break;
}
@ -494,7 +521,7 @@ void MainFrame::update_layout()
m_tabpanel->Show();
m_plater->Show();
#ifdef _MSW_DARK_MODE
#ifdef _WIN32
if (wxGetApp().tabs_as_menu())
show_tabs_menu(false);
#endif
@ -511,11 +538,6 @@ void MainFrame::update_layout()
}
}
#ifdef _MSW_DARK_MODE
// Sizer with buttons for mode changing
m_plater->sidebar().show_mode_sizer(wxGetApp().tabs_as_menu() || m_layout != ESettingsLayout::Old);
#endif
#ifdef __WXMSW__
if (update_scaling_state != State::noUpdate)
{
@ -559,10 +581,6 @@ void MainFrame::update_layout()
// m_tabpanel->SetMinSize(size);
// }
//#endif
#ifdef __APPLE__
m_plater->sidebar().change_top_border_for_mode_sizer(m_layout != ESettingsLayout::Old);
#endif
Layout();
Thaw();
@ -691,22 +709,14 @@ void MainFrame::init_tabpanel()
// wxGetApp().UpdateDarkUI(m_tabpanel);
}
else
m_tabpanel = new Notebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME, true);
#else
m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME);
#endif
wxGetApp().UpdateDarkUI(m_tabpanel);
m_tabpanel = new TopBar(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME);
m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font());
m_tabpanel->Hide();
m_settings_dialog.set_tabpanel(m_tabpanel);
#ifdef __WXMSW__
m_tabpanel->Bind(wxEVT_BOOKCTRL_PAGE_CHANGED, [this](wxBookCtrlEvent& e) {
#else
m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxBookCtrlEvent& e) {
#endif
if (int old_selection = e.GetOldSelection();
old_selection != wxNOT_FOUND && old_selection < static_cast<int>(m_tabpanel->GetPageCount())) {
Tab* old_tab = dynamic_cast<Tab*>(m_tabpanel->GetPage(old_selection));
@ -721,6 +731,10 @@ void MainFrame::init_tabpanel()
if (panel == nullptr || (tab != nullptr && !tab->supports_printer_technology(m_plater->printer_technology())))
return;
// temporary fix - WebViewPanel is not inheriting from Tab -> would jump to select Plater
if (panel && !tab)
return;
auto& tabs_list = wxGetApp().tabs_list;
if (tab && std::find(tabs_list.begin(), tabs_list.end(), tab) != tabs_list.end()) {
// On GTK, the wxEVT_NOTEBOOK_PAGE_CHANGED event is triggered
@ -736,6 +750,13 @@ void MainFrame::init_tabpanel()
select_tab(size_t(0)); // select Plater
});
if (wxGetApp().is_editor()) {
//m_webview = new WebViewPanel(m_tabpanel);
//m_tabpanel->AddPage(m_webview, "web", "cog"/*, "tab_home_active"*/);
//m_param_panel = new ParamsPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL);
}
m_plater = new Plater(this, this);
m_plater->Hide();
@ -820,6 +841,94 @@ void MainFrame::create_preset_tabs()
add_created_tab(new TabSLAPrint(m_tabpanel), "cog");
add_created_tab(new TabSLAMaterial(m_tabpanel), "resin");
add_created_tab(new TabPrinter(m_tabpanel), wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF ? "printer" : "sla_printer");
m_connect_webview = new ConnectWebViewPanel(m_tabpanel);
m_printer_webview = new PrinterWebViewPanel(m_tabpanel, L"");
// new created tabs have to be hidden by default
m_connect_webview->Hide();
m_printer_webview->Hide();
}
void MainFrame::add_connect_webview_tab()
{
if (m_connect_webview_added) {
return;
} // parameters of InsertPage (to prevent ambigous overloaded function)
// insert to positon 4, if physical printer is already added, it moves to 5
// order of tabs: Plater - Print Settings - Filaments - Printers - Prusa Connect - Prusa Link
size_t n = 4;
wxWindow* page = m_connect_webview;
const wxString text(L"Prusa Connect");
const std::string bmp_name = "";
bool bSelect = false;
dynamic_cast<TopBar*>(m_tabpanel)->InsertPage(n, page, text, bmp_name, bSelect);
m_connect_webview->load_default_url_delayed();
m_connect_webview_added = true;
}
void MainFrame::remove_connect_webview_tab()
{
if (!m_connect_webview_added) {
return;
}
// connect tab should always be at position 4
if (m_tabpanel->GetSelection() == 4)
m_tabpanel->SetSelection(0);
dynamic_cast<TopBar*>(m_tabpanel)->RemovePage(4);
m_connect_webview_added = false;
m_connect_webview->logout();
}
void MainFrame::add_printer_webview_tab(const wxString& url)
{
if (m_printer_webview_added) {
set_printer_webview_tab_url(url);
return;
}
m_printer_webview_added = true;
// add as the last (rightmost) panel
dynamic_cast<TopBar*>(m_tabpanel)->AddPage(m_printer_webview, L"Physical Printer", "", false);
m_printer_webview->set_default_url(url);
m_printer_webview->load_default_url_delayed();
}
void MainFrame::remove_printer_webview_tab()
{
if (!m_printer_webview_added) {
return;
}
m_printer_webview_added = false;
m_printer_webview->Hide();
// always remove the last tab
dynamic_cast<TopBar*>(m_tabpanel)->RemovePage(m_tabpanel->GetPageCount() - 1);
}
void MainFrame::set_printer_webview_tab_url(const wxString& url)
{
if (!m_printer_webview_added) {
add_printer_webview_tab(url);
return;
}
m_printer_webview->clear();
m_printer_webview->set_default_url(url);
if (m_tabpanel->GetSelection() == m_tabpanel->GetPageCount() - 1) {
m_printer_webview->load_url(url);
} else {
m_printer_webview->load_default_url_delayed();
}
}
void MainFrame::set_printer_webview_api_key(const std::string& key)
{
m_printer_webview->set_api_key(key);
}
void MainFrame::set_printer_webview_credentials(const std::string& usr, const std::string& psk)
{
m_printer_webview->set_credentials(usr, psk);
}
void Slic3r::GUI::MainFrame::refresh_account_menu(bool avatar/* = false */)
{
// Update User name in TopBar
dynamic_cast<TopBar*>(m_tabpanel)->GetTopBarItemsCtrl()->UpdateAccountMenu(avatar);
}
void MainFrame::add_created_tab(Tab* panel, const std::string& bmp_name /*= ""*/)
@ -829,12 +938,14 @@ void MainFrame::add_created_tab(Tab* panel, const std::string& bmp_name /*= ""*
const auto printer_tech = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
if (panel->supports_printer_technology(printer_tech))
#ifdef _MSW_DARK_MODE
#ifdef _WIN32
if (!wxGetApp().tabs_as_menu())
dynamic_cast<Notebook*>(m_tabpanel)->AddPage(panel, panel->title(), bmp_name);
else
#endif
dynamic_cast<TopBar*>(m_tabpanel)->AddPage(panel, panel->title(), bmp_name);
#ifdef _WIN32
else
m_tabpanel->AddPage(panel, panel->title());
#endif
}
bool MainFrame::is_active_and_shown_tab(Tab* tab)
@ -1017,10 +1128,10 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect)
wxGetApp().update_fonts(this);
this->SetFont(this->normal_font());
#ifdef _MSW_DARK_MODE
#ifdef _WIN32
// update common mode sizer
if (!wxGetApp().tabs_as_menu())
dynamic_cast<Notebook*>(m_tabpanel)->Rescale();
dynamic_cast<TopBar*>(m_tabpanel)->Rescale();
#endif
// update Plater
@ -1065,12 +1176,9 @@ void MainFrame::on_sys_color_changed()
wxGetApp().update_ui_colours_from_appconfig();
#ifdef __WXMSW__
wxGetApp().UpdateDarkUI(m_tabpanel);
#ifdef _MSW_DARK_MODE
// update common mode sizer
if (!wxGetApp().tabs_as_menu())
dynamic_cast<Notebook*>(m_tabpanel)->OnColorsChanged();
#endif
#endif
dynamic_cast<TopBar*>(m_tabpanel)->OnColorsChanged();
// update Plater
wxGetApp().plater()->sys_color_changed();
@ -1079,6 +1187,9 @@ void MainFrame::on_sys_color_changed()
for (auto tab : wxGetApp().tabs_list)
tab->sys_color_changed();
m_connect_webview->sys_color_changed();
m_printer_webview->sys_color_changed();
MenuFactory::sys_color_changed(m_menubar);
this->Refresh();
@ -1088,16 +1199,9 @@ void MainFrame::on_sys_color_changed()
void MainFrame::update_mode_markers()
{
#ifdef __WXMSW__
#ifdef _MSW_DARK_MODE
// update markers in common mode sizer
if (!wxGetApp().tabs_as_menu())
dynamic_cast<Notebook*>(m_tabpanel)->UpdateModeMarkers();
#endif
#endif
// update mode markers on side_bar
wxGetApp().sidebar().update_mode_markers();
dynamic_cast<TopBar*>(m_tabpanel)->UpdateModeMarkers();
// update mode markers in tabs
for (auto tab : wxGetApp().tabs_list)
@ -1434,7 +1538,7 @@ void MainFrame::init_menubar_as_editor()
editMenu->AppendSeparator();
append_menu_item(editMenu, wxID_ANY, _L("Searc&h") + "\tCtrl+F",
_L("Search in settings"), [this](wxCommandEvent&) { m_plater->IsShown() ? m_plater->search() : wxGetApp().show_search_dialog(); },
_L("Search in settings"), [](wxCommandEvent&) { wxGetApp().show_search_dialog(); },
"search", nullptr, []() {return true; }, this);
}
@ -1520,6 +1624,28 @@ void MainFrame::init_menubar_as_editor()
// Help menu
auto helpMenu = generate_help_menu();
#ifndef __APPLE__
// append menus for Menu button from TopBar
TopBar* top_bar = dynamic_cast<TopBar*>(m_tabpanel);
top_bar->AppendMenuItem(fileMenu, _L("&File"));
if (editMenu)
top_bar->AppendMenuItem(editMenu, _L("&Edit"));
top_bar->AppendMenuSeparaorItem();
top_bar->AppendMenuItem(windowMenu, _L("&Window"));
if (viewMenu)
top_bar->AppendMenuItem(viewMenu, _L("&View"));
top_bar->AppendMenuItem(wxGetApp().get_config_menu(), _L("&Configuration"));
top_bar->AppendMenuSeparaorItem();
top_bar->AppendMenuItem(helpMenu, _L("&Help"));
#else
// menubar
// assign menubar to frame after appending items, otherwise special items
// will not be handled correctly
@ -1530,25 +1656,13 @@ void MainFrame::init_menubar_as_editor()
m_menubar->Append(windowMenu, _L("&Window"));
if (viewMenu) m_menubar->Append(viewMenu, _L("&View"));
// Add additional menus from C++
wxGetApp().add_config_menu(m_menubar);
m_menubar->Append(wxGetApp().get_config_menu(), _L("&Configuration"));
m_menubar->Append(helpMenu, _L("&Help"));
#ifdef _MSW_DARK_MODE
if (wxGetApp().tabs_as_menu()) {
// Add separator
m_menubar->Append(new wxMenu(), " ");
add_tabs_as_menu(m_menubar, this, this);
}
#endif
SetMenuBar(m_menubar);
#ifdef _MSW_DARK_MODE
if (wxGetApp().tabs_as_menu())
m_menubar->EnableTop(6, false);
#endif
#ifdef __APPLE__
init_macos_application_menu(m_menubar, this);
#endif // __APPLE__
if (plater()->printer_technology() == ptSLA)
@ -1635,7 +1749,7 @@ void MainFrame::init_menubar_as_gcodeviewer()
m_menubar->Append(fileMenu, _L("&File"));
if (viewMenu != nullptr) m_menubar->Append(viewMenu, _L("&View"));
// Add additional menus from C++
wxGetApp().add_config_menu(m_menubar);
// wxGetApp().add_config_menu(m_menubar);
m_menubar->Append(helpMenu, _L("&Help"));
SetMenuBar(m_menubar);
@ -1789,9 +1903,54 @@ void MainFrame::export_configbundle(bool export_physical_printers /*= false*/)
file = dlg.GetPath();
if (!file.IsEmpty()) {
// Export the config bundle.
bool passwords_to_plain = false;
bool passwords_dialog_shown = false;
// callback function thats going to be passed to preset bundle (so preset bundle doesnt have to include WX secret lib)
std::function<bool(const std::string&, const std::string&, std::string&)> load_password = [&](const std::string& printer_id, const std::string& opt, std::string& out_psswd)->bool{
out_psswd = std::string();
#if wxUSE_SECRETSTORE
// First password prompts user with dialog
if (!passwords_dialog_shown) {
//wxString msg = GUI::format_wxstr(L"%1%\n%2%", _L("Some of the exported printers contains passwords, which are stored in the system password store."), _L("Do you wish to include the passwords to the exported file in the plain text form?"));
wxString msg = _L("Some of the exported printers contains passwords, which are stored in the system password store."
" Do you wish to include the passwords in the plain text form to the exported file?");
MessageDialog dlg_psswd(this, msg, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION);
if (dlg_psswd.ShowModal() == wxID_YES)
passwords_to_plain = true;
passwords_dialog_shown = true;
}
if (!passwords_to_plain)
return false;
wxSecretStore store = wxSecretStore::GetDefault();
wxString errmsg;
if (!store.IsOk(&errmsg)) {
std::string msg = GUI::format("%1% (%2%).", _u8L("Failed to load credentials from the system secret store."), errmsg);
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
// Do not try again. System store is not reachable.
passwords_to_plain = false;
return false;
}
const wxString service = GUI::format_wxstr(L"%1%/PhysicalPrinter/%2%/%3%", SLIC3R_APP_NAME, printer_id, opt);
wxString username;
wxSecretValue password;
if (!store.Load(service, username, password)) {
std::string msg = GUI::format(_u8L("Failed to load credentials from the system secret store for the printer %1%."), printer_id);
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
return false;
}
out_psswd = into_u8(password.GetAsString());
return true;
#else
return false;
#endif // wxUSE_SECRETSTORE
};
wxGetApp().app_config->update_config_dir(get_dir_name(file));
try {
wxGetApp().preset_bundle->export_configbundle(file.ToUTF8().data(), false, export_physical_printers);
wxGetApp().preset_bundle->export_configbundle(file.ToUTF8().data(), false, export_physical_printers, load_password);
} catch (const std::exception &ex) {
show_error(this, ex.what());
}
@ -1887,6 +2046,8 @@ void MainFrame::select_tab(Tab* tab)
void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
{
if (!wxGetApp().is_editor())
return;
bool tabpanel_was_hidden = false;
// Controls on page are created on active page of active tab now.
@ -1918,9 +2079,6 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
if (tab==0) {
if (m_settings_dialog.IsShown())
this->SetFocus();
// plater should be focused for correct navigation inside search window
if (m_plater->canvas3D()->is_search_pressed())
m_plater->SetFocus();
return;
}
// Show/Activate Settings Dialog
@ -2038,6 +2196,8 @@ void MainFrame::add_to_recent_projects(const wxString& filename)
void MainFrame::technology_changed()
{
if (!m_menubar)
return;
// update menu titles
PrinterTechnology pt = plater()->printer_technology();
if (int id = m_menubar->FindMenu(pt == ptFFF ? _L("Material Settings") : _L("Filament Settings")); id != wxNOT_FOUND)
@ -2171,10 +2331,10 @@ void SettingsDialog::on_dpi_changed(const wxRect& suggested_rect)
const int& em = em_unit();
const wxSize& size = wxSize(85 * em, 50 * em);
#ifdef _MSW_DARK_MODE
#ifdef _WIN32
// update common mode sizer
if (!wxGetApp().tabs_as_menu())
dynamic_cast<Notebook*>(m_tabpanel)->Rescale();
dynamic_cast<TopBar*>(m_tabpanel)->Rescale();
#endif
// update Tabs

View File

@ -43,6 +43,8 @@ class Plater;
class MainFrame;
class PreferencesDialog;
class GalleryDialog;
class ConnectWebViewPanel;
class PrinterWebViewPanel;
enum QuickSlice
{
@ -96,6 +98,11 @@ class MainFrame : public DPIFrame
size_t m_last_selected_tab;
Search::OptionsSearcher m_searcher;
ConnectWebViewPanel* m_connect_webview{ nullptr };
bool m_connect_webview_added{ false };
PrinterWebViewPanel* m_printer_webview{ nullptr };
bool m_printer_webview_added{ false };
std::string get_base_name(const wxString &full_name, const char *extension = nullptr) const;
std::string get_dir_name(const wxString &full_name) const;
@ -124,6 +131,7 @@ class MainFrame : public DPIFrame
miSend, // Send G-code Send to print
miMaterialTab, // Filament Settings Material Settings
miPrinterTab, // Different bitmap for Printer Settings
miLogin,
};
// vector of a MenuBar items changeable in respect to printer technology
@ -207,6 +215,18 @@ public:
void add_to_recent_projects(const wxString& filename);
void technology_changed();
void add_connect_webview_tab();
void remove_connect_webview_tab();
void add_printer_webview_tab(const wxString& url);
void remove_printer_webview_tab();
void set_printer_webview_tab_url(const wxString& url);
bool get_printer_webview_tab_added() const { return m_printer_webview_added; }
void set_printer_webview_api_key(const std::string& key);
void set_printer_webview_credentials(const std::string& usr, const std::string& psk);
void refresh_account_menu(bool avatar = false);
PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; }
Plater* m_plater { nullptr };
@ -218,7 +238,7 @@ public:
PreferencesDialog* preferences_dialog { nullptr };
PrintHostQueueDialog* m_printhost_queue_dlg;
GalleryDialog* m_gallery_dialog{ nullptr };
#ifdef __APPLE__
std::unique_ptr<wxTaskBarIcon> m_taskbar_icon;
#endif // __APPLE__

View File

@ -129,7 +129,11 @@ enum class NotificationType
// MacOS specific - PS comes forward even when downloader is not allowed
URLNotRegistered,
// Config file was detected during startup, open wifi config dialog via hypertext
WifiConfigFileDetected
WifiConfigFileDetected,
// Info abouty successful login or logout
UserAccountID,
// Debug notification for connect communication
PrusaConnectPrinters,
};
class NotificationManager

View File

@ -9,6 +9,7 @@
#include <vector>
#include <string>
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
#include <wx/sizer.h>
#include <wx/stattext.h>
@ -16,6 +17,10 @@
#include <wx/button.h>
#include <wx/statbox.h>
#include <wx/wupdlock.h>
#if wxUSE_SECRETSTORE
#include <wx/secretstore.h>
#endif
#include <wx/clipbrd.h>
#include "libslic3r/libslic3r.h"
#include "libslic3r/PrintConfig.hpp"
@ -153,6 +158,78 @@ void PresetForPrinter::on_sys_color_changed()
m_delete_preset_btn->sys_color_changed();
}
namespace {
bool is_secret_store_ok()
{
#if wxUSE_SECRETSTORE
wxSecretStore store = wxSecretStore::GetDefault();
wxString errmsg;
if (!store.IsOk(&errmsg)) {
BOOST_LOG_TRIVIAL(warning) << "wxSecretStore is not supported: " << errmsg;
return false;
}
return true;
#else
return false;
#endif
}
bool save_secret(const std::string& id, const std::string& opt, const std::string& usr, const std::string& psswd)
{
#if wxUSE_SECRETSTORE
wxSecretStore store = wxSecretStore::GetDefault();
wxString errmsg;
if (!store.IsOk(&errmsg)) {
std::string msg = GUI::format("%1% (%2%).", _u8L("This system doesn't support storing passwords securely"), errmsg);
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
return false;
}
const wxString service = GUI::format_wxstr(L"%1%/PhysicalPrinter/%2%/%3%", SLIC3R_APP_NAME, id, opt);
const wxString username = boost::nowide::widen(usr);
const wxSecretValue password(boost::nowide::widen(psswd));
if (!store.Save(service, username, password)) {
std::string msg(_u8L("Failed to save credentials to the system secret store."));
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
return false;
}
return true;
#else
BOOST_LOG_TRIVIAL(error) << "wxUSE_SECRETSTORE not supported. Cannot save password to the system store.";
return false;
#endif // wxUSE_SECRETSTORE
}
bool load_secret(const std::string& id, const std::string& opt, std::string& usr, std::string& psswd)
{
#if wxUSE_SECRETSTORE
wxSecretStore store = wxSecretStore::GetDefault();
wxString errmsg;
if (!store.IsOk(&errmsg)) {
std::string msg = GUI::format("%1% (%2%).", _u8L("This system doesn't support storing passwords securely"), errmsg);
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
return false;
}
const wxString service = GUI::format_wxstr(L"%1%/PhysicalPrinter/%2%/%3%", SLIC3R_APP_NAME, id, opt);
wxString username;
wxSecretValue password;
if (!store.Load(service, username, password)) {
std::string msg(_u8L("Failed to load credentials from the system secret store."));
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
return false;
}
usr = into_u8(username);
psswd = into_u8(password.GetAsString());
return true;
#else
BOOST_LOG_TRIVIAL(error) << "wxUSE_SECRETSTORE not supported. Cannot load password from the system store.";
return false;
#endif // wxUSE_SECRETSTORE
}
}
//------------------------------------------
// PhysicalPrinterDialog
@ -202,6 +279,20 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxWindow* parent, wxString printer_
const std::set<std::string>& preset_names = printer->get_preset_names();
for (const std::string& preset_name : preset_names)
m_presets.emplace_back(new PresetForPrinter(this, preset_name));
// "stored" indicates data are stored secretly, load them from store.
if (m_printer.config.opt_string("printhost_password") == "stored" && m_printer.config.opt_string("printhost_password") == "stored") {
std::string username;
std::string password;
if (load_secret(m_printer.name, "printhost_password", username, password)) {
if (!username.empty())
m_printer.config.opt_string("printhost_user") = username;
if (!password.empty())
m_printer.config.opt_string("printhost_password") = password;
} else {
m_printer.config.opt_string("printhost_user") = std::string();
m_printer.config.opt_string("printhost_password") = std::string();
}
}
}
if (m_presets.size() == 1)
@ -348,6 +439,20 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
return sizer;
};
auto api_key_copy = [=](wxWindow* parent) {
auto sizer = create_sizer_with_btn(parent, &m_api_key_copy_btn, "copy", _L("Copy"));
m_api_key_copy_btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent& e) {
if (Field* apikey_field = m_optgroup->get_field("printhost_apikey"); apikey_field) {
if (wxTextCtrl* temp = dynamic_cast<wxTextCtrl*>(apikey_field->getWindow()); temp ) {
wxTheClipboard->Open();
wxTheClipboard->SetData(new wxTextDataObject(temp->GetValue()));
wxTheClipboard->Close();
}
}
});
return sizer;
};
// Set a wider width for a better alignment
Option option = m_optgroup->get_option("print_host");
option.opt.width = Field::def_width_wider();
@ -360,7 +465,9 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
option = m_optgroup->get_option("printhost_apikey");
option.opt.width = Field::def_width_wider();
m_optgroup->append_single_option_line(option);
Line apikey_line = m_optgroup->create_single_option_line(option);
apikey_line.append_widget(api_key_copy);
m_optgroup->append_line(apikey_line);
option = m_optgroup->get_option("printhost_port");
option.opt.width = Field::def_width_wider();
@ -422,6 +529,38 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
m_optgroup->append_line(line);
}
// Text line with info how passwords and api keys are stored
if (is_secret_store_ok()) {
Line line{ "", "" };
line.full_width = 1;
line.widget = [ca_file_hint](wxWindow* parent) {
wxString info = GUI::format_wxstr(L"%1%:\n\t%2%\n"
, _L("Storing passwords")
, GUI::format_wxstr(_L("On this system, %1% uses the system password store to safely store and read passwords and API keys."), SLIC3R_APP_NAME));
auto txt = new wxStaticText(parent, wxID_ANY, info);
txt->SetFont(wxGetApp().normal_font());
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(txt, 1, wxEXPAND);
return sizer;
};
m_optgroup->append_line(line);
} else {
Line line{ "", "" };
line.full_width = 1;
line.widget = [ca_file_hint](wxWindow* parent) {
wxString info = GUI::format_wxstr(L"%1%:\n\t%2%\n\t%3%\n"
, _L("Storing passwords")
, GUI::format_wxstr(_L("On this system, %1% cannot access the system password store."), SLIC3R_APP_NAME)
, _L("Passwords and API keys are stored in plain text."));
auto txt = new wxStaticText(parent, wxID_ANY, info);
txt->SetFont(wxGetApp().normal_font());
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(txt, 1, wxEXPAND);
return sizer;
};
m_optgroup->append_line(line);
}
for (const std::string& opt_key : std::vector<std::string>{ "printhost_user", "printhost_password" }) {
option = m_optgroup->get_option(opt_key);
option.opt.width = Field::def_width_wider();
@ -810,6 +949,13 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event)
//update printer name, if it was changed
m_printer.set_name(into_u8(printer_name));
// save access data secretly
if (!m_printer.config.opt_string("printhost_password").empty()) {
if (save_secret(m_printer.name, "printhost_password", m_printer.config.opt_string("printhost_user"), m_printer.config.opt_string("printhost_password"))) {
m_printer.config.opt_string("printhost_password", false) = "stored";
}
}
// save new physical printer
printers.save_printer(m_printer, renamed_from);

View File

@ -76,6 +76,7 @@ class PhysicalPrinterDialog : public DPIDialog
ScalableButton* m_printhost_test_btn {nullptr};
ScalableButton* m_printhost_cafile_browse_btn {nullptr};
ScalableButton* m_printhost_port_browse_btn {nullptr};
ScalableButton* m_api_key_copy_btn {nullptr};
wxBoxSizer* m_presets_sizer {nullptr};

View File

@ -35,6 +35,8 @@
#include <boost/filesystem/operations.hpp>
#include <boost/log/trivial.hpp>
#include <boost/nowide/convert.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <wx/sizer.h>
#include <wx/stattext.h>
@ -50,6 +52,9 @@
#include <wx/debug.h>
#include <wx/busyinfo.h>
#include <wx/stdpaths.h>
#if wxUSE_SECRETSTORE
#include <wx/secretstore.h>
#endif
#include <LibBGCode/convert/convert.hpp>
@ -116,6 +121,9 @@
#include "Gizmos/GLGizmoSVG.hpp" // Drop SVG file
#include "Gizmos/GLGizmoCut.hpp"
#include "FileArchiveDialog.hpp"
#include "UserAccount.hpp"
#include "DesktopIntegrationDialog.hpp"
#include "WebViewDialog.hpp"
#ifdef __APPLE__
#include "Gizmos/GLGizmosManager.hpp"
@ -259,9 +267,10 @@ struct Plater::priv
GLToolbar collapse_toolbar;
Preview *preview;
std::unique_ptr<NotificationManager> notification_manager;
std::unique_ptr<UserAccount> user_account;
ProjectDirtyStateManager dirty_state;
BackgroundSlicingProcess background_process;
bool suppressed_backround_processing_update { false };
@ -605,6 +614,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
}))
, sidebar(new Sidebar(q))
, notification_manager(std::make_unique<NotificationManager>(q))
, user_account(std::make_unique<UserAccount>(q, wxGetApp().app_config, wxGetApp().get_instance_hash_string()))
, m_worker{q, std::make_unique<NotificationProgressIndicator>(notification_manager.get()), "ui_worker"}
, m_sla_import_dlg{new SLAImportDialog{q}}
, delayed_scene_refresh(false)
@ -850,11 +860,118 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
wxGetApp().start_download(evt.data[i]);
}
});
this->q->Bind(EVT_LOGIN_OTHER_INSTANCE, [this](LoginOtherInstanceEvent& evt) {
BOOST_LOG_TRIVIAL(trace) << "Received login from other instance event.";
user_account->on_login_code_recieved(evt.data);
});
this->q->Bind(EVT_INSTANCE_GO_TO_FRONT, [this](InstanceGoToFrontEvent &) {
bring_instance_forward();
});
this->q->Bind(EVT_OPEN_PRUSAAUTH, [](OpenPrusaAuthEvent& evt) {
BOOST_LOG_TRIVIAL(info) << "open browser: " << evt.data;
// first register url to be sure to get the code back
//auto downloader_worker = new DownloaderUtils::Worker(nullptr);
DownloaderUtils::Worker::perform_register(wxGetApp().app_config->get("url_downloader_dest"));
#ifdef __linux__
if (DownloaderUtils::Worker::perform_registration_linux)
DesktopIntegrationDialog::perform_downloader_desktop_integration();
#endif // __linux__
// than open url
wxGetApp().open_login_browser_with_dialog(evt.data);
});
this->q->Bind(EVT_UA_LOGGEDOUT, [this](UserAccountSuccessEvent& evt) {
user_account->clear();
std::string text = _u8L("Logged out from Prusa Account.");
this->notification_manager->close_notification_of_type(NotificationType::UserAccountID);
this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text);
this->main_frame->remove_connect_webview_tab();
this->main_frame->refresh_account_menu(true);
// Update sidebar printer status
sidebar->update_printer_presets_combobox();
wxGetApp().update_login_dialog();
this->show_action_buttons(this->ready_to_slice);
});
this->q->Bind(EVT_UA_ID_USER_SUCCESS, [this](UserAccountSuccessEvent& evt) {
std::string username;
if (user_account->on_user_id_success(evt.data, username)) {
// login notification
std::string text = format(_u8L("Logged to Prusa Account as %1%."), username);
this->notification_manager->close_notification_of_type(NotificationType::UserAccountID);
this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text);
// show connect tab
this->main_frame->add_connect_webview_tab();
// Update User name in TopBar
this->main_frame->refresh_account_menu();
wxGetApp().update_login_dialog();
this->show_action_buttons(this->ready_to_slice);
} else {
// data were corrupt and username was not retrieved
// procced as if EVT_UA_RESET was recieved
BOOST_LOG_TRIVIAL(error) << "Reseting Prusa Account communication. Recieved data were corrupt.";
user_account->clear();
this->notification_manager->close_notification_of_type(NotificationType::UserAccountID);
this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::WarningNotificationLevel, _u8L("Failed to connect to Prusa Account."));
this->main_frame->remove_connect_webview_tab();
// Update User name in TopBar
this->main_frame->refresh_account_menu(true);
// Update sidebar printer status
sidebar->update_printer_presets_combobox();
}
});
this->q->Bind(EVT_UA_RESET, [this](UserAccountFailEvent& evt) {
BOOST_LOG_TRIVIAL(error) << "Reseting Prusa Account communication. Error message: " << evt.data;
user_account->clear();
this->notification_manager->close_notification_of_type(NotificationType::UserAccountID);
this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::WarningNotificationLevel, _u8L("Failed to connect to Prusa Account."));
this->main_frame->remove_connect_webview_tab();
// Update User name in TopBar
this->main_frame->refresh_account_menu(true);
// Update sidebar printer status
sidebar->update_printer_presets_combobox();
});
this->q->Bind(EVT_UA_FAIL, [this](UserAccountFailEvent& evt) {
BOOST_LOG_TRIVIAL(error) << "Failed communication with Prusa Account: " << evt.data;
user_account->on_communication_fail();
});
#if 0
// for debug purposes only
this->q->Bind(EVT_UA_SUCCESS, [this](UserAccountSuccessEvent& evt) {
this->notification_manager->close_notification_of_type(NotificationType::UserAccountID);
this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::ImportantNotificationLevel, evt.data);
});
this->q->Bind(EVT_UA_CONNECT_USER_DATA_SUCCESS, [this](UserAccountSuccessEvent& evt) {
BOOST_LOG_TRIVIAL(error) << evt.data;
user_account->on_connect_user_data_success(evt.data);
});
#endif // 0
this->q->Bind(EVT_UA_PRUSACONNECT_PRINTERS_SUCCESS, [this](UserAccountSuccessEvent& evt) {
std::string text;
bool printers_changed = false;
if (user_account->on_connect_printers_success(evt.data, wxGetApp().app_config, printers_changed)) {
if (printers_changed) {
sidebar->update_printer_presets_combobox();
}
} else {
// message was corrupt, procceed like EVT_UA_FAIL
user_account->on_communication_fail();
}
});
this->q->Bind(EVT_UA_AVATAR_SUCCESS, [this](UserAccountSuccessEvent& evt) {
boost::filesystem::path path = user_account->get_avatar_path(true);
FILE* file;
file = fopen(path.string().c_str(), "wb");
fwrite(evt.data.c_str(), 1, evt.data.size(), file);
fclose(file);
this->main_frame->refresh_account_menu(true);
wxGetApp().update_login_dialog();
});
wxGetApp().other_instance_message_handler()->init(this->q);
// collapse sidebar according to saved value
@ -3408,7 +3525,7 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice_) const
DynamicPrintConfig* selected_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config();
const auto print_host_opt = selected_printer_config ? selected_printer_config->option<ConfigOptionString>("print_host") : nullptr;
const bool send_gcode_shown = print_host_opt != nullptr && !print_host_opt->value.empty();
const bool connect_gcode_shown = print_host_opt == nullptr && user_account->is_logged();
// when a background processing is ON, export_btn and/or send_btn are showing
if (get_config_bool("background_processing"))
{
@ -3416,6 +3533,7 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice_) const
if (sidebar->show_reslice(false) |
sidebar->show_export(true) |
sidebar->show_send(send_gcode_shown) |
sidebar->show_connect(connect_gcode_shown) |
sidebar->show_export_removable(removable_media_status.has_removable_drives))
sidebar->Layout();
}
@ -3427,6 +3545,7 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice_) const
if (sidebar->show_reslice(ready_to_slice) |
sidebar->show_export(!ready_to_slice) |
sidebar->show_send(send_gcode_shown && !ready_to_slice) |
sidebar->show_connect(connect_gcode_shown && !ready_to_slice) |
sidebar->show_export_removable(!ready_to_slice && removable_media_status.has_removable_drives))
sidebar->Layout();
}
@ -5700,6 +5819,166 @@ void Plater::reslice_SLA_until_step(SLAPrintObjectStep step, const ModelObject &
this->reslice_until_step_inner(SLAPrintObjectStep(step), object, postpone_error_messages);
}
namespace {
bool load_secret(const std::string& id, const std::string& opt, std::string& usr, std::string& psswd)
{
#if wxUSE_SECRETSTORE
wxSecretStore store = wxSecretStore::GetDefault();
wxString errmsg;
if (!store.IsOk(&errmsg)) {
std::string msg = GUI::format("%1% (%2%).", _u8L("This system doesn't support storing passwords securely"), errmsg);
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
return false;
}
const wxString service = GUI::format_wxstr(L"%1%/PhysicalPrinter/%2%/%3%", SLIC3R_APP_NAME, id, opt);
wxString username;
wxSecretValue password;
if (!store.Load(service, username, password)) {
std::string msg(_u8L("Failed to load credentials from the system secret store."));
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
return false;
}
usr = into_u8(username);
psswd = into_u8(password.GetAsString());
return true;
#else
BOOST_LOG_TRIVIAL(error) << "wxUSE_SECRETSTORE not supported. Cannot load password from the system store.";
return false;
#endif // wxUSE_SECRETSTORE
}
}
void Plater::connect_gcode()
{
assert(p->user_account->is_logged());
std::string dialog_msg;
if(PrinterPickWebViewDialog(this, dialog_msg).ShowModal() != wxID_OK) {
return;
}
if (dialog_msg.empty()) {
show_error(this, _L("Failed to select a printer. PrusaConnect did not return a value."));
return;
}
BOOST_LOG_TRIVIAL(debug) << "Message from Printer pick webview: " << dialog_msg;
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
// Connect data
std::vector<std::string> compatible_printers;
p->user_account->fill_compatible_printers_from_json(dialog_msg, compatible_printers);
std::string connect_nozzle = p->user_account->get_nozzle_from_json(dialog_msg);
std::string connect_filament = p->user_account->get_keyword_from_json(dialog_msg, "filament_type");
std::vector<const Preset*> compatible_printer_presets;
for (const std::string& cp : compatible_printers) {
compatible_printer_presets.emplace_back(preset_bundle->printers.find_system_preset_by_model_and_variant(cp, connect_nozzle));
}
// Selected profiles
const Preset* selected_printer_preset = &preset_bundle->printers.get_selected_preset();
const Preset* selected_filament_preset = &preset_bundle->filaments.get_selected_preset();
const std::string selected_nozzle_serialized = dynamic_cast<const ConfigOptionFloats*>(selected_printer_preset->config.option("nozzle_diameter"))->serialize();
const std::string selected_filament_serialized = selected_filament_preset->config.option("filament_type")->serialize();
const std::string selected_printer_model_serialized = selected_printer_preset->config.option("printer_model")->serialize();
bool is_first = compatible_printer_presets.front()->name == selected_printer_preset->name;
bool found = false;
for (const Preset* connect_preset : compatible_printer_presets) {
if (!connect_preset) {
continue;
}
if (selected_printer_preset->name == connect_preset->name) {
found = true;
break;
}
}
//
if (!found) {
wxString line1 = _L("The printer profile you've selected for upload is not compatible with profiles selected for slicing.");
wxString line2 = GUI::format_wxstr(_L("PrusaSlicer Profile:\n%1%"), selected_printer_preset->name);
wxString line3 = _L("Known profiles compatible with printer selected for upload:");
wxString printers_line;
for (const Preset* connect_preset : compatible_printer_presets) {
if (!connect_preset) {
continue;
}
printers_line += GUI::format_wxstr(_L("\n%1%"), connect_preset->name);
}
wxString line4 = _L("Do you still wish to upload?");
wxString message = GUI::format_wxstr("%1%\n\n%2%\n\n%3%%4%\n\n%5%", line1, line2, line3, printers_line,line4);
MessageDialog msg_dialog(this, message, _L("Do you wish to upload?"), wxYES_NO);
auto modal_res = msg_dialog.ShowModal();
if (modal_res != wxID_YES) {
return;
}
} else if (!is_first) {
wxString line1 = _L("The printer profile you've selected for upload might not be compatible with profiles selected for slicing.");
wxString line2 = GUI::format_wxstr(_L("PrusaSlicer Profile:\n%1%"), selected_printer_preset->name);
wxString line3 = _L("Known profiles compatible with printer selected for upload:");
wxString printers_line;
for (const Preset* connect_preset : compatible_printer_presets) {
if (!connect_preset) {
continue;
}
printers_line += GUI::format_wxstr(_L("\n%1%"), connect_preset->name);
}
wxString line4 = _L("Do you still wish to upload?");
wxString message = GUI::format_wxstr("%1%\n\n%2%\n\n%3%%4%\n\n%5%", line1, line2, line3, printers_line, line4);
MessageDialog msg_dialog(this, message, _L("Do you wish to upload?"), wxYES_NO);
auto modal_res = msg_dialog.ShowModal();
if (modal_res != wxID_YES) {
return;
}
}
// Commented code with selecting printers in plater
/*
// if selected (in connect) preset is not visible, make it visible and selected
if (!connect_printer_preset->is_visible) {
size_t preset_id = preset_bundle->printers.get_preset_idx_by_name(connect_printer_preset->name);
assert(preset_id != size_t(-1));
preset_bundle->printers.select_preset(preset_id);
wxGetApp().get_tab(Preset::Type::TYPE_PRINTER)->select_preset(connect_printer_preset->name);
p->notification_manager->close_notification_of_type(NotificationType::PrusaConnectPrinters);
p->notification_manager->push_notification(NotificationType::PrusaConnectPrinters, NotificationManager::NotificationLevel::ImportantNotificationLevel, format(_u8L("Changed Printer to %1%."), connect_printer_preset->name));
select_view_3D("3D");
}
// if selected (in connect) preset is not selected in slicer, select it
if (preset_bundle->printers.get_selected_preset_name() != connect_printer_preset->name) {
size_t preset_id = preset_bundle->printers.get_preset_idx_by_name(connect_printer_preset->name);
assert(preset_id != size_t(-1));
preset_bundle->printers.select_preset(preset_id);
wxGetApp().get_tab(Preset::Type::TYPE_PRINTER)->select_preset(connect_printer_preset->name);
p->notification_manager->close_notification_of_type(NotificationType::PrusaConnectPrinters);
p->notification_manager->push_notification(NotificationType::PrusaConnectPrinters, NotificationManager::NotificationLevel::ImportantNotificationLevel, format(_u8L("Changed Printer to %1%."), connect_printer_preset->name));
select_view_3D("3D");
}
*/
const std::string connect_state = p->user_account->get_keyword_from_json(dialog_msg, "connect_state");
const std::string printer_state = p->user_account->get_keyword_from_json(dialog_msg, "printer_state");
const std::map<std::string, ConnectPrinterState>& printer_state_table = p->user_account->get_printer_state_table();
const auto state = printer_state_table.find(connect_state);
assert(state != printer_state_table.end());
// TODO: all states that does not allow to upload
if (state->second == ConnectPrinterState::CONNECT_PRINTER_OFFLINE) {
show_error(this, _L("Failed to select a printer. Chosen printer is in offline state."));
return;
}
const std::string uuid = p->user_account->get_keyword_from_json(dialog_msg, "uuid");
const std::string team_id = p->user_account->get_keyword_from_json(dialog_msg, "team_id");
if (uuid.empty() || team_id.empty()) {
show_error(this, _L("Failed to select a printer. Missing data (uuid and team id) for chosen printer."));
return;
}
PhysicalPrinter ph_printer("connect_temp_printer", wxGetApp().preset_bundle->physical_printers.default_config(), /**connect_printer_preset*/*selected_printer_preset);
ph_printer.config.set_key_value("host_type", new ConfigOptionEnum<PrintHostType>(htPrusaConnectNew));
// use existing structures to pass data
ph_printer.config.opt_string("printhost_apikey") = team_id;
ph_printer.config.opt_string("print_host") = uuid;
DynamicPrintConfig* physical_printer_config = &ph_printer.config;
send_gcode_inner(physical_printer_config);
}
void Plater::send_gcode()
{
// if physical_printer is selected, send gcode for this printer
@ -5707,6 +5986,38 @@ void Plater::send_gcode()
if (! physical_printer_config || p->model.objects.empty())
return;
// Passwords and API keys
// "stored" indicates data are stored secretly, load them from store.
std::string printer_name = wxGetApp().preset_bundle->physical_printers.get_selected_printer().name;
if (physical_printer_config->opt_string("printhost_password") == "stored" && physical_printer_config->opt_string("printhost_password") == "stored") {
std::string username;
std::string password;
if (load_secret(printer_name, "printhost_password", username, password)) {
if (!username.empty())
physical_printer_config->opt_string("printhost_user") = username;
if (!password.empty())
physical_printer_config->opt_string("printhost_password") = password;
}
else {
physical_printer_config->opt_string("printhost_user") = std::string();
physical_printer_config->opt_string("printhost_password") = std::string();
}
}
/*
if (physical_printer_config->opt_string("printhost_apikey") == "stored") {
std::string username;
std::string password;
if (load_secret(printer_name, "printhost_apikey", username, password) && !password.empty())
physical_printer_config->opt_string("printhost_apikey") = password;
else
physical_printer_config->opt_string("printhost_apikey") = std::string();
}
*/
send_gcode_inner(physical_printer_config);
}
void Plater::send_gcode_inner(DynamicPrintConfig* physical_printer_config)
{
PrintHostJob upload_job(physical_printer_config);
if (upload_job.empty())
return;
@ -5782,6 +6093,7 @@ void Plater::send_gcode()
p->export_gcode(fs::path(), false, std::move(upload_job));
}
}
// Called when the Eject button is pressed.
@ -6371,23 +6683,6 @@ void Plater::paste_from_clipboard()
p->view3D->get_canvas3d()->get_selection().paste_from_clipboard();
}
void Plater::search()
{
if (is_preview_shown())
return;
// plater should be focused for correct navigation inside search window
this->SetFocus();
wxKeyEvent evt;
#ifdef __APPLE__
evt.m_keyCode = 'f';
#else /* __APPLE__ */
evt.m_keyCode = WXK_CONTROL_F;
#endif /* __APPLE__ */
evt.SetControlDown(true);
canvas3D()->on_char(evt);
}
void Plater::msw_rescale()
{
p->preview->msw_rescale();
@ -6519,6 +6814,16 @@ const NotificationManager * Plater::get_notification_manager() const
return p->notification_manager.get();
}
UserAccount* Plater::get_user_account()
{
return p->user_account.get();
}
const UserAccount* Plater::get_user_account() const
{
return p->user_account.get();
}
void Plater::init_notification_manager()
{
p->init_notification_manager();

View File

@ -62,6 +62,7 @@ class Mouse3DController;
class NotificationManager;
struct Camera;
class GLToolbar;
class UserAccount;
class Plater: public wxPanel
{
@ -223,7 +224,9 @@ public:
bool is_background_process_update_scheduled() const;
void suppress_background_process(const bool stop_background_process) ;
void send_gcode();
void send_gcode_inner(DynamicPrintConfig* physical_printer_config);
void eject_drive();
void connect_gcode();
void take_snapshot(const std::string &snapshot_name);
void take_snapshot(const wxString &snapshot_name);
@ -282,7 +285,6 @@ public:
void copy_selection_to_clipboard();
void paste_from_clipboard();
void search();
void mirror(Axis axis);
void split_object();
void split_volume();
@ -347,8 +349,11 @@ public:
void set_bed_shape(const Pointfs& shape, const double max_print_height, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false) const;
void set_default_bed_shape() const;
NotificationManager * get_notification_manager();
const NotificationManager * get_notification_manager() const;
NotificationManager* get_notification_manager();
const NotificationManager* get_notification_manager() const;
UserAccount* get_user_account();
const UserAccount* get_user_account() const;
void init_notification_manager();

View File

@ -761,7 +761,7 @@ void PreferencesDialog::accept(wxEvent&)
if (!downloader->on_finish())
return;
#ifdef __linux__
if( downloader->get_perform_registration_linux())
if(DownloaderUtils::Worker::perform_registration_linux)
DesktopIntegrationDialog::perform_downloader_desktop_integration();
#endif // __linux__
}

View File

@ -20,6 +20,7 @@
#include <wx/menu.h>
#include <wx/odcombo.h>
#include <wx/listbook.h>
#include <wx/generic/stattextg.h>
#ifdef _WIN32
#include <wx/msw/dcclient.h>
@ -44,6 +45,7 @@
#include "BitmapCache.hpp"
#include "PhysicalPrinterDialog.hpp"
#include "MsgDialog.hpp"
#include "UserAccount.hpp"
#include "Widgets/ComboBox.hpp"
@ -618,6 +620,12 @@ bool PresetComboBox::selection_is_changed_according_to_physical_printers()
// *** PlaterPresetComboBox ***
// ---------------------------------
static bool is_active_connect()
{
auto user_account = wxGetApp().plater()->get_user_account();
return user_account && user_account->is_logged();
}
PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset_type) :
PresetComboBox(parent, preset_type, wxSize(15 * wxGetApp().em_unit(), -1))
{
@ -657,6 +665,9 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset
else
switch_to_tab();
});
if (m_type == Preset::TYPE_PRINTER)
connect_info = new wxGenericStaticText(parent, wxID_ANY, /*"Info about <b>Connect</b> for printer preset"*/ "");
}
PlaterPresetComboBox::~PlaterPresetComboBox()
@ -832,6 +843,86 @@ wxString PlaterPresetComboBox::get_preset_name(const Preset& preset)
return from_u8(name + suffix(preset));
}
struct PrinterStatesCount
{
size_t offline_cnt { 0 };
size_t busy_cnt { 0 };
size_t available_cnt { 0 };
size_t total { 0 };
};
static PrinterStatesCount get_printe_states_count(const std::vector<size_t>& states)
{
PrinterStatesCount states_cnt;
for (size_t i = 0; i < states.size(); i++) {
if (states[i] == 0)
continue;
ConnectPrinterState state = static_cast<ConnectPrinterState>(i);
if (state == ConnectPrinterState::CONNECT_PRINTER_OFFLINE)
states_cnt.offline_cnt += states[i];
else if (state == ConnectPrinterState::CONNECT_PRINTER_PAUSED ||
state == ConnectPrinterState::CONNECT_PRINTER_STOPPED ||
state == ConnectPrinterState::CONNECT_PRINTER_PRINTING ||
state == ConnectPrinterState::CONNECT_PRINTER_BUSY ||
state == ConnectPrinterState::CONNECT_PRINTER_ATTENTION ||
state == ConnectPrinterState::CONNECT_PRINTER_ERROR)
states_cnt.busy_cnt += states[i];
else
states_cnt.available_cnt += states[i];
}
states_cnt.total = states_cnt.offline_cnt + states_cnt.busy_cnt + states_cnt.available_cnt;
return states_cnt;
}
static std::string get_connect_state_suffix_for_printer(const Preset& printer_preset)
{
// process real data from Connect
if (auto printer_state_map = wxGetApp().plater()->get_user_account()->get_printer_state_map();
!printer_state_map.empty()) {
for (const auto& [printer_model_id, states] : printer_state_map) {
if (printer_model_id == printer_preset.config.opt_string("printer_model")) {
PrinterStatesCount states_cnt = get_printe_states_count(states);
if (states_cnt.available_cnt > 0)
return "_available";
if (states_cnt.busy_cnt > 0)
return "_busy";
return "_offline";
}
}
}
return "";
}
static wxString get_connect_info_line(const Preset& printer_preset)
{
if (auto printer_state_map = wxGetApp().plater()->get_user_account()->get_printer_state_map();
!printer_state_map.empty()) {
for (const auto& [printer_model_id, states] : printer_state_map) {
if (printer_model_id == printer_preset.config.opt_string("printer_model")) {
PrinterStatesCount states_cnt = get_printe_states_count(states);
return format_wxstr(_L("Available: %1%, Offline: %2%, Busy: %3%. Total: %4% printers"),
format("<b><span color=\"green\">%1%</span></b>" , states_cnt.available_cnt),
format("<b><span color=\"red\">%1%</span></b>" , states_cnt.offline_cnt),
format("<b><span color=\"yellow\">%1%</span></b>", states_cnt.busy_cnt),
format("<b>%1%</b>", states_cnt.total));
}
}
}
return " "; // to correct update of strinh height don't use empty string
}
// Only the compatible presets are shown.
// If an incompatible preset is selected, it is shown as well.
void PlaterPresetComboBox::update()
@ -904,6 +995,12 @@ void PlaterPresetComboBox::update()
std::string bitmap_key, filament_rgb, extruder_rgb, material_rgb;
std::string bitmap_type_name = bitmap_key = m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA ? "sla_printer" : m_main_bitmap_name;
if (m_type == Preset::TYPE_PRINTER) {
bitmap_type_name = bitmap_key += get_connect_state_suffix_for_printer(preset);
if (is_selected)
connect_info->SetLabelMarkup(get_connect_info_line(preset));
}
bool single_bar = false;
if (m_type == Preset::TYPE_FILAMENT)
{
@ -1032,6 +1129,8 @@ void PlaterPresetComboBox::update()
validate_selection(data.selected);
}
}
connect_info->Show(is_active_connect());
}
if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_FILAMENT || m_type == Preset::TYPE_SLA_MATERIAL) {

View File

@ -16,6 +16,7 @@
class wxString;
class wxTextCtrl;
class wxStaticText;
class wxGenericStaticText;
class ScalableButton;
class wxBoxSizer;
class wxComboBox;
@ -153,6 +154,7 @@ public:
~PlaterPresetComboBox();
ScalableButton* edit_btn { nullptr };
wxGenericStaticText* connect_info { nullptr };
void set_extruder_idx(const int extr_idx) { m_extruder_idx = extr_idx; }
int get_extruder_idx() const { return m_extruder_idx; }

View File

@ -16,6 +16,8 @@
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "GUI_App.hpp"
#include "I18N.hpp"
#include "format.hpp"
#include "MainFrame.hpp"
#include "Tab.hpp"
@ -306,6 +308,7 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/)
OptionsSearcher::OptionsSearcher()
{
default_string = _L("Enter a search term");
}
OptionsSearcher::~OptionsSearcher()
@ -421,20 +424,60 @@ Option OptionsSearcher::get_option(const std::string& opt_key, const wxString& l
return create_option(opt_key, label, type, gc);
}
void OptionsSearcher::show_dialog()
static bool has_focus(wxWindow* win)
{
if (!search_dialog) {
search_dialog = new SearchDialog(this);
if (win->HasFocus())
return true;
auto parent = search_dialog->GetParent();
wxPoint pos = parent->ClientToScreen(wxPoint(0, 0));
pos.x += em_unit(parent) * 40;
pos.y += em_unit(parent) * 4;
search_dialog->SetPosition(pos);
auto children = win->GetChildren();
for (auto child : children) {
if (has_focus(child))
return true;
}
return false;
}
void OptionsSearcher::update_dialog_position()
{
if (search_dialog) {
search_dialog->CenterOnParent(wxHORIZONTAL);
search_dialog->SetPosition({ search_dialog->GetPosition().x, search_input->GetScreenPosition().y + search_input->GetSize().y });
}
}
void OptionsSearcher::show_dialog(bool show /*= true*/)
{
if (search_dialog && !show) {
search_dialog->EndModal(wxID_CLOSE);
return;
}
if (!search_dialog) {
search_dialog = new SearchDialog(this, search_input);
update_dialog_position();
search_dialog->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e)
{
if (search_dialog->IsShown() && !search_input->HasFocus())
show_dialog(false);
e.Skip();
});
search_input->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e)
{
e.Skip();
if (search_dialog->IsShown() && !has_focus(search_dialog))
show_dialog(false);
});
}
search_string();
search_input->SetSelection(-1,-1);
search_dialog->Popup();
if (!search_input->HasFocus())
search_input->SetFocus();
}
void OptionsSearcher::dlg_sys_color_changed()
@ -449,6 +492,57 @@ void OptionsSearcher::dlg_msw_rescale()
search_dialog->msw_rescale();
}
void OptionsSearcher::set_search_input(TextInput* input_ctrl)
{
search_input = input_ctrl;
search_input->Bind(wxEVT_TEXT, [this](wxEvent& e)
{
if (search_dialog) {
search_dialog->input_text(search_input->GetValue());
if (!search_dialog->IsShown())
search_dialog->Popup();
}
else
GUI::wxGetApp().show_search_dialog();
});
wxTextCtrl* ctrl = search_input->GetTextCtrl();
ctrl->SetToolTip(GUI::format_wxstr(_L("Search in settings [%1%]"), "Ctrl+F"));
ctrl->Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& e)
{
int key = e.GetKeyCode();
if (key == WXK_TAB)
search_input->Navigate(e.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward);
else if (key == WXK_ESCAPE)
search_dialog->EndModal(wxID_CLOSE);
else if (search_dialog && (key == WXK_UP || key == WXK_DOWN || key == WXK_NUMPAD_ENTER || key == WXK_RETURN))
search_dialog->KeyDown(e);
e.Skip();
});
ctrl->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& event)
{
if (search_input->GetValue() == default_string)
search_input->SetValue("");
event.Skip();
});
ctrl->Bind(wxEVT_LEFT_DOWN, [](wxMouseEvent& event) {
GUI::wxGetApp().show_search_dialog();
event.Skip();
});
search_input->Bind(wxEVT_MOVE, [this](wxMoveEvent& event)
{
event.Skip();
update_dialog_position();
});
search_input->SetOnDropDownIcon([](){ GUI::wxGetApp().show_search_dialog(); });
}
void OptionsSearcher::add_key(const std::string& opt_key, Preset::Type type, const wxString& group, const wxString& category)
{
groups_and_categories[get_key(opt_key, type)] = GroupAndCategory{group, category};
@ -468,8 +562,8 @@ static const std::map<const char, int> icon_idxs = {
{ImGui::PreferencesButton , 5},
};
SearchDialog::SearchDialog(OptionsSearcher* searcher)
: GUI::DPIDialog(GUI::wxGetApp().tab_panel(), wxID_ANY, _L("Search"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
SearchDialog::SearchDialog(OptionsSearcher* searcher, wxWindow* parent)
: GUI::DPIDialog(parent ? parent : GUI::wxGetApp().tab_panel(), wxID_ANY, _L("Search"), wxDefaultPosition, wxDefaultSize, wxSTAY_ON_TOP | wxRESIZE_BORDER),
searcher(searcher)
{
SetFont(GUI::wxGetApp().normal_font());
@ -479,13 +573,9 @@ SearchDialog::SearchDialog(OptionsSearcher* searcher)
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
#endif
default_string = _L("Enter a search term");
int border = 10;
int em = em_unit();
search_line = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
GUI::wxGetApp().UpdateDarkUI(search_line);
search_list = new wxDataViewCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(em * 40, em * 30), wxDV_NO_HEADER | wxDV_SINGLE
#ifdef _WIN32
| wxBORDER_SIMPLE
@ -531,15 +621,9 @@ SearchDialog::SearchDialog(OptionsSearcher* searcher)
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
topSizer->Add(search_line, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);
topSizer->Add(search_list, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);
topSizer->Add(check_sizer, 0, wxEXPAND | wxALL, border);
search_line->Bind(wxEVT_TEXT, &SearchDialog::OnInputText, this);
search_line->Bind(wxEVT_LEFT_UP, &SearchDialog::OnLeftUpInTextCtrl, this);
// process wxEVT_KEY_DOWN to navigate inside search_list, if ArrowUp/Down was pressed
search_line->Bind(wxEVT_KEY_DOWN,&SearchDialog::OnKeyDown, this);
search_list->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, &SearchDialog::OnSelect, this);
search_list->Bind(wxEVT_DATAVIEW_ITEM_ACTIVATED, &SearchDialog::OnActivate, this);
#ifdef __WXMSW__
@ -559,7 +643,7 @@ SearchDialog::SearchDialog(OptionsSearcher* searcher)
check_english ->Bind(wxEVT_CHECKBOX, &SearchDialog::OnCheck, this);
// Bind(wxEVT_MOTION, &SearchDialog::OnMotion, this);
Bind(wxEVT_LEFT_DOWN, &SearchDialog::OnLeftDown, this);
// Bind(wxEVT_LEFT_DOWN, &SearchDialog::OnLeftDown, this);
SetSizer(topSizer);
topSizer->SetSizeHints(this);
@ -573,11 +657,6 @@ SearchDialog::~SearchDialog()
void SearchDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
{
const std::string& line = searcher->search_string();
search_line->SetValue(line.empty() ? default_string : from_u8(line));
search_line->SetFocus();
search_line->SelectAll();
update_list();
const OptionViewParameters& params = searcher->view_params;
@ -587,7 +666,11 @@ void SearchDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
if (position != wxDefaultPosition)
this->SetPosition(position);
this->ShowModal();
#ifdef __APPLE__
this->ShowWithoutActivating();
#else
this->Show();
#endif
}
void SearchDialog::ProcessSelection(wxDataViewItem selection)
@ -606,10 +689,9 @@ void SearchDialog::ProcessSelection(wxDataViewItem selection)
wxPostEvent(GUI::wxGetApp().mainframe, event);
}
void SearchDialog::OnInputText(wxCommandEvent&)
void SearchDialog::input_text(wxString input_string)
{
wxString input_string = search_line->GetValue();
if (input_string == default_string)
if (input_string == searcher->default_string)
input_string.Clear();
searcher->search(into_u8(input_string));
@ -617,14 +699,6 @@ void SearchDialog::OnInputText(wxCommandEvent&)
update_list();
}
void SearchDialog::OnLeftUpInTextCtrl(wxEvent& event)
{
if (search_line->GetValue() == default_string)
search_line->SetValue("");
event.Skip();
}
void SearchDialog::OnKeyDown(wxKeyEvent& event)
{
int key = event.GetKeyCode();
@ -749,7 +823,7 @@ void SearchDialog::on_sys_color_changed()
#ifdef _WIN32
GUI::wxGetApp().UpdateAllStaticTextDarkUI(this);
GUI::wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_CANCEL, this)), true);
for (wxWindow* win : std::vector<wxWindow*> {search_line, search_list, check_category, check_english})
for (wxWindow* win : std::vector<wxWindow*> {search_list, check_category, check_english})
if (win) GUI::wxGetApp().UpdateDarkUI(win);
#endif

View File

@ -27,6 +27,7 @@
#include "Widgets/CheckBox.hpp"
class CheckBox;
class TextInput;
namespace Slic3r {
@ -91,6 +92,8 @@ class OptionsSearcher
std::map<std::string, GroupAndCategory> groups_and_categories;
PrinterTechnology printer_technology {ptAny};
ConfigOptionMode mode{ comUndef };
TextInput* search_input { nullptr };
SearchDialog* search_dialog { nullptr };
std::vector<Option> options {};
std::vector<Option> preferences_options {};
@ -112,8 +115,7 @@ class OptionsSearcher
public:
OptionViewParameters view_params;
SearchDialog* search_dialog { nullptr };
wxString default_string;
OptionsSearcher();
~OptionsSearcher();
@ -145,9 +147,12 @@ public:
}
void sort_options_by_label() { sort_options(); }
void show_dialog();
void update_dialog_position();
void show_dialog(bool show = true);
void dlg_sys_color_changed();
void dlg_msw_rescale();
void set_search_input(TextInput* input_ctrl);
};
@ -158,11 +163,9 @@ class SearchListModel;
class SearchDialog : public GUI::DPIDialog
{
wxString search_str;
wxString default_string;
bool prevent_list_events {false};
wxTextCtrl* search_line { nullptr };
wxDataViewCtrl* search_list { nullptr };
SearchListModel* search_list_model { nullptr };
CheckBox* check_category { nullptr };
@ -170,8 +173,6 @@ class SearchDialog : public GUI::DPIDialog
OptionsSearcher* searcher { nullptr };
void OnInputText(wxCommandEvent& event);
void OnLeftUpInTextCtrl(wxEvent& event);
void OnKeyDown(wxKeyEvent& event);
void OnActivate(wxDataViewEvent& event);
@ -184,7 +185,7 @@ class SearchDialog : public GUI::DPIDialog
void update_list();
public:
SearchDialog(OptionsSearcher* searcher);
SearchDialog(OptionsSearcher* searcher, wxWindow* parent);
~SearchDialog();
void Popup(wxPoint position = wxDefaultPosition);
@ -193,6 +194,9 @@ public:
void msw_rescale();
void on_sys_color_changed() override;
void input_text(wxString input);
void KeyDown(wxKeyEvent& event) { OnKeyDown(event); }
protected:
void on_dpi_changed(const wxRect& suggested_rect) override { msw_rescale(); }
};

View File

@ -32,6 +32,7 @@
#include <wx/statbox.h>
#include <wx/statbmp.h>
#include <wx/wupdlock.h>
#include "wx/generic/stattextg.h"
#ifdef _WIN32
#include <wx/richtooltip.h>
#include <wx/custombgwin.h>
@ -331,9 +332,6 @@ Sidebar::Sidebar(Plater *parent)
auto *scrolled_sizer = new wxBoxSizer(wxVERTICAL);
m_scrolled_panel->SetSizer(scrolled_sizer);
// Sizer with buttons for mode changing
m_mode_sizer = new ModeSizer(m_scrolled_panel, int(0.5 * wxGetApp().em_unit()));
// The preset chooser
m_presets_sizer = new wxFlexGridSizer(10, 1, 1, 2);
m_presets_sizer->AddGrowableCol(0, 1);
@ -381,6 +379,9 @@ Sidebar::Sidebar(Plater *parent)
wxBOTTOM, 1);
(void)margin_5; // supress unused capture warning
#endif // __WXGTK3__
if ((*combo)->connect_info)
sizer_presets->Add((*combo)->connect_info, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT | wxBOTTOM,
int(0.3 * wxGetApp().em_unit()));
} else {
sizer_filaments->Add(combo_and_btn_sizer, 0, wxEXPAND |
#ifdef __WXGTK3__
@ -434,10 +435,6 @@ Sidebar::Sidebar(Plater *parent)
m_object_info = new ObjectInfo(m_scrolled_panel);
m_sliced_info = new SlicedInfo(m_scrolled_panel);
// Sizer in the scrolled area
if (m_mode_sizer)
scrolled_sizer->Add(m_mode_sizer, 0, wxALIGN_CENTER_HORIZONTAL);
int size_margin = wxGTK3 ? wxLEFT | wxRIGHT : wxLEFT;
is_msw ?
@ -497,6 +494,7 @@ Sidebar::Sidebar(Plater *parent)
init_btn(&m_btn_export_gcode, _L("Export G-code") + dots , scaled_height);
init_btn(&m_btn_reslice , _L("Slice now") , scaled_height);
init_btn(&m_btn_connect_gcode, _L("Send to Connect"), scaled_height);
enable_buttons(false);
@ -504,6 +502,7 @@ Sidebar::Sidebar(Plater *parent)
auto* complect_btns_sizer = new wxBoxSizer(wxHORIZONTAL);
complect_btns_sizer->Add(m_btn_export_gcode, 1, wxEXPAND);
complect_btns_sizer->Add(m_btn_connect_gcode, 1, wxLEFT, margin_5);
complect_btns_sizer->Add(m_btn_send_gcode, 0, wxLEFT, margin_5);
complect_btns_sizer->Add(m_btn_export_gcode_removable, 0, wxLEFT, margin_5);
@ -543,6 +542,7 @@ Sidebar::Sidebar(Plater *parent)
m_btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { m_plater->send_gcode(); });
m_btn_export_gcode_removable->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { m_plater->export_gcode(true); });
m_btn_connect_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { m_plater->connect_gcode(); });
this->Bind(wxEVT_COMBOBOX, &Sidebar::on_select_preset, this);
@ -603,6 +603,12 @@ void Sidebar::update_all_preset_comboboxes()
}
}
void Sidebar::update_printer_presets_combobox()
{
m_combo_printer->update();
Layout();
}
void Sidebar::update_presets(Preset::Type preset_type)
{
PresetBundle &preset_bundle = *wxGetApp().preset_bundle;
@ -711,6 +717,8 @@ void Sidebar::on_select_preset(wxCommandEvent& evt)
* and for SLA presets they should be deleted
*/
m_object_list->update_object_list_by_printer_technology();
wxGetApp().show_printer_webview_tab();
}
#ifdef __WXMSW__
@ -722,14 +730,6 @@ void Sidebar::on_select_preset(wxCommandEvent& evt)
#endif
}
void Sidebar::change_top_border_for_mode_sizer(bool increase_border)
{
if (m_mode_sizer) {
m_mode_sizer->set_items_flag(increase_border ? wxTOP : 0);
m_mode_sizer->set_items_border(increase_border ? int(0.5 * wxGetApp().em_unit()) : 0);
}
}
void Sidebar::update_reslice_btn_tooltip()
{
wxString tooltip = wxString("Slice") + " [" + GUI::shortkey_ctrl_prefix() + "R]";
@ -778,11 +778,9 @@ void Sidebar::sys_color_changed()
wxGetApp().UpdateDarkUI(win);
for (wxWindow* win : std::vector<wxWindow*>{ m_scrolled_panel, m_presets_panel })
wxGetApp().UpdateAllStaticTextDarkUI(win);
for (wxWindow* btn : std::vector<wxWindow*>{ m_btn_reslice, m_btn_export_gcode })
for (wxWindow* btn : std::vector<wxWindow*>{ m_btn_reslice, m_btn_export_gcode, m_btn_connect_gcode })
wxGetApp().UpdateDarkUI(btn, true);
if (m_mode_sizer)
m_mode_sizer->sys_color_changed();
m_frequently_changed_parameters->sys_color_changed();
m_object_settings ->sys_color_changed();
#endif
@ -807,12 +805,6 @@ void Sidebar::sys_color_changed()
m_scrolled_panel->Refresh();
}
void Sidebar::update_mode_markers()
{
if (m_mode_sizer)
m_mode_sizer->update_mode_markers();
}
ObjectManipulation* Sidebar::obj_manipul()
{
return m_object_manipulation.get();
@ -1075,18 +1067,19 @@ void Sidebar::enable_buttons(bool enable)
m_btn_export_gcode->Enable(enable);
m_btn_send_gcode->Enable(enable);
m_btn_export_gcode_removable->Enable(enable);
m_btn_connect_gcode->Enable(enable);
}
bool Sidebar::show_reslice(bool show) const { return m_btn_reslice->Show(show); }
bool Sidebar::show_export(bool show) const { return m_btn_export_gcode->Show(show); }
bool Sidebar::show_send(bool show) const { return m_btn_send_gcode->Show(show); }
bool Sidebar::show_export_removable(bool show) const { return m_btn_export_gcode_removable->Show(show); }
bool Sidebar::show_connect(bool show) const { return m_btn_connect_gcode->Show(show); }
void Sidebar::update_mode()
{
m_mode = wxGetApp().get_mode();
if (m_mode_sizer)
m_mode_sizer->SetMode(m_mode);
update_reslice_btn_tooltip();
@ -1109,7 +1102,8 @@ void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& lab
{
case ActionButtonType::Reslice: m_btn_reslice->SetLabelText(label); break;
case ActionButtonType::Export: m_btn_export_gcode->SetLabelText(label); break;
case ActionButtonType::SendGCode: /*m_btn_send_gcode->SetLabelText(label);*/ break;
case ActionButtonType::SendGCode: /*m_btn_send_gcode->SetLabelText(label);*/ break;
case ActionButtonType::Connect: /*m_btn_connect_gcode->SetLabelText(label);*/ break;
}
}
@ -1125,13 +1119,6 @@ void Sidebar::collapse(bool collapse)
wxGetApp().app_config->set("collapsed_sidebar", collapse ? "1" : "0");
}
#ifdef _MSW_DARK_MODE
void Sidebar::show_mode_sizer(bool show)
{
m_mode_sizer->Show(show);
}
#endif
void Sidebar::update_ui_from_settings()
{
m_object_manipulation->update_ui_from_settings();

View File

@ -21,6 +21,7 @@
#include <wx/panel.h>
#include <wx/string.h>
#include <wx/sizer.h>
#include "libslic3r/Preset.hpp"
#include "GUI.hpp"
@ -49,7 +50,8 @@ class Plater;
enum class ActionButtonType : int {
Reslice,
Export,
SendGCode
SendGCode,
Connect
};
class Sidebar : public wxPanel
@ -61,7 +63,6 @@ class Sidebar : public wxPanel
wxScrolledWindow* m_scrolled_panel { nullptr };
wxPanel* m_presets_panel { nullptr }; // Used for MSW better layouts
ModeSizer* m_mode_sizer { nullptr };
wxFlexGridSizer* m_presets_sizer { nullptr };
wxBoxSizer* m_filaments_sizer { nullptr };
@ -77,6 +78,7 @@ class Sidebar : public wxPanel
wxButton* m_btn_export_gcode { nullptr };
wxButton* m_btn_reslice { nullptr };
wxButton* m_btn_connect_gcode { nullptr };
ScalableButton* m_btn_send_gcode { nullptr };
ScalableButton* m_btn_export_gcode_removable{ nullptr }; //exports to removable drives (appears only if removable drive is connected)
@ -123,24 +125,20 @@ public:
bool show_export(bool show) const;
bool show_send(bool show) const;
bool show_export_removable(bool show) const;
bool show_connect(bool show) const;
void collapse(bool collapse);
void change_top_border_for_mode_sizer(bool increase_border);
void set_extruders_count(size_t extruders_count);
void update_mode();
void update_ui_from_settings();
void update_objects_list_extruder_column(size_t extruders_count);
void update_presets(Preset::Type preset_type);
void update_mode_markers();
void update_printer_presets_combobox();
void msw_rescale();
void sys_color_changed();
#ifdef _MSW_DARK_MODE
void show_mode_sizer(bool show);
#endif
bool is_collapsed{ false };
};

View File

@ -67,7 +67,8 @@
#include "SavePresetDialog.hpp"
#include "EditGCodeDialog.hpp"
#include "MsgDialog.hpp"
#include "Notebook.hpp"
//#include "Notebook.hpp"
#include "TopBar.hpp"
#include "Widgets/CheckBox.hpp"
@ -88,6 +89,8 @@ Tab::Tab(wxBookCtrlBase* parent, const wxString& title, Preset::Type type) :
wxGetApp().UpdateDarkUI(this);
#elif __WXOSX__
SetBackgroundColour(parent->GetBackgroundColour());
#elif __WXGTK3__
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
#endif
m_compatible_printers.type = Preset::TYPE_PRINTER;
@ -203,9 +206,6 @@ void Tab::create_preset_tab()
m_question_btn->SetToolTip(_(L("Hover the cursor over buttons to find more information \n"
"or click this button.")));
add_scaled_button(panel, &m_search_btn, "search");
m_search_btn->SetToolTip(format_wxstr(_L("Search in settings [%1%]"), "Ctrl+F"));
// Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field.
add_scaled_bitmap(this, m_bmp_value_lock , "lock_closed");
add_scaled_bitmap(this, m_bmp_value_unlock, "lock_open");
@ -229,19 +229,12 @@ void Tab::create_preset_tab()
if (dlg.ShowModal() == wxID_OK)
wxGetApp().update_label_colours();
});
m_search_btn->Bind(wxEVT_BUTTON, [](wxCommandEvent) { wxGetApp().show_search_dialog(); });
// Colors for ui "decoration"
m_sys_label_clr = wxGetApp().get_label_clr_sys();
m_modified_label_clr = wxGetApp().get_label_clr_modified();
m_default_text_clr = wxGetApp().get_label_clr_default();
#ifdef _MSW_DARK_MODE
// Sizer with buttons for mode changing
if (wxGetApp().tabs_as_menu())
#endif
m_mode_sizer = new ModeSizer(panel, int (0.5*em_unit(this)));
const float scale_factor = em_unit(this)*0.1;// GetContentScaleFactor();
m_top_hsizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(m_top_hsizer, 0, wxEXPAND | wxBOTTOM, 3);
@ -265,22 +258,12 @@ void Tab::create_preset_tab()
m_h_buttons_sizer->AddSpacer(int(32 * scale_factor));
m_h_buttons_sizer->Add(m_undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL);
m_h_buttons_sizer->Add(m_undo_btn, 0, wxALIGN_CENTER_VERTICAL);
m_h_buttons_sizer->AddSpacer(int(32 * scale_factor));
m_h_buttons_sizer->Add(m_search_btn, 0, wxALIGN_CENTER_VERTICAL);
m_h_buttons_sizer->AddSpacer(int(8*scale_factor));
m_h_buttons_sizer->Add(m_btn_compare_preset, 0, wxALIGN_CENTER_VERTICAL);
m_top_hsizer->Add(m_h_buttons_sizer, 1, wxEXPAND);
m_top_hsizer->AddSpacer(int(16*scale_factor));
// StretchSpacer has a strange behavior under OSX, so
// There is used just additional sizer for m_mode_sizer with right alignment
if (m_mode_sizer) {
auto mode_sizer = new wxBoxSizer(wxVERTICAL);
// Don't set the 2nd parameter to 1, making the sizer rubbery scalable in Y axis may lead
// to wrong vertical size assigned to wxBitmapComboBoxes, see GH issue #7176.
mode_sizer->Add(m_mode_sizer, 0, wxALIGN_RIGHT);
m_top_hsizer->Add(mode_sizer, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, wxOSX ? 15 : 10);
}
// hide whole top sizer to correct layout later
m_top_hsizer->ShowItems(false);
@ -923,10 +906,6 @@ void Tab::update_mode()
{
m_mode = wxGetApp().get_mode();
// update mode for ModeSizer
if (m_mode_sizer)
m_mode_sizer->SetMode(m_mode);
update_visibility();
update_changed_tree_ui();
@ -934,10 +913,6 @@ void Tab::update_mode()
void Tab::update_mode_markers()
{
// update mode for ModeSizer
if (m_mode_sizer)
m_mode_sizer->update_mode_markers();
if (m_active_page)
m_active_page->refresh();
}
@ -1002,8 +977,6 @@ void Tab::sys_color_changed()
update_label_colours();
#ifdef _WIN32
wxWindowUpdateLocker noUpdates(this);
if (m_mode_sizer)
m_mode_sizer->sys_color_changed();
wxGetApp().UpdateDarkUI(this);
wxGetApp().UpdateDarkUI(m_treectrl);
#endif
@ -1177,14 +1150,14 @@ void Tab::activate_option(const std::string& opt_key, const wxString& category)
{
wxString page_title = translate_category(category, m_type);
auto cur_item = m_treectrl->GetFirstVisibleItem();
if (!cur_item)
return;
// We should to activate a tab with searched option, if it doesn't.
// And do it before finding of the cur_item to avoid a case when Tab isn't activated jet and all treeItems are invisible
wxGetApp().mainframe->select_tab(this);
auto cur_item = m_treectrl->GetFirstVisibleItem();
if (!cur_item)
return;
while (cur_item) {
auto title = m_treectrl->GetItemText(cur_item);
if (page_title != title) {
@ -3648,13 +3621,6 @@ void Tab::load_current_preset()
// m_undo_to_sys_btn->Enable(!preset.is_default);
#if 0
// use CallAfter because some field triggers schedule on_change calls using CallAfter,
// and we don't want them to be called after this update_dirty() as they would mark the
// preset dirty again
// (not sure this is true anymore now that update_dirty is idempotent)
wxTheApp->CallAfter([this]
#endif
{
// checking out if this Tab exists till this moment
if (!wxGetApp().checked_tab(this))
@ -3682,16 +3648,15 @@ void Tab::load_current_preset()
}
if (tab->supports_printer_technology(printer_technology))
{
#ifdef _MSW_DARK_MODE
#ifdef _WIN32
if (!wxGetApp().tabs_as_menu()) {
std::string bmp_name = tab->type() == Slic3r::Preset::TYPE_FILAMENT ? "spool" :
tab->type() == Slic3r::Preset::TYPE_SLA_MATERIAL ? "resin" : "cog";
tab->Hide(); // #ys_WORKAROUND : Hide tab before inserting to avoid unwanted rendering of the tab
dynamic_cast<Notebook*>(wxGetApp().tab_panel())->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title(), bmp_name);
#endif
dynamic_cast<TopBar*>(wxGetApp().tab_panel())->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title(),"");
#ifdef _WIN32
}
else
#endif
wxGetApp().tab_panel()->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title());
#endif
#ifdef __linux__ // the tabs apparently need to be explicitly shown on Linux (pull request #1563)
int page_id = wxGetApp().tab_panel()->FindPage(tab);
wxGetApp().tab_panel()->GetPage(page_id)->Show(true);
@ -3705,10 +3670,6 @@ void Tab::load_current_preset()
}
static_cast<TabPrinter*>(this)->m_printer_technology = printer_technology;
m_active_page = tmp_page;
#ifdef _MSW_DARK_MODE
if (!wxGetApp().tabs_as_menu())
dynamic_cast<Notebook*>(wxGetApp().tab_panel())->SetPageImage(wxGetApp().tab_panel()->FindPage(this), printer_technology == ptFFF ? "printer" : "sla_printer");
#endif
}
on_presets_changed();
if (printer_technology == ptFFF) {

View File

@ -184,7 +184,6 @@ protected:
std::string m_name;
const wxString m_title;
TabPresetComboBox* m_presets_choice;
ScalableButton* m_search_btn;
ScalableButton* m_btn_compare_preset;
ScalableButton* m_btn_save_preset;
ScalableButton* m_btn_rename_preset;
@ -200,8 +199,6 @@ protected:
wxScrolledWindow* m_page_view {nullptr};
wxBoxSizer* m_page_sizer {nullptr};
ModeSizer* m_mode_sizer {nullptr};
struct PresetDependencies {
Preset::Type type = Preset::TYPE_INVALID;
wxWindow *checkbox = nullptr;
@ -478,7 +475,8 @@ class TabFilament : public Tab
std::map<std::string, wxWindow*> m_overrides_options;
public:
TabFilament(wxBookCtrlBase* parent) :
Tab(parent, _(L("Filament Settings")), Slic3r::Preset::TYPE_FILAMENT) {}
// Tab(parent, _(L("Filament Settings")), Slic3r::Preset::TYPE_FILAMENT) {}
Tab(parent, _L("Filaments"), Slic3r::Preset::TYPE_FILAMENT) {}
~TabFilament() {}
void build() override;
@ -536,7 +534,8 @@ public:
PrinterTechnology m_printer_technology = ptFFF;
TabPrinter(wxBookCtrlBase* parent) :
Tab(parent, _L("Printer Settings"), Slic3r::Preset::TYPE_PRINTER) {}
// Tab(parent, _L("Printer Settings"), Slic3r::Preset::TYPE_PRINTER) {}
Tab(parent, _L("Printers"), Slic3r::Preset::TYPE_PRINTER) {}
~TabPrinter() {}
void build() override;
@ -574,7 +573,8 @@ class TabSLAMaterial : public Tab
std::map<std::string, wxWindow*> m_overrides_options;
public:
TabSLAMaterial(wxBookCtrlBase* parent) :
Tab(parent, _(L("Material Settings")), Slic3r::Preset::TYPE_SLA_MATERIAL) {}
// Tab(parent, _(L("Material Settings")), Slic3r::Preset::TYPE_SLA_MATERIAL) {}
Tab(parent, _L("Materials"), Slic3r::Preset::TYPE_SLA_MATERIAL) {}
~TabSLAMaterial() {}
void build() override;

519
src/slic3r/GUI/TopBar.cpp Normal file
View File

@ -0,0 +1,519 @@
#include "TopBar.hpp"
#include "GUI_App.hpp"
#include "Plater.hpp"
#include "Search.hpp"
#include "UserAccount.hpp"
//#include "wxExtensions.hpp"
#include "format.hpp"
#include "I18N.hpp"
#include <wx/button.h>
#include <wx/sizer.h>
wxDEFINE_EVENT(wxCUSTOMEVT_TOPBAR_SEL_CHANGED, wxCommandEvent);
using namespace Slic3r::GUI;
#ifdef __APPLE__
#define down_arrow L"\u25BC";
#else
#define down_arrow L"\u23f7";
#endif
TopBarItemsCtrl::Button::Button(wxWindow* parent, const wxString& label, const std::string& icon_name, const int px_cnt)
:ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, wxNO_BORDER, px_cnt)
#ifdef _WIN32
,m_background_color(wxGetApp().get_window_default_clr())
#else
,m_background_color(wxTransparentColor)
#endif
,m_foreground_color(wxGetApp().get_label_clr_default())
,m_bmp_bundle(icon_name.empty() ? wxBitmapBundle() : *get_bmp_bundle(icon_name, px_cnt))
{
int btn_margin = em_unit(this);
int x, y;
GetTextExtent(label, &x, &y);
wxSize size(x + 4 * btn_margin, y + int(1.5 * btn_margin));
if (icon_name.empty())
this->SetMinSize(size);
#ifdef _WIN32
else if (label.IsEmpty()) {
const int btn_side = px_cnt + btn_margin;
this->SetMinSize(wxSize(btn_side, btn_side));
}
else
this->SetMinSize(wxSize(-1, size.y));
#else
else if (label.IsEmpty())
this->SetMinSize(wxSize(px_cnt, px_cnt));
else
this->SetMinSize(wxSize(size.x + px_cnt, size.y));
#endif
//button events
Bind(wxEVT_SET_FOCUS, [this](wxFocusEvent& event) { set_hovered(true ); event.Skip(); });
Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& event) { set_hovered(false); event.Skip(); });
Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& event) { set_hovered(true ); event.Skip(); });
Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& event) { set_hovered(false); event.Skip(); });
Bind(wxEVT_PAINT, [this](wxPaintEvent&) { render(); });
}
void TopBarItemsCtrl::Button::set_selected(bool selected)
{
m_is_selected = selected;
m_foreground_color = m_is_selected ? wxGetApp().get_window_default_clr(): wxGetApp().get_label_clr_default() ;
m_background_color = m_is_selected ? wxGetApp().get_label_clr_default() :
#ifdef _WIN32
wxGetApp().get_window_default_clr();
#else
wxTransparentColor;
#endif
#ifdef __linux__
this->SetBackgroundColour(m_background_color);
this->SetForegroundColour(m_foreground_color);
this->Refresh();
this->Update();
#endif // __linux__
}
void TopBarItemsCtrl::Button::set_hovered(bool hovered)
{
using namespace Slic3r::GUI;
const wxFont& new_font = hovered ? wxGetApp().bold_font() : wxGetApp().normal_font();
this->SetFont(new_font);
#ifdef _WIN32
this->GetParent()->Refresh(); // force redraw a background of the selected mode button
#endif /* no _WIN32 */
m_background_color = hovered ? wxGetApp().get_color_selected_btn_bg() :
m_is_selected ? wxGetApp().get_label_clr_default() :
#ifdef _WIN32
wxGetApp().get_window_default_clr();
#else
wxTransparentColor;
#endif
#ifdef __linux__
this->SetBackgroundColour(m_background_color);
#endif // __linux__
this->Refresh();
this->Update();
}
void TopBarItemsCtrl::Button::render()
{
const wxRect rc(GetSize());
wxPaintDC dc(this);
#ifdef _WIN32
// Draw default background
dc.SetPen(wxGetApp().get_window_default_clr());
dc.SetBrush(wxGetApp().get_window_default_clr());
dc.DrawRectangle(rc);
#endif
int em = em_unit(this);
// Draw def rect with rounded corners
dc.SetPen(m_background_color);
dc.SetBrush(m_background_color);
dc.DrawRoundedRectangle(rc, int(0.4* em));
wxPoint pt = { 0, 0 };
if (m_bmp_bundle.IsOk()) {
wxSize szIcon = get_preferred_size(m_bmp_bundle, this);
pt.x = em;
pt.y = (rc.height - szIcon.y) / 2;
#ifdef __WXGTK3__
dc.DrawBitmap(m_bmp_bundle.GetBitmap(szIcon), pt);
#else
dc.DrawBitmap(m_bmp_bundle.GetBitmapFor(this), pt);
#endif
pt.x += szIcon.x;
}
// Draw text
wxString text = GetLabelText();
if (!text.IsEmpty()) {
wxSize labelSize = dc.GetTextExtent(text);
if (labelSize.x > rc.width)
text = wxControl::Ellipsize(text, dc, wxELLIPSIZE_END, rc.width);
pt.x += (rc.width - pt.x - labelSize.x) / 2;
pt.y = (rc.height - labelSize.y) / 2;
dc.SetTextForeground(m_foreground_color);
dc.SetFont(GetFont());
dc.DrawText(text, pt);
}
}
void TopBarItemsCtrl::Button::sys_color_changed()
{
ScalableButton::sys_color_changed();
#ifdef _WIN32
m_background_color = wxGetApp().get_window_default_clr();
#endif
m_foreground_color = wxGetApp().get_label_clr_default();
}
TopBarItemsCtrl::ButtonWithPopup::ButtonWithPopup(wxWindow* parent, const wxString& label, const std::string& icon_name)
:TopBarItemsCtrl::Button(parent, label, icon_name, 24)
{
this->SetLabel(label);
}
TopBarItemsCtrl::ButtonWithPopup::ButtonWithPopup(wxWindow* parent, const std::string& icon_name, int icon_width/* = 20*/, int icon_height/* = 20*/)
:TopBarItemsCtrl::Button(parent, "", icon_name, icon_width)
{
}
void TopBarItemsCtrl::ButtonWithPopup::SetLabel(const wxString& label)
{
wxString full_label = " " + label + " " + down_arrow;
ScalableButton::SetLabel(full_label);
}
static wxString get_workspace_name(Slic3r::ConfigOptionMode mode)
{
return mode == Slic3r::ConfigOptionMode::comSimple ? _L("Beginners") :
mode == Slic3r::ConfigOptionMode::comAdvanced ? _L("Regulars") : _L("Experts");
}
void TopBarItemsCtrl::ApplyWorkspacesMenu()
{
wxMenuItemList& items = m_workspaces_menu.GetMenuItems();
if (!items.IsEmpty()) {
for (int id = int(m_workspaces_menu.GetMenuItemCount()) - 1; id >= 0; id--)
m_workspaces_menu.Destroy(items[id]);
}
for (const Slic3r::ConfigOptionMode& mode : { Slic3r::ConfigOptionMode::comSimple,
Slic3r::ConfigOptionMode::comAdvanced,
Slic3r::ConfigOptionMode::comExpert }) {
const wxString label = get_workspace_name(mode);
append_menu_item(&m_workspaces_menu, wxID_ANY, label, label,
[mode](wxCommandEvent&) {
if (wxGetApp().get_mode() != mode)
wxGetApp().save_mode(mode);
}, get_bmp_bundle("mode", 16, -1, wxGetApp().get_mode_btn_color(mode)));
if (mode < Slic3r::ConfigOptionMode::comExpert)
m_workspaces_menu.AppendSeparator();
}
}
void TopBarItemsCtrl::CreateAccountMenu()
{
m_user_menu_item = append_menu_item(&m_account_menu, wxID_ANY, "", "",
[this](wxCommandEvent& e) {
m_account_btn->set_selected(true);
wxGetApp().plater()->PopupMenu(&m_account_menu, m_account_btn->GetPosition());
}, get_bmp_bundle("user", 16));
m_account_menu.AppendSeparator();
#if 0
m_connect_dummy_menu_item = append_menu_item(&m_account_menu, wxID_ANY, _L("PrusaConnect Printers"), "",
[](wxCommandEvent&) { wxGetApp().plater()->get_user_account()->enqueue_connect_printers_action(); },
"", nullptr, []() { return wxGetApp().plater()->get_user_account()->is_logged(); }, this->GetParent());
#endif // 0
wxMenuItem* remember_me_menu_item = append_menu_check_item(&m_account_menu, wxID_ANY, _L("Remember me"), ""
, [](wxCommandEvent&) { wxGetApp().plater()->get_user_account()->toggle_remember_session(); }
, &m_account_menu
, []() { return wxGetApp().plater()->get_user_account() ? wxGetApp().plater()->get_user_account()->is_logged() : false; }
, []() { return wxGetApp().plater()->get_user_account() ? wxGetApp().plater()->get_user_account()->get_remember_session() : false; }
, this->GetParent());
m_login_menu_item = append_menu_item(&m_account_menu, wxID_ANY, "", "",
[](wxCommandEvent&) {
auto user_account = wxGetApp().plater()->get_user_account();
if (user_account->is_logged())
user_account->do_logout();
else
user_account->do_login();
}, get_bmp_bundle("login", 16));
}
void TopBarItemsCtrl::UpdateAccountMenu(bool avatar/* = false*/)
{
auto user_account = wxGetApp().plater()->get_user_account();
if (m_login_menu_item) {
m_login_menu_item->SetItemLabel(user_account->is_logged() ? _L("Prusa Account Log out") : _L("Prusa Account Log in"));
m_login_menu_item->SetBitmap(user_account->is_logged() ? *get_bmp_bundle("logout", 16) : *get_bmp_bundle("login", 16));
}
const wxString user_name = user_account->is_logged() ? from_u8(user_account->get_username()) : _L("Anonymous");
if (m_user_menu_item)
m_user_menu_item->SetItemLabel(user_name);
m_account_btn->SetLabel(user_name);
#ifdef __linux__
if (avatar) {
if (user_account->is_logged()) {
boost::filesystem::path path = user_account->get_avatar_path(true);
ScalableBitmap new_logo(this, path, m_account_btn->GetBitmapSize());
if (new_logo.IsOk())
m_account_btn->SetBitmap_(new_logo);
else
m_account_btn->SetBitmap_("user");
}
else {
m_account_btn->SetBitmap_("user");
}
}
#else
if (avatar) {
if (user_account->is_logged()) {
boost::filesystem::path path = user_account->get_avatar_path(true);
ScalableBitmap new_logo(this, path, m_account_btn->GetBitmapSize());
if (new_logo.IsOk())
m_account_btn->SetBitmapBundle(new_logo.bmp());
else
m_account_btn->SetBitmapBundle(*get_bmp_bundle("user"));
}
else {
m_account_btn->SetBitmapBundle(*get_bmp_bundle("user"));
}
}
#endif
m_account_btn->Refresh();
}
void TopBarItemsCtrl::CreateSearch()
{
m_search = new ::TextInput(this, wxGetApp().searcher().default_string, "", "search", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
m_search->SetMaxSize(wxSize(42*em_unit(this), -1));
wxGetApp().UpdateDarkUI(m_search);
wxGetApp().searcher().set_search_input(m_search);
}
void TopBarItemsCtrl::update_margins()
{
int em = em_unit(this);
m_btn_margin = std::lround(0.9 * em);
m_line_margin = std::lround(0.1 * em);
}
TopBarItemsCtrl::TopBarItemsCtrl(wxWindow *parent) :
wxControl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE | wxTAB_TRAVERSAL)
{
#ifdef __WINDOWS__
SetDoubleBuffered(true);
#endif //__WINDOWS__
update_margins();
m_sizer = new wxFlexGridSizer(2);
m_sizer->AddGrowableCol(0);
m_sizer->SetFlexibleDirection(wxHORIZONTAL);
this->SetSizer(m_sizer);
wxBoxSizer* left_sizer = new wxBoxSizer(wxHORIZONTAL);
#ifdef __APPLE__
auto logo = new wxStaticBitmap(this, wxID_ANY, *get_bmp_bundle(wxGetApp().logo_name(), 40));
left_sizer->Add(logo, 0, wxALIGN_CENTER_VERTICAL | wxALL, m_btn_margin);
#else
m_menu_btn = new ButtonWithPopup(this, _L("Menu"), wxGetApp().logo_name());
left_sizer->Add(m_menu_btn, 0, wxALIGN_CENTER_VERTICAL | wxALL, m_btn_margin);
m_menu_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) {
m_menu_btn->set_selected(true);
wxPoint pos = m_menu_btn->GetPosition();
wxGetApp().plater()->PopupMenu(&m_main_menu, pos);
});
m_main_menu.Bind(wxEVT_MENU_CLOSE, [this](wxMenuEvent&) { m_menu_btn->set_selected(false); });
#endif
m_buttons_sizer = new wxFlexGridSizer(1, m_btn_margin, m_btn_margin);
left_sizer->Add(m_buttons_sizer, 0, wxALIGN_CENTER_VERTICAL/* | wxLEFT*/ | wxRIGHT, 2 * m_btn_margin);
CreateSearch();
wxBoxSizer* search_sizer = new wxBoxSizer(wxVERTICAL);
search_sizer->Add(m_search, 0, wxEXPAND | wxALIGN_RIGHT);
left_sizer->Add(search_sizer, 1, wxALIGN_CENTER_VERTICAL);
m_sizer->Add(left_sizer, 1, wxEXPAND);
wxBoxSizer* right_sizer = new wxBoxSizer(wxHORIZONTAL);
// create modes menu
ApplyWorkspacesMenu();
m_workspace_btn = new ButtonWithPopup(this, _L("Workspace"), "mode_simple");
right_sizer->AddStretchSpacer(20);
right_sizer->Add(m_workspace_btn, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT);
m_workspace_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) {
m_workspace_btn->set_selected(true);
wxPoint pos = m_workspace_btn->GetPosition();
wxGetApp().plater()->PopupMenu(&m_workspaces_menu, pos);
});
m_workspaces_menu.Bind(wxEVT_MENU_CLOSE, [this](wxMenuEvent&) { m_workspace_btn->set_selected(false); });
// create Account menu
CreateAccountMenu();
m_account_btn = new ButtonWithPopup(this, _L("Anonymous"), "user");
right_sizer->Add(m_account_btn, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxRIGHT | wxLEFT, m_btn_margin);
m_account_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) {
UpdateAccountMenu();
m_account_btn->set_selected(true);
wxPoint pos = m_account_btn->GetPosition();
wxGetApp().plater()->PopupMenu(&m_account_menu, pos);
});
m_account_menu.Bind(wxEVT_MENU_CLOSE, [this](wxMenuEvent&) { m_account_btn->set_selected(false); });
m_sizer->Add(right_sizer, 0, wxALIGN_CENTER_VERTICAL);
m_sizer->SetItemMinSize(1, wxSize(42 * wxGetApp().em_unit(), -1));
this->Bind(wxEVT_PAINT, &TopBarItemsCtrl::OnPaint, this);
}
void TopBarItemsCtrl::OnPaint(wxPaintEvent&)
{
wxGetApp().UpdateDarkUI(this);
m_search->Refresh();
}
void TopBarItemsCtrl::UpdateMode()
{
auto mode = wxGetApp().get_mode();
wxBitmapBundle bmp = *get_bmp_bundle("mode", 16, -1, wxGetApp().get_mode_btn_color(mode));
#ifdef __linux__
m_workspace_btn->SetBitmap(bmp);
m_workspace_btn->SetBitmapCurrent(bmp);
m_workspace_btn->SetBitmapPressed(bmp);
#else
m_workspace_btn->SetBitmapBundle(bmp);
#endif
m_workspace_btn->SetLabel(get_workspace_name(mode));
this->Layout();
}
void TopBarItemsCtrl::Rescale()
{
update_margins();
int em = em_unit(this);
m_search->SetMinSize(wxSize(4 * em, -1));
m_search->SetMaxSize(wxSize(42 * em, -1));
m_search->Rescale();
m_sizer->SetItemMinSize(1, wxSize(42 * em, -1));
m_buttons_sizer->SetVGap(m_btn_margin);
m_buttons_sizer->SetHGap(m_btn_margin);
m_sizer->Layout();
}
void TopBarItemsCtrl::OnColorsChanged()
{
wxGetApp().UpdateDarkUI(this);
if (m_menu_btn)
m_menu_btn->sys_color_changed();
m_workspace_btn->sys_color_changed();
m_account_btn->sys_color_changed();
m_search->SysColorsChanged();
UpdateSelection();
UpdateMode();
m_sizer->Layout();
}
void TopBarItemsCtrl::UpdateModeMarkers()
{
UpdateMode();
ApplyWorkspacesMenu();
}
void TopBarItemsCtrl::UpdateSelection()
{
for (Button* btn : m_pageButtons)
btn->set_selected(false);
if (m_selection >= 0)
m_pageButtons[m_selection]->set_selected(true);
Refresh();
}
void TopBarItemsCtrl::SetSelection(int sel)
{
if (m_selection == sel)
return;
m_selection = sel;
UpdateSelection();
}
bool TopBarItemsCtrl::InsertPage(size_t n, const wxString& text, bool bSelect/* = false*/, const std::string& bmp_name/* = ""*/)
{
Button* btn = new Button(this, text);
btn->Bind(wxEVT_BUTTON, [this, btn](wxCommandEvent& event) {
if (auto it = std::find(m_pageButtons.begin(), m_pageButtons.end(), btn); it != m_pageButtons.end()) {
m_selection = it - m_pageButtons.begin();
wxCommandEvent evt = wxCommandEvent(wxCUSTOMEVT_TOPBAR_SEL_CHANGED);
evt.SetId(m_selection);
wxPostEvent(this->GetParent(), evt);
UpdateSelection();
}
});
m_pageButtons.insert(m_pageButtons.begin() + n, btn);
m_buttons_sizer->Insert(n, new wxSizerItem(btn, 0, wxALIGN_CENTER_VERTICAL));
m_buttons_sizer->SetCols(m_buttons_sizer->GetCols() + 1);
m_sizer->Layout();
return true;
}
void TopBarItemsCtrl::RemovePage(size_t n)
{
ScalableButton* btn = m_pageButtons[n];
m_pageButtons.erase(m_pageButtons.begin() + n);
m_buttons_sizer->Remove(n);
btn->Reparent(nullptr);
btn->Destroy();
m_sizer->Layout();
}
void TopBarItemsCtrl::SetPageText(size_t n, const wxString& strText)
{
ScalableButton* btn = m_pageButtons[n];
btn->SetLabel(strText);
}
wxString TopBarItemsCtrl::GetPageText(size_t n) const
{
ScalableButton* btn = m_pageButtons[n];
return btn->GetLabel();
}
void TopBarItemsCtrl::AppendMenuItem(wxMenu* menu, const wxString& title)
{
append_submenu(&m_main_menu, menu, wxID_ANY, title, "cog");
}
void TopBarItemsCtrl::AppendMenuSeparaorItem()
{
m_main_menu.AppendSeparator();
}

490
src/slic3r/GUI/TopBar.hpp Normal file
View File

@ -0,0 +1,490 @@
#ifndef slic3r_TopBar_hpp_
#define slic3r_TopBar_hpp_
//#ifdef _WIN32
#include <wx/bookctrl.h>
#include "wxExtensions.hpp"
#include "Widgets/TextInput.hpp"
class ModeSizer;
//class ScalableButton;
// custom message the TopBarItemsCtrl sends to its parent (TopBar) to notify a selection change:
wxDECLARE_EVENT(wxCUSTOMEVT_TOPBAR_SEL_CHANGED, wxCommandEvent);
class TopBarItemsCtrl : public wxControl
{
class Button : public ScalableButton
{
bool m_is_selected{ false };
wxColour m_background_color;
wxColour m_foreground_color;
wxBitmapBundle m_bmp_bundle = wxBitmapBundle();
public:
Button() {};
Button( wxWindow* parent,
const wxString& label,
const std::string& icon_name = "",
const int px_cnt = 16);
~Button() {}
void set_selected(bool selected);
void set_hovered (bool hovered);
void render();
void sys_color_changed() override;
void SetBitmapBundle(wxBitmapBundle bmp_bundle) { m_bmp_bundle = bmp_bundle; }
};
class ButtonWithPopup : public Button
{
public:
ButtonWithPopup() {};
ButtonWithPopup(wxWindow* parent,
const wxString& label,
const std::string& icon_name = "");
ButtonWithPopup(wxWindow* parent,
const std::string& icon_name,
int icon_width = 20,
int icon_height = 20);
~ButtonWithPopup() {}
void SetLabel(const wxString& label) override;
};
wxMenu m_main_menu;
wxMenu m_workspaces_menu;
wxMenu m_account_menu;
// Prusa Account menu items
wxMenuItem* m_user_menu_item{ nullptr };
wxMenuItem* m_login_menu_item{ nullptr };
#if 0
wxMenuItem* m_connect_dummy_menu_item{ nullptr };
#endif // 0
::TextInput* m_search{ nullptr };
public:
TopBarItemsCtrl(wxWindow* parent);
~TopBarItemsCtrl() {}
void OnPaint(wxPaintEvent&);
void SetSelection(int sel);
void UpdateMode();
void Rescale();
void OnColorsChanged();
void UpdateModeMarkers();
void UpdateSelection();
bool InsertPage(size_t n, const wxString& text, bool bSelect = false, const std::string& bmp_name = "");
void RemovePage(size_t n);
void SetPageText(size_t n, const wxString& strText);
wxString GetPageText(size_t n) const;
void AppendMenuItem(wxMenu* menu, const wxString& title);
void AppendMenuSeparaorItem();
void ApplyWorkspacesMenu();
void CreateAccountMenu();
void UpdateAccountMenu(bool avatar = false);
void CreateSearch();
wxWindow* GetSearchCtrl() { return m_search->GetTextCtrl(); }
private:
wxFlexGridSizer* m_buttons_sizer;
wxFlexGridSizer* m_sizer;
ButtonWithPopup* m_menu_btn {nullptr};
ButtonWithPopup* m_workspace_btn {nullptr};
ButtonWithPopup* m_account_btn {nullptr};
std::vector<Button*> m_pageButtons;
int m_selection {-1};
int m_btn_margin;
int m_line_margin;
void update_margins();
};
class TopBar : public wxBookCtrlBase
{
public:
TopBar(wxWindow * parent,
wxWindowID winid = wxID_ANY,
const wxPoint & pos = wxDefaultPosition,
const wxSize & size = wxDefaultSize,
long style = 0)
{
Init();
Create(parent, winid, pos, size, style);
}
bool Create(wxWindow * parent,
wxWindowID winid = wxID_ANY,
const wxPoint & pos = wxDefaultPosition,
const wxSize & size = wxDefaultSize,
long style = 0)
{
if (!wxBookCtrlBase::Create(parent, winid, pos, size, style | wxBK_TOP))
return false;
m_bookctrl = new TopBarItemsCtrl(this);
wxSizer* mainSizer = new wxBoxSizer(IsVertical() ? wxVERTICAL : wxHORIZONTAL);
if (style & wxBK_RIGHT || style & wxBK_BOTTOM)
mainSizer->Add(0, 0, 1, wxEXPAND, 0);
m_controlSizer = new wxBoxSizer(IsVertical() ? wxHORIZONTAL : wxVERTICAL);
m_controlSizer->Add(m_bookctrl, wxSizerFlags(1).Expand());
wxSizerFlags flags;
if (IsVertical())
flags.Expand();
else
flags.CentreVertical();
mainSizer->Add(m_controlSizer, flags.Border(wxALL, m_controlMargin));
SetSizer(mainSizer);
this->Bind(wxCUSTOMEVT_TOPBAR_SEL_CHANGED, [this](wxCommandEvent& evt)
{
if (int page_idx = evt.GetId(); page_idx >= 0)
SetSelection(page_idx);
});
this->Bind(wxEVT_NAVIGATION_KEY, &TopBar::OnNavigationKey, this);
return true;
}
// Methods specific to this class.
// A method allowing to add a new page without any label (which is unused
// by this control) and show it immediately.
bool ShowNewPage(wxWindow * page)
{
return AddPage(page, wxString(), ""/*true *//* select it */);
}
// Set effect to use for showing/hiding pages.
void SetEffects(wxShowEffect showEffect, wxShowEffect hideEffect)
{
m_showEffect = showEffect;
m_hideEffect = hideEffect;
}
// Or the same effect for both of them.
void SetEffect(wxShowEffect effect)
{
SetEffects(effect, effect);
}
// And the same for time outs.
void SetEffectsTimeouts(unsigned showTimeout, unsigned hideTimeout)
{
m_showTimeout = showTimeout;
m_hideTimeout = hideTimeout;
}
void SetEffectTimeout(unsigned timeout)
{
SetEffectsTimeouts(timeout, timeout);
}
// Implement base class pure virtual methods.
// adds a new page to the control
bool AddPage(wxWindow* page,
const wxString& text,
const std::string& bmp_name,
bool bSelect = false)
{
DoInvalidateBestSize();
return InsertPage(GetPageCount(), page, text, bmp_name, bSelect);
}
// Page management
virtual bool InsertPage(size_t n,
wxWindow * page,
const wxString & text,
bool bSelect = false,
int imageId = NO_IMAGE) override
{
if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect, imageId))
return false;
GetTopBarItemsCtrl()->InsertPage(n, text, bSelect);
if (!DoSetSelectionAfterInsertion(n, bSelect))
page->Hide();
return true;
}
bool InsertPage(size_t n,
wxWindow * page,
const wxString & text,
const std::string& bmp_name = "",
bool bSelect = false)
{
if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect))
return false;
GetTopBarItemsCtrl()->InsertPage(n, text, bSelect, bmp_name);
if (bSelect)
SetSelection(n);
return true;
}
virtual int SetSelection(size_t n) override
{
GetTopBarItemsCtrl()->SetSelection(n);
int ret = DoSetSelection(n, SetSelection_SendEvent);
// check that only the selected page is visible and others are hidden:
for (size_t page = 0; page < m_pages.size(); page++)
if (page != n)
m_pages[page]->Hide();
return ret;
}
virtual int ChangeSelection(size_t n) override
{
GetTopBarItemsCtrl()->SetSelection(n);
return DoSetSelection(n);
}
// Neither labels nor images are supported but we still store the labels
// just in case the user code attaches some importance to them.
virtual bool SetPageText(size_t n, const wxString & strText) override
{
wxCHECK_MSG(n < GetPageCount(), false, wxS("Invalid page"));
GetTopBarItemsCtrl()->SetPageText(n, strText);
return true;
}
virtual wxString GetPageText(size_t n) const override
{
wxCHECK_MSG(n < GetPageCount(), wxString(), wxS("Invalid page"));
return GetTopBarItemsCtrl()->GetPageText(n);
}
virtual bool SetPageImage(size_t WXUNUSED(n), int WXUNUSED(imageId)) override
{
return false;
}
virtual int GetPageImage(size_t WXUNUSED(n)) const override
{
return NO_IMAGE;
}
// Override some wxWindow methods too.
virtual void SetFocus() override
{
wxWindow* const page = GetCurrentPage();
if (page)
page->SetFocus();
}
TopBarItemsCtrl* GetTopBarItemsCtrl() const { return static_cast<TopBarItemsCtrl*>(m_bookctrl); }
void UpdateMode()
{
GetTopBarItemsCtrl()->UpdateMode();
}
void Rescale()
{
GetTopBarItemsCtrl()->Rescale();
}
void OnColorsChanged()
{
GetTopBarItemsCtrl()->OnColorsChanged();
}
void UpdateModeMarkers()
{
GetTopBarItemsCtrl()->UpdateModeMarkers();
}
void OnNavigationKey(wxNavigationKeyEvent& event)
{
if (event.IsWindowChange()) {
// change pages
AdvanceSelection(event.GetDirection());
}
else {
// we get this event in 3 cases
//
// a) one of our pages might have generated it because the user TABbed
// out from it in which case we should propagate the event upwards and
// our parent will take care of setting the focus to prev/next sibling
//
// or
//
// b) the parent panel wants to give the focus to us so that we
// forward it to our selected page. We can't deal with this in
// OnSetFocus() because we don't know which direction the focus came
// from in this case and so can't choose between setting the focus to
// first or last panel child
//
// or
//
// c) we ourselves (see MSWTranslateMessage) generated the event
//
wxWindow* const parent = GetParent();
// the wxObject* casts are required to avoid MinGW GCC 2.95.3 ICE
const bool isFromParent = event.GetEventObject() == (wxObject*)parent;
const bool isFromSelf = event.GetEventObject() == (wxObject*)this;
const bool isForward = event.GetDirection();
wxWindow* search_win = (dynamic_cast<TopBarItemsCtrl*>(m_bookctrl)->GetSearchCtrl());
const bool isFromSearch = event.GetEventObject() == (wxObject*)search_win;
if (isFromSearch)
{
// find the target window in the siblings list
wxWindowList& siblings = m_bookctrl->GetChildren();
wxWindowList::compatibility_iterator i = siblings.Find(search_win->GetParent());
i->GetNext()->GetData()->SetFocus();
}
else if (isFromSelf && !isForward)
{
// focus is currently on notebook tab and should leave
// it backwards (Shift-TAB)
event.SetCurrentFocus(this);
parent->HandleWindowEvent(event);
}
else if (isFromParent || isFromSelf)
{
// no, it doesn't come from child, case (b) or (c): forward to a
// page but only if entering notebook page (i.e. direction is
// backwards (Shift-TAB) comething from out-of-notebook, or
// direction is forward (TAB) from ourselves),
if (m_selection != wxNOT_FOUND &&
(!event.GetDirection() || isFromSelf))
{
// so that the page knows that the event comes from it's parent
// and is being propagated downwards
event.SetEventObject(this);
wxWindow* page = m_pages[m_selection];
if (!page->HandleWindowEvent(event))
{
page->SetFocus();
}
//else: page manages focus inside it itself
}
else // otherwise set the focus to the notebook itself
{
SetFocus();
}
}
else
{
// it comes from our child, case (a), pass to the parent, but only
// if the direction is forwards. Otherwise set the focus to the
// notebook itself. The notebook is always the 'first' control of a
// page.
if (!isForward)
{
SetFocus();
}
else if (parent)
{
event.SetCurrentFocus(this);
parent->HandleWindowEvent(event);
}
}
}
}
// Methods for extensions of this class
void AppendMenuItem(wxMenu* menu, const wxString& title) {
GetTopBarItemsCtrl()->AppendMenuItem(menu, title);
}
void AppendMenuSeparaorItem() {
GetTopBarItemsCtrl()->AppendMenuSeparaorItem();
}
protected:
virtual void UpdateSelectedPage(size_t WXUNUSED(newsel)) override
{
// Nothing to do here, but must be overridden to avoid the assert in
// the base class version.
}
virtual wxBookCtrlEvent * CreatePageChangingEvent() const override
{
return new wxBookCtrlEvent(wxEVT_BOOKCTRL_PAGE_CHANGING,
GetId());
}
virtual void MakeChangedEvent(wxBookCtrlEvent & event) override
{
event.SetEventType(wxEVT_BOOKCTRL_PAGE_CHANGED);
}
virtual wxWindow * DoRemovePage(size_t page) override
{
wxWindow* const win = wxBookCtrlBase::DoRemovePage(page);
if (win)
{
GetTopBarItemsCtrl()->RemovePage(page);
DoSetSelectionAfterRemoval(page);
}
return win;
}
virtual void DoSize() override
{
wxWindow* const page = GetCurrentPage();
if (page)
page->SetSize(GetPageRect());
}
virtual void DoShowPage(wxWindow * page, bool show) override
{
if (show)
page->ShowWithEffect(m_showEffect, m_showTimeout);
else
page->HideWithEffect(m_hideEffect, m_hideTimeout);
}
private:
void Init()
{
// We don't need any border as we don't have anything to separate the
// page contents from.
SetInternalBorder(0);
// No effects by default.
m_showEffect =
m_hideEffect = wxSHOW_EFFECT_NONE;
m_showTimeout =
m_hideTimeout = 0;
}
wxShowEffect m_showEffect,
m_hideEffect;
unsigned m_showTimeout,
m_hideTimeout;
};
//#endif // _WIN32
#endif // slic3r_TopBar_hpp_

View File

@ -2067,7 +2067,7 @@ void DiffPresetDialog::button_event(Action act)
get_selected_options(type));
});
else if (!presets_to_save.empty())
process_options([this](Preset::Type type) {
process_options([](Preset::Type type) {
if (Tab* tab = wxGetApp().get_tab(type)) {
tab->update_preset_choice();
wxGetApp().sidebar().update_presets(type);

View File

@ -0,0 +1,370 @@
#include "UserAccount.hpp"
#include "format.hpp"
#include "libslic3r/Utils.hpp"
#include <boost/regex.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/log/trivial.hpp>
#include <wx/stdpaths.h>
namespace pt = boost::property_tree;
namespace Slic3r {
namespace GUI {
UserAccount::UserAccount(wxEvtHandler* evt_handler, AppConfig* app_config, const std::string& instance_hash)
: m_communication(std::make_unique<UserAccountCommunication>(evt_handler, app_config))
, m_instance_hash(instance_hash)
{}
UserAccount::~UserAccount()
{}
void UserAccount::set_username(const std::string& username)
{
m_username = username;
m_communication->set_username(username);
}
void UserAccount::clear()
{
m_username = {};
m_account_user_data.clear();
m_printer_map.clear();
m_communication->do_clear();
}
void UserAccount::set_remember_session(bool remember)
{
m_communication->set_remember_session(remember);
}
void UserAccount::toggle_remember_session()
{
m_communication->set_remember_session(!m_communication->get_remember_session());
}
bool UserAccount::get_remember_session()
{
return m_communication->get_remember_session();
}
bool UserAccount::is_logged()
{
return m_communication->is_logged();
}
void UserAccount::do_login()
{
m_communication->do_login();
}
void UserAccount::do_logout()
{
m_communication->do_logout();
}
std::string UserAccount::get_access_token()
{
return m_communication->get_access_token();
}
std::string UserAccount::get_shared_session_key()
{
return m_communication->get_shared_session_key();
}
boost::filesystem::path UserAccount::get_avatar_path(bool logged) const
{
if (logged) {
const std::string filename = "prusaslicer-avatar-" + m_instance_hash + m_avatar_extension;
return boost::filesystem::path(wxStandardPaths::Get().GetTempDir().utf8_str().data()) / filename;
} else {
return boost::filesystem::path(resources_dir()) / "icons" / "user.svg";
}
}
#if 0
void UserAccount::enqueue_user_id_action()
{
m_communication->enqueue_user_id_action();
}
void UserAccount::enqueue_connect_dummy_action()
{
m_communication->enqueue_connect_dummy_action();
}
void UserAccount::enqueue_connect_user_data_action()
{
m_communication->enqueue_connect_user_data_action();
}
#endif
void UserAccount::enqueue_connect_printers_action()
{
m_communication->enqueue_connect_printers_action();
}
void UserAccount::enqueue_avatar_action()
{
m_communication->enqueue_avatar_action(m_account_user_data["avatar"]);
}
bool UserAccount::on_login_code_recieved(const std::string& url_message)
{
m_communication->on_login_code_recieved(url_message);
return true;
}
bool UserAccount::on_user_id_success(const std::string data, std::string& out_username)
{
boost::property_tree::ptree ptree;
try {
std::stringstream ss(data);
boost::property_tree::read_json(ss, ptree);
}
catch (const std::exception&) {
BOOST_LOG_TRIVIAL(error) << "UserIDUserAction Could not parse server response.";
return false;
}
m_account_user_data.clear();
for (const auto& section : ptree) {
const auto opt = ptree.get_optional<std::string>(section.first);
if (opt) {
BOOST_LOG_TRIVIAL(debug) << static_cast<std::string>(section.first) << " " << *opt;
m_account_user_data[section.first] = *opt;
}
}
if (m_account_user_data.find("public_username") == m_account_user_data.end()) {
BOOST_LOG_TRIVIAL(error) << "User ID message from PrusaAuth did not contain public_username. Login failed. Message data: " << data;
return false;
}
std::string public_username = m_account_user_data["public_username"];
set_username(public_username);
out_username = public_username;
// equeue GET with avatar url
if (m_account_user_data.find("avatar") != m_account_user_data.end()) {
const boost::filesystem::path server_file(m_account_user_data["avatar"]);
m_avatar_extension = server_file.extension().string();
enqueue_avatar_action();
}
else {
BOOST_LOG_TRIVIAL(error) << "User ID message from PrusaAuth did not contain avatar.";
}
// update printers list
enqueue_connect_printers_action();
return true;
}
void UserAccount::on_communication_fail()
{
m_fail_counter++;
if (m_fail_counter > 5) // there is no deeper reason why 5
{
m_communication->enqueue_test_connection();
m_fail_counter = 0;
}
}
namespace {
std::string parse_tree_for_param(const pt::ptree& tree, const std::string& param)
{
for (const auto& section : tree) {
if (section.first == param) {
return section.second.data();
} else {
if (std::string res = parse_tree_for_param(section.second, param); !res.empty())
return res;
}
}
return {};
}
pt::ptree parse_tree_for_subtree(const pt::ptree& tree, const std::string& param)
{
for (const auto& section : tree) {
if (section.first == param) {
return section.second;
}
else {
if (pt::ptree res = parse_tree_for_subtree(section.second, param); !res.empty())
return res;
}
}
return pt::ptree();
}
}
bool UserAccount::on_connect_printers_success(const std::string& data, AppConfig* app_config, bool& out_printers_changed)
{
BOOST_LOG_TRIVIAL(debug) << "PrusaConnect printers message: " << data;
pt::ptree ptree;
try {
std::stringstream ss(data);
pt::read_json(ss, ptree);
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Could not parse prusaconnect message. " << e.what();
return false;
}
// fill m_printer_map with data from ptree
// tree string is in format {"result": [{"printer_type": "1.2.3", "states": [{"printer_state": "OFFLINE", "count": 1}, ...]}, {..}]}
ConnectPrinterStateMap new_printer_map;
assert(ptree.front().first == "result");
for (const auto& printer_tree : ptree.front().second) {
// printer_tree is {"printer_type": "1.2.3", "states": [..]}
std::string name;
ConnectPrinterState state;
const auto type_opt = printer_tree.second.get_optional<std::string>("printer_model");
if (!type_opt) {
continue;
}
// printer_type is actually printer_name for now
name = *type_opt;
// printer should not appear twice
assert(new_printer_map.find(name) == new_printer_map.end());
// prepare all states on 0
new_printer_map[name].reserve(static_cast<size_t>(ConnectPrinterState::CONNECT_PRINTER_STATE_COUNT));
for (size_t i = 0; i < static_cast<size_t>(ConnectPrinterState::CONNECT_PRINTER_STATE_COUNT); i++) {
new_printer_map[name].push_back(0);
}
for (const auto& section : printer_tree.second) {
// section is "printer_type": "1.2.3" OR "states": [..]}
if (section.first == "states") {
for (const auto& subsection : section.second) {
// subsection is {"printer_state": "OFFLINE", "count": 1}
const auto state_opt = subsection.second.get_optional<std::string>("printer_state");
const auto count_opt = subsection.second.get_optional<int>("count");
if (!state_opt || ! count_opt) {
continue;
}
if (auto pair = printer_state_table.find(*state_opt); pair != printer_state_table.end()) {
state = pair->second;
} else {
assert(true); // On this assert, printer_state_table needs to be updated with *state_opt and correct ConnectPrinterState
continue;
}
new_printer_map[name][static_cast<size_t>(state)] = *count_opt;
}
}
}
}
// compare new and old printer map and update old map into new
out_printers_changed = false;
for (const auto& it : new_printer_map) {
if (m_printer_map.find(it.first) == m_printer_map.end()) {
// printer is not in old map, add it by copying data from new map
out_printers_changed = true;
m_printer_map[it.first].reserve(static_cast<size_t>(ConnectPrinterState::CONNECT_PRINTER_STATE_COUNT));
for (size_t i = 0; i < static_cast<size_t>(ConnectPrinterState::CONNECT_PRINTER_STATE_COUNT); i++) {
m_printer_map[it.first].push_back(new_printer_map[it.first][i]);
}
} else {
// printer is in old map, check state by state
for (size_t i = 0; i < static_cast<size_t>(ConnectPrinterState::CONNECT_PRINTER_STATE_COUNT); i++) {
if (m_printer_map[it.first][i] != new_printer_map[it.first][i]) {
out_printers_changed = true;
m_printer_map[it.first][i] = new_printer_map[it.first][i];
}
}
}
}
return true;
}
std::string UserAccount::get_model_from_json(const std::string& message) const
{
std::string out;
try {
std::stringstream ss(message);
pt::ptree ptree;
pt::read_json(ss, ptree);
std::string printer_type = parse_tree_for_param(ptree, "printer_type");
if (auto pair = printer_type_and_name_table.find(printer_type); pair != printer_type_and_name_table.end()) {
out = pair->second;
}
//assert(!out.empty());
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Could not parse prusaconnect message. " << e.what();
}
return out;
}
std::string UserAccount::get_nozzle_from_json(const std::string& message) const
{
std::string out;
try {
std::stringstream ss(message);
pt::ptree ptree;
pt::read_json(ss, ptree);
out = parse_tree_for_param(ptree, "nozzle_diameter");
//assert(!out.empty());
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Could not parse prusaconnect message. " << e.what();
}
return out;
}
std::string UserAccount::get_keyword_from_json(const std::string& json, const std::string& keyword) const
{
std::string out;
try {
std::stringstream ss(json);
pt::ptree ptree;
pt::read_json(ss, ptree);
out = parse_tree_for_param(ptree, keyword);
//assert(!out.empty());
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Could not parse prusaconnect message. " << e.what();
}
return out;
}
void UserAccount::fill_compatible_printers_from_json(const std::string& json, std::vector<std::string>& result) const
{
try {
std::stringstream ss(json);
pt::ptree ptree;
pt::read_json(ss, ptree);
pt::ptree out = parse_tree_for_subtree(ptree, "printer_type_compatible");
if (out.empty()) {
BOOST_LOG_TRIVIAL(error) << "Failed to find compatible_printer_type in printer detail.";
return;
}
for (const auto& sub : out) {
result.emplace_back(sub.second.data());
}
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Could not parse prusaconnect message. " << e.what();
}
}
std::string UserAccount::get_printer_type_from_name(const std::string& printer_name) const
{
for (const auto& pair : printer_type_and_name_table) {
if (pair.second == printer_name) {
return pair.first;
}
}
assert(true); // This assert means printer_type_and_name_table needs a update
return {};
}
}} // namespace slic3r::GUI

View File

@ -0,0 +1,136 @@
#ifndef slic3r_UserAccount_hpp_
#define slic3r_UserAccount_hpp_
#include "UserAccountCommunication.hpp"
#include "libslic3r/AppConfig.hpp"
#include <string>
#include <memory>
#include <boost/filesystem.hpp>
namespace Slic3r{
namespace GUI{
enum class ConnectPrinterState {
CONNECT_PRINTER_OFFLINE,
CONNECT_PRINTER_PRINTING,
CONNECT_PRINTER_PAUSED,//?
CONNECT_PRINTER_STOPPED,//?
CONNECT_PRINTER_IDLE,
CONNECT_PRINTER_FINISHED,
CONNECT_PRINTER_READY, //?
CONNECT_PRINTER_ATTENTION,
CONNECT_PRINTER_BUSY,
CONNECT_PRINTER_ERROR,
CONNECT_PRINTER_STATE_COUNT
};
typedef std::map<std::string, std::vector<size_t>> ConnectPrinterStateMap;
// Class UserAccount should handle every request for entities outside PrusaSlicer like PrusaAuth or PrusaConnect.
// Outside communication is implemented in class UserAccountCommunication that runs separate thread. Results come back in events to Plater.
// All incoming data shoud be stored in UserAccount.
class UserAccount {
public:
UserAccount(wxEvtHandler* evt_handler, Slic3r::AppConfig* app_config, const std::string& instance_hash);
~UserAccount();
bool is_logged();
void do_login();
void do_logout();
void set_remember_session(bool remember);
void toggle_remember_session();
bool get_remember_session();
#if 0
void enqueue_user_id_action();
void enqueue_connect_dummy_action();
void enqueue_connect_user_data_action();
#endif
void enqueue_connect_printers_action();
void enqueue_avatar_action();
// Clears all data and connections, called on logout or EVT_UA_RESET
void clear();
// Functions called from UI where events emmited from UserAccountSession are binded
// Returns bool if data were correctly proccessed
bool on_login_code_recieved(const std::string& url_message);
bool on_user_id_success(const std::string data, std::string& out_username);
// Called on EVT_UA_FAIL, triggers test after several calls
void on_communication_fail();
bool on_connect_printers_success(const std::string& data, AppConfig* app_config, bool& out_printers_changed);
std::string get_username() const { return m_username; }
std::string get_access_token();
std::string get_shared_session_key();
const ConnectPrinterStateMap& get_printer_state_map() const { return m_printer_map; }
boost::filesystem::path get_avatar_path(bool logged) const;
// standalone utility methods
std::string get_model_from_json(const std::string& message) const;
std::string get_nozzle_from_json(const std::string& message) const;
//std::string get_apikey_from_json(const std::string& message) const;
std::string get_keyword_from_json(const std::string& json, const std::string& keyword) const;
void fill_compatible_printers_from_json(const std::string& json, std::vector<std::string>& result) const;
const std::map<std::string, ConnectPrinterState>& get_printer_state_table() const { return printer_state_table; }
std::string get_printer_type_from_name(const std::string& printer_name) const;
private:
void set_username(const std::string& username);
std::string m_instance_hash; // used in avatar path
std::unique_ptr<Slic3r::GUI::UserAccountCommunication> m_communication;
ConnectPrinterStateMap m_printer_map;
std::map<std::string, std::string> m_account_user_data;
std::string m_username;
size_t m_fail_counter { 0 };
std::string m_avatar_extension;
// first string is "printer_type" code from Connect edpoints
const std::map<std::string, std::string> printer_type_and_name_table = {
{"1.2.5", "MK2.5" },
{"1.2.6", "MK2.5S" },
{"1.3.0", "MK3" },
{"1.3.1", "MK3S" },
{"1.3.5", "MK3.5" },
{"1.3.9", "MK3.9" },
{"1.4.0", "MK4" },
{"2.1.0", "MINI" },
{"3.1.0", "XL" },
{"5.1.0", "SL1" },
// ysFIXME : needs to add Connect ids for next printers
/*{"0.0.0", "MK4IS" },
{"0.0.0", "MK3SMMU2S" },
{"0.0.0", "MK3MMU2" },
{"0.0.0", "MK2.5SMMU2S" },
{"0.0.0", "MK2.5MMU2" },
{"0.0.0", "MK2S" },
{"0.0.0", "MK2SMM" },
{"0.0.0", "SL1S" },*/
};
/* TODO:
4 1 0 iXL
6 2 0 Trilab DeltiQ 2
6 2 1 Trilab DelriQ 2 Plus
7 1 0 Trilab AzteQ
7 2 0 Trilab AzteQ Industrial
7 2 1 Trilab AzteQ Industrial Plus
*/
const std::map<std::string, ConnectPrinterState> printer_state_table = {
{"OFFLINE" , ConnectPrinterState::CONNECT_PRINTER_OFFLINE},
{"PRINTING" , ConnectPrinterState::CONNECT_PRINTER_PRINTING},
{"PAUSED" , ConnectPrinterState::CONNECT_PRINTER_PAUSED},
{"STOPPED" , ConnectPrinterState::CONNECT_PRINTER_STOPPED},
{"IDLE" , ConnectPrinterState::CONNECT_PRINTER_IDLE},
{"FINISHED" , ConnectPrinterState::CONNECT_PRINTER_FINISHED},
{"READY" , ConnectPrinterState::CONNECT_PRINTER_READY},
{"ATTENTION", ConnectPrinterState::CONNECT_PRINTER_ATTENTION},
{"BUSY" , ConnectPrinterState::CONNECT_PRINTER_BUSY},
};
};
}} // namespace slic3r::GUI
#endif // slic3r_UserAccount_hpp_

View File

@ -0,0 +1,497 @@
#include "UserAccountCommunication.hpp"
#include "GUI_App.hpp"
#include "GUI.hpp"
#include "format.hpp"
#include "../Utils/Http.hpp"
#include "slic3r/GUI/I18N.hpp"
#include <boost/log/trivial.hpp>
#include <boost/beast/core/detail/base64.hpp>
#include <curl/curl.h>
#include <string>
#include <iostream>
#include <random>
#include <algorithm>
#include <iterator>
#include <regex>
#include <iomanip>
#include <cstring>
#include <cstdint>
#if wxUSE_SECRETSTORE
#include <wx/secretstore.h>
#endif
#ifdef WIN32
#include <wincrypt.h>
#endif // WIN32
#ifdef __APPLE__
#include <CommonCrypto/CommonDigest.h>
#endif
#ifdef __linux__
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#endif // __linux__
namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
namespace {
std::string get_code_from_message(const std::string& url_message)
{
size_t pos = url_message.rfind("code=");
std::string out;
for (size_t i = pos + 5; i < url_message.size(); i++) {
const char& c = url_message[i];
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
out+= c;
else
break;
}
return out;
}
bool is_secret_store_ok()
{
#if wxUSE_SECRETSTORE
wxSecretStore store = wxSecretStore::GetDefault();
wxString errmsg;
if (!store.IsOk(&errmsg)) {
BOOST_LOG_TRIVIAL(warning) << "wxSecretStore is not supported: " << errmsg;
return false;
}
return true;
#else
return false;
#endif
}
bool save_secret(const std::string& opt, const std::string& usr, const std::string& psswd)
{
#if wxUSE_SECRETSTORE
wxSecretStore store = wxSecretStore::GetDefault();
wxString errmsg;
if (!store.IsOk(&errmsg)) {
std::string msg = GUI::format("%1% (%2%).", _u8L("This system doesn't support storing passwords securely"), errmsg);
BOOST_LOG_TRIVIAL(error) << msg;
//show_error(nullptr, msg);
return false;
}
const wxString service = GUI::format_wxstr(L"%1%/PrusaAccount/%2%", SLIC3R_APP_NAME, opt);
const wxString username = boost::nowide::widen(usr);
const wxSecretValue password(boost::nowide::widen(psswd));
if (!store.Save(service, username, password)) {
std::string msg(_u8L("Failed to save credentials to the system secret store."));
BOOST_LOG_TRIVIAL(error) << msg;
//show_error(nullptr, msg);
return false;
}
return true;
#else
BOOST_LOG_TRIVIAL(error) << "wxUSE_SECRETSTORE not supported. Cannot save password to the system store.";
return false;
#endif // wxUSE_SECRETSTORE
}
bool load_secret(const std::string& opt, std::string& usr, std::string& psswd)
{
#if wxUSE_SECRETSTORE
wxSecretStore store = wxSecretStore::GetDefault();
wxString errmsg;
if (!store.IsOk(&errmsg)) {
std::string msg = GUI::format("%1% (%2%).", _u8L("This system doesn't support storing passwords securely"), errmsg);
BOOST_LOG_TRIVIAL(error) << msg;
//show_error(nullptr, msg);
return false;
}
const wxString service = GUI::format_wxstr(L"%1%/PrusaAccount/%2%", SLIC3R_APP_NAME, opt);
wxString username;
wxSecretValue password;
if (!store.Load(service, username, password)) {
std::string msg(_u8L("Failed to load credentials from the system secret store."));
BOOST_LOG_TRIVIAL(error) << msg;
//show_error(nullptr, msg);
return false;
}
usr = into_u8(username);
psswd = into_u8(password.GetAsString());
return true;
#else
BOOST_LOG_TRIVIAL(error) << "wxUSE_SECRETSTORE not supported. Cannot load password from the system store.";
return false;
#endif // wxUSE_SECRETSTORE
}
}
UserAccountCommunication::UserAccountCommunication(wxEvtHandler* evt_handler, AppConfig* app_config)
: m_evt_handler(evt_handler)
, m_app_config(app_config)
{
std::string access_token, refresh_token, shared_session_key;
if (is_secret_store_ok()) {
std::string key0, key1;
load_secret("access_token", key0, access_token);
load_secret("refresh_token", key1, refresh_token);
assert(key0 == key1);
shared_session_key = key0;
} else {
access_token = m_app_config->get("access_token");
refresh_token = m_app_config->get("refresh_token");
shared_session_key = m_app_config->get("shared_session_key");
}
bool has_token = !access_token.empty() && !refresh_token.empty();
m_session = std::make_unique<UserAccountSession>(evt_handler, access_token, refresh_token, shared_session_key, m_app_config->get_bool("connect_polling"));
init_session_thread();
// perform login at the start, but only with tokens
if (has_token)
do_login();
}
UserAccountCommunication::~UserAccountCommunication()
{
if (m_thread.joinable()) {
// Stop the worker thread, if running.
{
// Notify the worker thread to cancel wait on detection polling.
std::lock_guard<std::mutex> lck(m_thread_stop_mutex);
m_thread_stop = true;
}
m_thread_stop_condition.notify_all();
// Wait for the worker thread to stop.
m_thread.join();
}
}
void UserAccountCommunication::set_username(const std::string& username)
{
m_username = username;
{
std::lock_guard<std::mutex> lock(m_session_mutex);
if (is_secret_store_ok()) {
save_secret("access_token", m_session->get_shared_session_key(), m_remember_session ? m_session->get_access_token() : std::string());
save_secret("refresh_token", m_session->get_shared_session_key(), m_remember_session ? m_session->get_refresh_token() : std::string());
}
else {
m_app_config->set("access_token", m_remember_session ? m_session->get_access_token() : std::string());
m_app_config->set("refresh_token", m_remember_session ? m_session->get_refresh_token() : std::string());
m_app_config->set("shared_session_key", m_remember_session ? m_session->get_shared_session_key() : std::string());
}
}
}
void UserAccountCommunication::set_remember_session(bool b)
{
m_remember_session = b;
// tokens needs to be stored or deleted
set_username(m_username);
}
std::string UserAccountCommunication::get_access_token()
{
{
std::lock_guard<std::mutex> lock(m_session_mutex);
return m_session->get_access_token();
}
}
std::string UserAccountCommunication::get_shared_session_key()
{
{
std::lock_guard<std::mutex> lock(m_session_mutex);
return m_session->get_shared_session_key();
}
}
void UserAccountCommunication::set_polling_enabled(bool enabled)
{
{
std::lock_guard<std::mutex> lock(m_session_mutex);
return m_session->set_polling_enabled(enabled);
}
}
void UserAccountCommunication::login_redirect()
{
const std::string AUTH_HOST = "https://test-account.prusa3d.com";
const std::string CLIENT_ID = client_id();
const std::string REDIRECT_URI = "prusaslicer://login";
CodeChalengeGenerator ccg;
m_code_verifier = ccg.generate_verifier();
std::string code_challenge = ccg.generate_chalenge(m_code_verifier);
BOOST_LOG_TRIVIAL(info) << "code verifier: " << m_code_verifier;
BOOST_LOG_TRIVIAL(info) << "code challenge: " << code_challenge;
wxString url = GUI::format_wxstr(L"%1%/o/authorize/?client_id=%2%&response_type=code&code_challenge=%3%&code_challenge_method=S256&scope=basic_info&redirect_uri=%4%", AUTH_HOST, CLIENT_ID, code_challenge, REDIRECT_URI);
wxQueueEvent(m_evt_handler,new OpenPrusaAuthEvent(GUI::EVT_OPEN_PRUSAAUTH, std::move(url)));
}
bool UserAccountCommunication::is_logged()
{
return !m_username.empty();
}
void UserAccountCommunication::do_login()
{
{
std::lock_guard<std::mutex> lock(m_session_mutex);
if (!m_session->is_initialized()) {
login_redirect();
} else {
m_session->enqueue_test_with_refresh();
}
}
wakeup_session_thread();
}
void UserAccountCommunication::do_logout()
{
do_clear();
wxQueueEvent(m_evt_handler, new UserAccountSuccessEvent(GUI::EVT_UA_LOGGEDOUT, {}));
}
void UserAccountCommunication::do_clear()
{
{
std::lock_guard<std::mutex> lock(m_session_mutex);
m_session->clear();
}
set_username({});
}
void UserAccountCommunication::on_login_code_recieved(const std::string& url_message)
{
{
std::lock_guard<std::mutex> lock(m_session_mutex);
const std::string code = get_code_from_message(url_message);
m_session->init_with_code(code, m_code_verifier);
}
wakeup_session_thread();
}
#if 0
void UserAccountCommunication::enqueue_user_id_action()
{
{
std::lock_guard<std::mutex> lock(m_session_mutex);
if (!m_session->is_initialized()) {
return;
}
m_session->enqueue_action(UserAccountActionID::USER_ID, nullptr, nullptr, {});
}
wakeup_session_thread();
}
void UserAccountCommunication::enqueue_connect_dummy_action()
{
{
std::lock_guard<std::mutex> lock(m_session_mutex);
if (!m_session->is_initialized()) {
BOOST_LOG_TRIVIAL(error) << "Connect Dummy endpoint connection failed - Not Logged in.";
return;
}
m_session->enqueue_action(UserAccountActionID::CONNECT_DUMMY, nullptr, nullptr, {});
}
wakeup_session_thread();
}
void UserAccountCommunication::enqueue_connect_user_data_action()
{
{
std::lock_guard<std::mutex> lock(m_session_mutex);
if (!m_session->is_initialized()) {
BOOST_LOG_TRIVIAL(error) << "Connect Printers endpoint connection failed - Not Logged in.";
return;
}
m_session->enqueue_action(UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_USER_DATA, nullptr, nullptr, {});
}
wakeup_session_thread();
}
#endif 0
void UserAccountCommunication::enqueue_connect_printers_action()
{
{
std::lock_guard<std::mutex> lock(m_session_mutex);
if (!m_session->is_initialized()) {
BOOST_LOG_TRIVIAL(error) << "Connect Printers endpoint connection failed - Not Logged in.";
return;
}
m_session->enqueue_action(UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_STATUS, nullptr, nullptr, {});
}
wakeup_session_thread();
}
void UserAccountCommunication::enqueue_test_connection()
{
{
std::lock_guard<std::mutex> lock(m_session_mutex);
if (!m_session->is_initialized()) {
BOOST_LOG_TRIVIAL(error) << "Connect Printers endpoint connection failed - Not Logged in.";
return;
}
m_session->enqueue_action(UserAccountActionID::USER_ACCOUNT_ACTION_TEST_CONNECTION, nullptr, nullptr, {});
}
wakeup_session_thread();
}
void UserAccountCommunication::enqueue_avatar_action(const std::string& url)
{
{
std::lock_guard<std::mutex> lock(m_session_mutex);
if (!m_session->is_initialized()) {
BOOST_LOG_TRIVIAL(error) << "Connect Printers endpoint connection failed - Not Logged in.";
return;
}
m_session->enqueue_action(UserAccountActionID::USER_ACCOUNT_ACTION_AVATAR, nullptr, nullptr, url);
}
wakeup_session_thread();
}
void UserAccountCommunication::init_session_thread()
{
m_thread = std::thread([this]() {
for (;;) {
// Wait for 5 seconds or wakeup call
{
std::unique_lock<std::mutex> lck(m_thread_stop_mutex);
m_thread_stop_condition.wait_for(lck, std::chrono::seconds(5), [this] { return m_thread_stop || m_thread_wakeup; });
}
if (m_thread_stop)
// Stop the worker thread.
break;
m_thread_wakeup = false;
{
std::lock_guard<std::mutex> lock(m_session_mutex);
m_session->process_action_queue();
}
}
});
}
void UserAccountCommunication::wakeup_session_thread()
{
{
std::lock_guard<std::mutex> lck(m_thread_stop_mutex);
m_thread_wakeup = true;
}
m_thread_stop_condition.notify_all();
}
std::string CodeChalengeGenerator::generate_chalenge(const std::string& verifier)
{
std::string code_challenge;
try
{
code_challenge = sha256(verifier);
code_challenge = base64_encode(code_challenge);
}
catch (const std::exception& e)
{
BOOST_LOG_TRIVIAL(error) << "Code Chalenge Generator failed: " << e.what();
}
assert(!code_challenge.empty());
return code_challenge;
}
std::string CodeChalengeGenerator::generate_verifier()
{
size_t length = 40;
std::string code_verifier = generate_code_verifier(length);
assert(code_verifier.size() == length);
return code_verifier;
}
std::string CodeChalengeGenerator::base64_encode(const std::string& input)
{
std::string output;
output.resize(boost::beast::detail::base64::encoded_size(input.size()));
boost::beast::detail::base64::encode(&output[0], input.data(), input.size());
// save encode - replace + and / with - and _
std::replace(output.begin(), output.end(), '+', '-');
std::replace(output.begin(), output.end(), '/', '_');
// remove last '=' sign
while (output.back() == '=')
output.pop_back();
return output;
}
std::string CodeChalengeGenerator::generate_code_verifier(size_t length)
{
const std::string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> distribution(0, chars.size() - 1);
std::string code_verifier;
for (int i = 0; i < length; ++i) {
code_verifier += chars[distribution(gen)];
}
return code_verifier;
}
#ifdef WIN32
std::string CodeChalengeGenerator::sha256(const std::string& input)
{
HCRYPTPROV prov_handle = NULL;
HCRYPTHASH hash_handle = NULL;
DWORD hash_size = 0;
DWORD buffer_size = sizeof(DWORD);
std::string output;
if (!CryptAcquireContext(&prov_handle, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
throw std::exception("CryptAcquireContext failed.");
}
if (!CryptCreateHash(prov_handle, CALG_SHA_256, 0, 0, &hash_handle)) {
CryptReleaseContext(prov_handle, 0);
throw std::exception("CryptCreateHash failed.");
}
if (!CryptHashData(hash_handle, reinterpret_cast<const BYTE*>(input.c_str()), input.length(), 0)) {
CryptDestroyHash(hash_handle);
CryptReleaseContext(prov_handle, 0);
throw std::exception("CryptCreateHash failed.");
}
if (!CryptGetHashParam(hash_handle, HP_HASHSIZE, reinterpret_cast<BYTE*>(&hash_size), &buffer_size, 0)) {
CryptDestroyHash(hash_handle);
CryptReleaseContext(prov_handle, 0);
throw std::exception("CryptGetHashParam HP_HASHSIZE failed.");
}
output.resize(hash_size);
if (!CryptGetHashParam(hash_handle, HP_HASHVAL, reinterpret_cast<BYTE*>(&output[0]), &hash_size, 0)) {
CryptDestroyHash(hash_handle);
CryptReleaseContext(prov_handle, 0);
throw std::exception("CryptGetHashParam HP_HASHVAL failed.");
}
return output;
}
#elif __APPLE__
std::string CodeChalengeGenerator::sha256(const std::string& input) {
// Initialize the context
CC_SHA256_CTX sha256;
CC_SHA256_Init(&sha256);
// Update the context with the input data
CC_SHA256_Update(&sha256, input.c_str(), static_cast<CC_LONG>(input.length()));
// Finalize the hash and retrieve the result
unsigned char digest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256_Final(digest, &sha256);
return std::string(reinterpret_cast<char*>(digest), CC_SHA256_DIGEST_LENGTH);
}
#else
std::string CodeChalengeGenerator::sha256(const std::string& input) {
EVP_MD_CTX* mdctx;
const EVP_MD* md;
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned int digestLen;
md = EVP_sha256();
mdctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(mdctx, md, NULL);
EVP_DigestUpdate(mdctx, input.c_str(), input.length());
EVP_DigestFinal_ex(mdctx, digest, &digestLen);
EVP_MD_CTX_free(mdctx);
return std::string(reinterpret_cast<char*>(digest), digestLen);
}
#endif // __linux__
}} // Slic3r::GUI

View File

@ -0,0 +1,92 @@
#ifndef slic3r_UserAccountCommunication_hpp_
#define slic3r_UserAccountCommunication_hpp_
#include "UserAccountSession.hpp"
#include "Event.hpp"
#include "libslic3r/AppConfig.hpp"
#include <queue>
#include <map>
#include <thread>
#include <mutex>
#include <memory>
namespace Slic3r {
namespace GUI {
class CodeChalengeGenerator
{
public:
CodeChalengeGenerator() {}
~CodeChalengeGenerator() {}
std::string generate_chalenge(const std::string& verifier);
std::string generate_verifier();
private:
std::string generate_code_verifier(size_t length);
std::string base64_encode(const std::string& input);
std::string sha256(const std::string& input);
};
class UserAccountCommunication {
public:
UserAccountCommunication(wxEvtHandler* evt_handler, AppConfig* app_config);
~UserAccountCommunication();
// UI Session thread Interface
//
bool is_logged();
void do_login();
void do_logout();
void do_clear();
// Trigger function starts various remote operations
#if 0
void enqueue_user_id_action();
void enqueue_connect_dummy_action();
void enqueue_connect_user_data_action();
#endif
void enqueue_connect_printers_action();
void enqueue_avatar_action(const std::string& url);
void enqueue_test_connection();
// Callbacks - called from UI after receiving Event from Session thread. Some might use Session thread.
//
// Called when browser returns code via prusaslicer:// custom url.
// Exchanges code for tokens and shared_session_key
void on_login_code_recieved(const std::string& url_message);
void set_username(const std::string& username);
void set_remember_session(bool b);
bool get_remember_session() const {return m_remember_session; }
std::string get_username() const { return m_username; }
std::string get_access_token();
std::string get_shared_session_key();
void set_polling_enabled(bool enabled);
private:
std::unique_ptr<UserAccountSession> m_session;
std::thread m_thread;
std::mutex m_session_mutex;
std::mutex m_thread_stop_mutex;
std::condition_variable m_thread_stop_condition;
bool m_thread_stop { false };
bool m_thread_wakeup{ false };
std::string m_code_verifier;
wxEvtHandler* m_evt_handler;
AppConfig* m_app_config;
// if not empty - user is logged in
std::string m_username;
bool m_remember_session { true }; // if default is true, on every login Remember me will be checked.
void wakeup_session_thread();
void init_session_thread();
void login_redirect();
std::string client_id() const { return "UfTRUm5QjWwaQEGpWQBHGHO3reAyuzgOdBaiqO52"; }
};
}
}
#endif

View File

@ -0,0 +1,222 @@
#include "UserAccountSession.hpp"
#include "GUI_App.hpp"
#include "format.hpp"
#include "../Utils/Http.hpp"
#include "I18N.hpp"
#include <boost/log/trivial.hpp>
#include <boost/regex.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/beast/core/detail/base64.hpp>
#include <curl/curl.h>
#include <string>
namespace fs = boost::filesystem;
namespace pt = boost::property_tree;
namespace Slic3r {
namespace GUI {
wxDEFINE_EVENT(EVT_OPEN_PRUSAAUTH, OpenPrusaAuthEvent);
wxDEFINE_EVENT(EVT_UA_LOGGEDOUT, UserAccountSuccessEvent);
wxDEFINE_EVENT(EVT_UA_ID_USER_SUCCESS, UserAccountSuccessEvent);
wxDEFINE_EVENT(EVT_UA_SUCCESS, UserAccountSuccessEvent);
wxDEFINE_EVENT(EVT_UA_PRUSACONNECT_PRINTERS_SUCCESS, UserAccountSuccessEvent);
wxDEFINE_EVENT(EVT_UA_AVATAR_SUCCESS, UserAccountSuccessEvent);
wxDEFINE_EVENT(EVT_UA_FAIL, UserAccountFailEvent);
wxDEFINE_EVENT(EVT_UA_RESET, UserAccountFailEvent);
#if 0
wxDEFINE_EVENT(EVT_UA_FAIL, UserAccountFailEvent);
#endif // 0
void UserActionPost::perform(/*UNUSED*/ wxEvtHandler* evt_handler, /*UNUSED*/ const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input)
{
std::string url = m_url;
auto http = Http::post(std::move(url));
if (!input.empty())
http.set_post_body(input);
http.header("Content-type", "application/x-www-form-urlencoded");
http.on_error([fail_callback](std::string body, std::string error, unsigned status) {
if (fail_callback)
fail_callback(body);
});
http.on_complete([success_callback](std::string body, unsigned status) {
if (success_callback)
success_callback(body);
});
http.perform_sync();
}
void UserActionGetWithEvent::perform(wxEvtHandler* evt_handler, const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input)
{
std::string url = m_url + input;
auto http = Http::get(std::move(url));
if (!access_token.empty())
http.header("Authorization", "Bearer " + access_token);
http.on_error([evt_handler, fail_callback, action_name = &m_action_name, fail_evt_type = m_fail_evt_type](std::string body, std::string error, unsigned status) {
if (fail_callback)
fail_callback(body);
std::string message = GUI::format("%1% action failed (%2%): %3%", action_name, std::to_string(status), body);
if (fail_evt_type != wxEVT_NULL)
wxQueueEvent(evt_handler, new UserAccountFailEvent(fail_evt_type, std::move(message)));
});
http.on_complete([evt_handler, success_callback, succ_evt_type = m_succ_evt_type](std::string body, unsigned status) {
if (success_callback)
success_callback(body);
if (succ_evt_type != wxEVT_NULL)
wxQueueEvent(evt_handler, new UserAccountSuccessEvent(succ_evt_type, body));
});
http.perform_sync();
}
void UserAccountSession::process_action_queue()
{
if (!m_proccessing_enabled)
return;
if (m_priority_action_queue.empty() && m_action_queue.empty()) {
// update printers periodically
if (m_polling_enabled) {
enqueue_action(UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_STATUS, nullptr, nullptr, {});
} else {
return;
}
}
// priority queue works even when tokens are empty or broken
while (!m_priority_action_queue.empty()) {
m_actions[m_priority_action_queue.front().action_id]->perform(p_evt_handler, m_access_token, m_priority_action_queue.front().success_callback, m_priority_action_queue.front().fail_callback, m_priority_action_queue.front().input);
if (!m_priority_action_queue.empty())
m_priority_action_queue.pop();
}
// regular queue has to wait until priority fills tokens
if (!this->is_initialized())
return;
while (!m_action_queue.empty()) {
m_actions[m_action_queue.front().action_id]->perform(p_evt_handler, m_access_token, m_action_queue.front().success_callback, m_action_queue.front().fail_callback, m_action_queue.front().input);
if (!m_action_queue.empty())
m_action_queue.pop();
}
}
void UserAccountSession::enqueue_action(UserAccountActionID id, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input)
{
m_proccessing_enabled = true;
m_action_queue.push({ id, success_callback, fail_callback, input });
}
void UserAccountSession::init_with_code(const std::string& code, const std::string& code_verifier)
{
// Data we have
const std::string REDIRECT_URI = "prusaslicer://login";
std::string post_fields = "code=" + code +
"&client_id=" + client_id() +
"&grant_type=authorization_code" +
"&redirect_uri=" + REDIRECT_URI +
"&code_verifier="+ code_verifier;
m_proccessing_enabled = true;
// fail fn might be cancel_queue here
m_priority_action_queue.push({ UserAccountActionID::USER_ACCOUNT_ACTION_CODE_FOR_TOKEN
, std::bind(&UserAccountSession::token_success_callback, this, std::placeholders::_1)
, std::bind(&UserAccountSession::code_exchange_fail_callback, this, std::placeholders::_1)
, post_fields });
}
void UserAccountSession::token_success_callback(const std::string& body)
{
// Data we need
std::string access_token, refresh_token, shared_session_key;
try {
std::stringstream ss(body);
pt::ptree ptree;
pt::read_json(ss, ptree);
const auto access_token_optional = ptree.get_optional<std::string>("access_token");
const auto refresh_token_optional = ptree.get_optional<std::string>("refresh_token");
const auto shared_session_key_optional = ptree.get_optional<std::string>("shared_session_key");
if (access_token_optional)
access_token = *access_token_optional;
if (refresh_token_optional)
refresh_token = *refresh_token_optional;
if (shared_session_key_optional)
shared_session_key = *shared_session_key_optional;
}
catch (const std::exception&) {
std::string msg = "Could not parse server response after code exchange.";
wxQueueEvent(p_evt_handler, new UserAccountFailEvent(EVT_UA_RESET, std::move(msg)));
return;
}
if (access_token.empty() || refresh_token.empty() || shared_session_key.empty()) {
// just debug msg, no need to translate
std::string msg = GUI::format("Failed read tokens after POST.\nAccess token: %1%\nRefresh token: %2%\nShared session token: %3%\nbody: %4%", access_token, refresh_token, shared_session_key, body);
m_access_token = std::string();
m_refresh_token = std::string();
m_shared_session_key = std::string();
wxQueueEvent(p_evt_handler, new UserAccountFailEvent(EVT_UA_RESET, std::move(msg)));
return;
}
BOOST_LOG_TRIVIAL(info) << "access_token: " << access_token;
BOOST_LOG_TRIVIAL(info) << "refresh_token: " << refresh_token;
BOOST_LOG_TRIVIAL(info) << "shared_session_key: " << shared_session_key;
m_access_token = access_token;
m_refresh_token = refresh_token;
m_shared_session_key = shared_session_key;
enqueue_action(UserAccountActionID::USER_ACCOUNT_ACTION_USER_ID, nullptr, nullptr, {});
}
void UserAccountSession::code_exchange_fail_callback(const std::string& body)
{
clear();
cancel_queue();
// Unlike refresh_fail_callback, no event was triggered so far, do it. (USER_ACCOUNT_ACTION_CODE_FOR_TOKEN does not send events)
wxQueueEvent(p_evt_handler, new UserAccountFailEvent(EVT_UA_RESET, std::move(body)));
}
void UserAccountSession::enqueue_test_with_refresh()
{
// on test fail - try refresh
m_proccessing_enabled = true;
m_priority_action_queue.push({ UserAccountActionID::USER_ACCOUNT_ACTION_TEST_ACCESS_TOKEN, nullptr, std::bind(&UserAccountSession::enqueue_refresh, this, std::placeholders::_1), {} });
}
void UserAccountSession::enqueue_refresh(const std::string& body)
{
assert(!m_refresh_token.empty());
std::string post_fields = "grant_type=refresh_token"
"&client_id=" + client_id() +
"&refresh_token=" + m_refresh_token;
m_priority_action_queue.push({ UserAccountActionID::USER_ACCOUNT_ACTION_REFRESH_TOKEN
, std::bind(&UserAccountSession::token_success_callback, this, std::placeholders::_1)
, std::bind(&UserAccountSession::refresh_fail_callback, this, std::placeholders::_1)
, post_fields });
}
void UserAccountSession::refresh_fail_callback(const std::string& body)
{
clear();
cancel_queue();
// No need to notify UI thread here
// backtrace: load tokens -> TEST_TOKEN fail (access token bad) -> REFRESH_TOKEN fail (refresh token bad)
// USER_ACCOUNT_ACTION_TEST_ACCESS_TOKEN triggers EVT_UA_FAIL, we need also RESET
wxQueueEvent(p_evt_handler, new UserAccountFailEvent(EVT_UA_RESET, std::move(body)));
}
void UserAccountSession::cancel_queue()
{
while (!m_priority_action_queue.empty()) {
m_priority_action_queue.pop();
}
while (!m_action_queue.empty()) {
m_action_queue.pop();
}
}
}} // Slic3r::GUI

View File

@ -0,0 +1,196 @@
#ifndef slic3r_UserAccountSession_hpp_
#define slic3r_UserAccountSession_hpp_
#include "Event.hpp"
#include "libslic3r/AppConfig.hpp"
#include <queue>
#include <map>
#include <thread>
#include <mutex>
#include <memory>
namespace Slic3r {
namespace GUI {
using OpenPrusaAuthEvent = Event<wxString>;
using UserAccountSuccessEvent = Event<std::string>;
using UserAccountFailEvent = Event<std::string>;
wxDECLARE_EVENT(EVT_OPEN_PRUSAAUTH, OpenPrusaAuthEvent);
wxDECLARE_EVENT(EVT_UA_LOGGEDOUT, UserAccountSuccessEvent);
wxDECLARE_EVENT(EVT_UA_ID_USER_SUCCESS, UserAccountSuccessEvent);
wxDECLARE_EVENT(EVT_UA_SUCCESS, UserAccountSuccessEvent);
wxDECLARE_EVENT(EVT_UA_PRUSACONNECT_PRINTERS_SUCCESS, UserAccountSuccessEvent);
wxDECLARE_EVENT(EVT_UA_AVATAR_SUCCESS, UserAccountSuccessEvent);
wxDECLARE_EVENT(EVT_UA_FAIL, UserAccountFailEvent); // Soft fail - clears only after some number of fails
wxDECLARE_EVENT(EVT_UA_RESET, UserAccountFailEvent); // Hard fail - clears all
#if 0
wxDECLARE_EVENT(EVT_UA_CONNECT_USER_DATA_SUCCESS, UserAccountSuccessEvent);
#endif // 0
typedef std::function<void(const std::string& body)> UserActionSuccessFn;
typedef std::function<void(const std::string& body)> UserActionFailFn;
// UserActions implements different operations via trigger() method. Stored in m_actions.
enum class UserAccountActionID {
USER_ACCOUNT_ACTION_DUMMY,
USER_ACCOUNT_ACTION_REFRESH_TOKEN,
USER_ACCOUNT_ACTION_CODE_FOR_TOKEN,
USER_ACCOUNT_ACTION_USER_ID,
USER_ACCOUNT_ACTION_TEST_ACCESS_TOKEN,
USER_ACCOUNT_ACTION_TEST_CONNECTION,
USER_ACCOUNT_ACTION_CONNECT_STATUS, // status of all printers
USER_ACCOUNT_ACTION_AVATAR,
#if 0
USER_ACCOUNT_ACTION_CONNECT_PRINTERS, // all info about all printers
USER_ACCOUNT_ACTION_CONNECT_USER_DATA,
USER_ACCOUNT_ACTION_CONNECT_DUMMY,
#endif // 0
};
class UserAction
{
public:
UserAction(const std::string name, const std::string url) : m_action_name(name), m_url(url){}
~UserAction() {}
virtual void perform(wxEvtHandler* evt_handler, const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input) = 0;
protected:
std::string m_action_name;
std::string m_url;
};
class UserActionGetWithEvent : public UserAction
{
public:
UserActionGetWithEvent(const std::string name, const std::string url, wxEventType succ_event_type, wxEventType fail_event_type)
: m_succ_evt_type(succ_event_type)
, m_fail_evt_type(fail_event_type)
, UserAction(name, url)
{}
~UserActionGetWithEvent() {}
void perform(wxEvtHandler* evt_handler, const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input) override;
private:
wxEventType m_succ_evt_type;
wxEventType m_fail_evt_type;
};
class UserActionPost : public UserAction
{
public:
UserActionPost(const std::string name, const std::string url) : UserAction(name, url) {}
~UserActionPost() {}
void perform(wxEvtHandler* evt_handler, const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input) override;
};
class DummyUserAction : public UserAction
{
public:
DummyUserAction() : UserAction("Dummy", {}) {}
~DummyUserAction() {}
void perform(wxEvtHandler* evt_handler, const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input) override { }
};
struct ActionQueueData
{
UserAccountActionID action_id;
UserActionSuccessFn success_callback;
UserActionFailFn fail_callback;
std::string input;
};
class UserAccountSession
{
public:
UserAccountSession(wxEvtHandler* evt_handler, const std::string& access_token, const std::string& refresh_token, const std::string& shared_session_key, bool polling_enabled)
: p_evt_handler(evt_handler)
, m_access_token(access_token)
, m_refresh_token(refresh_token)
, m_shared_session_key(shared_session_key)
, m_polling_enabled(polling_enabled)
{
// do not forget to add delete to destructor
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_DUMMY] = std::make_unique<DummyUserAction>();
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_REFRESH_TOKEN] = std::make_unique<UserActionPost>("EXCHANGE_TOKENS", "https://test-account.prusa3d.com/o/token/");
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CODE_FOR_TOKEN] = std::make_unique<UserActionPost>("EXCHANGE_TOKENS", "https://test-account.prusa3d.com/o/token/");
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_USER_ID] = std::make_unique<UserActionGetWithEvent>("USER_ID", "https://test-account.prusa3d.com/api/v1/me/", EVT_UA_ID_USER_SUCCESS, EVT_UA_RESET);
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_TEST_ACCESS_TOKEN] = std::make_unique<UserActionGetWithEvent>("TEST_ACCESS_TOKEN", "https://test-account.prusa3d.com/api/v1/me/", EVT_UA_ID_USER_SUCCESS, EVT_UA_FAIL);
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_TEST_CONNECTION] = std::make_unique<UserActionGetWithEvent>("TEST_CONNECTION", "https://test-account.prusa3d.com/api/v1/me/", wxEVT_NULL, EVT_UA_RESET);
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_STATUS] = std::make_unique<UserActionGetWithEvent>("CONNECT_STATUS", "https://dev.connect.prusa3d.com/slicer/status", EVT_UA_PRUSACONNECT_PRINTERS_SUCCESS, EVT_UA_FAIL);
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_AVATAR] = std::make_unique<UserActionGetWithEvent>("AVATAR", "https://test-media.printables.com/media/", EVT_UA_AVATAR_SUCCESS, EVT_UA_FAIL);
#if 0
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_USER_DATA] = std::make_unique<UserActionGetWithEvent>("CONNECT_USER_DATA", "https://dev.connect.prusa3d.com/app/login", EVT_UA_CONNECT_USER_DATA_SUCCESS, EVT_UA_FAIL);
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_DUMMY] = std::make_unique<UserActionGetWithEvent>("CONNECT_DUMMY", "https://dev.connect.prusa3d.com/slicer/dummy", EVT_UA_SUCCESS, EVT_UA_FAIL);
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_PRINTERS] = std::make_unique<UserActionGetWithEvent>("CONNECT_PRINTERS", "https://dev.connect.prusa3d.com/slicer/printers", EVT_UA_PRUSACONNECT_PRINTERS_SUCCESS, EVT_UA_FAIL);
#endif // 0
}
~UserAccountSession()
{
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_DUMMY].reset(nullptr);
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_REFRESH_TOKEN].reset(nullptr);
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CODE_FOR_TOKEN].reset(nullptr);
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_USER_ID].reset(nullptr);
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_TEST_ACCESS_TOKEN].reset(nullptr);
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_TEST_CONNECTION].reset(nullptr);
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_STATUS].reset(nullptr);
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_AVATAR].reset(nullptr);
#if 0
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_USER_DATA].reset(nullptr);
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_DUMMY].reset(nullptr);
m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_PRINTERS].reset(nullptr);
#endif // 0
}
void clear() {
m_access_token.clear();
m_refresh_token.clear();
m_shared_session_key.clear();
m_proccessing_enabled = false;
}
// Functions that automatically enable action queu processing
void init_with_code(const std::string& code, const std::string& code_verifier);
void enqueue_action(UserAccountActionID id, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input);
// Special enques, that sets callbacks.
void enqueue_test_with_refresh();
void process_action_queue();
bool is_initialized() { return !m_access_token.empty() || !m_refresh_token.empty(); }
std::string get_access_token() const { return m_access_token; }
std::string get_refresh_token() const { return m_refresh_token; }
std::string get_shared_session_key() const { return m_shared_session_key; }
void set_polling_enabled(bool enabled) {m_polling_enabled = enabled; }
private:
void enqueue_refresh(const std::string& body);
void refresh_fail_callback(const std::string& body);
void cancel_queue();
void code_exchange_fail_callback(const std::string& body);
void token_success_callback(const std::string& body);
std::string client_id() const { return "UfTRUm5QjWwaQEGpWQBHGHO3reAyuzgOdBaiqO52"; }
// false prevents action queu to be processed - no communication is done
// sets to true by init_with_code or enqueue_action call
bool m_proccessing_enabled {false};
// triggers CONNECT_PRINTERS action when woken up on idle
bool m_polling_enabled;
std::string m_access_token;
std::string m_refresh_token;
std::string m_shared_session_key;
std::queue<ActionQueueData> m_action_queue;
std::queue<ActionQueueData> m_priority_action_queue;
std::map<UserAccountActionID, std::unique_ptr<UserAction>> m_actions;
wxEvtHandler* p_evt_handler;
};
}
}
#endif

176
src/slic3r/GUI/WebView.cpp Normal file
View File

@ -0,0 +1,176 @@
#include "WebView.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/Utils/MacDarkMode.hpp"
#include <wx/webviewarchivehandler.h>
#include <wx/webviewfshandler.h>
#include <wx/msw/webview_edge.h>
#include <wx/uri.h>
#include "wx/private/jsscriptwrapper.h"
#include <boost/log/trivial.hpp>
#ifdef __WIN32__
#include <WebView2.h>
#elif defined __linux__
#include <gtk/gtk.h>
#define WEBKIT_API
struct WebKitWebView;
struct WebKitJavascriptResult;
extern "C" {
WEBKIT_API void
webkit_web_view_run_javascript (WebKitWebView *web_view,
const gchar *script,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
WEBKIT_API WebKitJavascriptResult *
webkit_web_view_run_javascript_finish (WebKitWebView *web_view,
GAsyncResult *result,
GError **error);
WEBKIT_API void
webkit_javascript_result_unref (WebKitJavascriptResult *js_result);
}
#endif
class FakeWebView : public wxWebView
{
virtual bool Create(wxWindow* parent, wxWindowID id, const wxString& url, const wxPoint& pos, const wxSize& size, long style, const wxString& name) override { return false; }
virtual wxString GetCurrentTitle() const override { return wxString(); }
virtual wxString GetCurrentURL() const override { return wxString(); }
virtual bool IsBusy() const override { return false; }
virtual bool IsEditable() const override { return false; }
virtual void LoadURL(const wxString& url) override { }
virtual void Print() override { }
virtual void RegisterHandler(wxSharedPtr<wxWebViewHandler> handler) override { }
virtual void Reload(wxWebViewReloadFlags flags = wxWEBVIEW_RELOAD_DEFAULT) override { }
virtual bool RunScript(const wxString& javascript, wxString* output = NULL) const override { return false; }
virtual void SetEditable(bool enable = true) override { }
virtual void Stop() override { }
virtual bool CanGoBack() const override { return false; }
virtual bool CanGoForward() const override { return false; }
virtual void GoBack() override { }
virtual void GoForward() override { }
virtual void ClearHistory() override { }
virtual void EnableHistory(bool enable = true) override { }
virtual wxVector<wxSharedPtr<wxWebViewHistoryItem>> GetBackwardHistory() override { return {}; }
virtual wxVector<wxSharedPtr<wxWebViewHistoryItem>> GetForwardHistory() override { return {}; }
virtual void LoadHistoryItem(wxSharedPtr<wxWebViewHistoryItem> item) override { }
virtual bool CanSetZoomType(wxWebViewZoomType type) const override { return false; }
virtual float GetZoomFactor() const override { return 0.0f; }
virtual wxWebViewZoomType GetZoomType() const override { return wxWebViewZoomType(); }
virtual void SetZoomFactor(float zoom) override { }
virtual void SetZoomType(wxWebViewZoomType zoomType) override { }
virtual bool CanUndo() const override { return false; }
virtual bool CanRedo() const override { return false; }
virtual void Undo() override { }
virtual void Redo() override { }
virtual void* GetNativeBackend() const override { return nullptr; }
virtual void DoSetPage(const wxString& html, const wxString& baseUrl) override { }
};
wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url)
{
#if wxUSE_WEBVIEW_EDGE
// WebView2Loader.dll in exe folder is enough?
/*
// Check if a fixed version of edge is present in
// $executable_path/edge_fixed and use it
wxFileName edgeFixedDir(wxStandardPaths::Get().GetExecutablePath());
edgeFixedDir.SetFullName("");
edgeFixedDir.AppendDir("edge_fixed");
if (edgeFixedDir.DirExists()) {
wxWebViewEdge::MSWSetBrowserExecutableDir(edgeFixedDir.GetFullPath());
wxLogMessage("Using fixed edge version");
}
*/
#endif
wxString correct_url = url;
#ifdef __WIN32__
correct_url.Replace("\\", "/");
#endif
if (!correct_url.empty())
correct_url = wxURI(correct_url).BuildURI();
auto webView = wxWebView::New();
if (webView) {
#ifdef __WIN32__
webView->SetUserAgent(wxString::Format("PrusaSlicer/v%s", SLIC3R_VERSION));
webView->Create(parent, wxID_ANY, correct_url, wxDefaultPosition, wxDefaultSize);
//We register the wxfs:// protocol for testing purposes
//webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewArchiveHandler("wxfs")));
//And the memory: file system
//webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewFSHandler("memory")));
#else
// With WKWebView handlers need to be registered before creation
//webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewArchiveHandler("wxfs")));
// And the memory: file system
//webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewFSHandler("memory")));
webView->Create(parent, wxID_ANY, correct_url, wxDefaultPosition, wxDefaultSize);
webView->SetUserAgent(wxString::Format("PrusaSlicer/v%s", SLIC3R_VERSION));
#endif
#ifndef __WIN32__
Slic3r::GUI::wxGetApp().CallAfter([webView] {
#endif
if (!webView->AddScriptMessageHandler("_prusaSlicer")) {
// TODO: dialog to user
//wxLogError("Could not add script message handler");
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Could not add script message handler";
}
#ifndef __WIN32__
});
#endif
webView->EnableContextMenu(false);
} else {
// TODO: dialog to user
BOOST_LOG_TRIVIAL(error) << "Failed to create wxWebView object. Using Dummy object instead. Webview won't be working.";
webView = new FakeWebView;
}
return webView;
}
void WebView::LoadUrl(wxWebView * webView, wxString const &url)
{
auto url2 = url;
#ifdef __WIN32__
url2.Replace("\\", "/");
#endif
if (!url2.empty()) { url2 = wxURI(url2).BuildURI(); }
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << url2.ToUTF8();
webView->LoadURL(url2);
}
bool WebView::run_script(wxWebView *webView, wxString const &javascript)
{
try {
#ifdef __WIN32__
ICoreWebView2 * webView2 = (ICoreWebView2 *) webView->GetNativeBackend();
if (webView2 == nullptr)
return false;
int count = 0;
wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::OutputType::JS_OUTPUT_STRING);
wxString wrapped_code = wrapJS.GetWrappedCode();
return webView2->ExecuteScript(wrapJS.GetWrappedCode(), NULL) == 0;
#elif defined __WXMAC__
WKWebView * wkWebView = (WKWebView *) webView->GetNativeBackend();
wxJSScriptWrapper wrapJS(javascript, wxJSScriptWrapper::OutputType::JS_OUTPUT_STRING);
Slic3r::GUI::WKWebView_evaluateJavaScript(wkWebView, wrapJS.GetWrappedCode(), nullptr);
return true;
#else
WebKitWebView *wkWebView = (WebKitWebView *) webView->GetNativeBackend();
webkit_web_view_run_javascript(
wkWebView, javascript.utf8_str(), NULL,
[](GObject *wkWebView, GAsyncResult *res, void *) {
GError * error = NULL;
auto result = webkit_web_view_run_javascript_finish((WebKitWebView*)wkWebView, res, &error);
if (!result)
g_error_free (error);
else
webkit_javascript_result_unref (result);
}, NULL);
return true;
#endif
} catch (std::exception &e) {
return false;
}
}

View File

@ -0,0 +1,16 @@
#ifndef slic3r_GUI_WebView_hpp_
#define slic3r_GUI_WebView_hpp_
#include <wx/webview.h>
class WebView
{
public:
static wxWebView *CreateWebView(wxWindow *parent, wxString const &url);
static void LoadUrl(wxWebView * webView, wxString const &url);
static bool run_script(wxWebView * webView, wxString const & msg);
};
#endif // !slic3r_GUI_WebView_hpp_

View File

@ -0,0 +1,711 @@
#include "WebViewDialog.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "libslic3r_version.h"
#include "libslic3r/Utils.hpp"
#include "libslic3r/libslic3r.h"
#include "slic3r/GUI/UserAccount.hpp"
#include "slic3r/GUI/format.hpp"
#include <wx/sizer.h>
#include <wx/toolbar.h>
#include <wx/textdlg.h>
#include <boost/log/trivial.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include "slic3r/GUI/WebView.hpp"
namespace pt = boost::property_tree;
namespace Slic3r {
namespace GUI {
WebViewPanel::WebViewPanel(wxWindow *parent, const wxString& default_url)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
, m_default_url (default_url)
{
//wxString url = "https://dev.connect.prusa3d.com/prusa-slicer/printers";
// https://dev.connect.prusa3d.com/connect-slicer-app/printer-list
//std::string strlang = wxGetApp().app_config->get("language");
//if (strlang != "")
// url = wxString::Format("file://%s/web/homepage/index.html?lang=%s", from_u8(resources_dir()), strlang);
//m_bbl_user_agent = wxString::Format("BBL-Slicer/v%s", SLIC3R_VERSION);
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
#ifdef DEBUG_URL_PANEL
// Create the button
bSizer_toolbar = new wxBoxSizer(wxHORIZONTAL);
m_button_back = new wxButton(this, wxID_ANY, wxT("Back"), wxDefaultPosition, wxDefaultSize, 0);
m_button_back->Enable(false);
bSizer_toolbar->Add(m_button_back, 0, wxALL, 5);
m_button_forward = new wxButton(this, wxID_ANY, wxT("Forward"), wxDefaultPosition, wxDefaultSize, 0);
m_button_forward->Enable(false);
bSizer_toolbar->Add(m_button_forward, 0, wxALL, 5);
m_button_stop = new wxButton(this, wxID_ANY, wxT("Stop"), wxDefaultPosition, wxDefaultSize, 0);
bSizer_toolbar->Add(m_button_stop, 0, wxALL, 5);
m_button_reload = new wxButton(this, wxID_ANY, wxT("Reload"), wxDefaultPosition, wxDefaultSize, 0);
bSizer_toolbar->Add(m_button_reload, 0, wxALL, 5);
m_url = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
bSizer_toolbar->Add(m_url, 1, wxALL | wxEXPAND, 5);
m_button_tools = new wxButton(this, wxID_ANY, wxT("Tools"), wxDefaultPosition, wxDefaultSize, 0);
bSizer_toolbar->Add(m_button_tools, 0, wxALL, 5);
// Create panel for find toolbar.
wxPanel* panel = new wxPanel(this);
topsizer->Add(bSizer_toolbar, 0, wxEXPAND, 0);
topsizer->Add(panel, wxSizerFlags().Expand());
// Create sizer for panel.
wxBoxSizer* panel_sizer = new wxBoxSizer(wxVERTICAL);
panel->SetSizer(panel_sizer);
// Create the info panel
m_info = new wxInfoBar(this);
topsizer->Add(m_info, wxSizerFlags().Expand());
#endif
// Create the webview
m_browser = WebView::CreateWebView(this, /*m_default_url*/ wxString::Format("file://%s/web/connection_failed.html", from_u8(resources_dir())));
if (m_browser == nullptr) {
wxLogError("Could not init m_browser");
return;
}
SetSizer(topsizer);
topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
#ifdef DEBUG_URL_PANEL
// Create the Tools menu
m_tools_menu = new wxMenu();
wxMenuItem* viewSource = m_tools_menu->Append(wxID_ANY, _L("View Source"));
wxMenuItem* viewText = m_tools_menu->Append(wxID_ANY, _L("View Text"));
m_tools_menu->AppendSeparator();
wxMenu* script_menu = new wxMenu;
m_script_custom = script_menu->Append(wxID_ANY, "Custom script");
m_tools_menu->AppendSubMenu(script_menu, _L("Run Script"));
wxMenuItem* addUserScript = m_tools_menu->Append(wxID_ANY, _L("Add user script"));
wxMenuItem* setCustomUserAgent = m_tools_menu->Append(wxID_ANY, _L("Set custom user agent"));
m_context_menu = m_tools_menu->AppendCheckItem(wxID_ANY, _L("Enable Context Menu"));
m_dev_tools = m_tools_menu->AppendCheckItem(wxID_ANY, _L("Enable Dev Tools"));
#endif
//Zoom
m_zoomFactor = 100;
Bind(wxEVT_SHOW, &WebViewPanel::on_show, this);
// Connect the webview events
Bind(wxEVT_WEBVIEW_ERROR, &WebViewPanel::on_error, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WebViewPanel::on_script_message, this, m_browser->GetId());
#ifdef DEBUG_URL_PANEL
// Connect the button events
Bind(wxEVT_BUTTON, &WebViewPanel::on_back_button, this, m_button_back->GetId());
Bind(wxEVT_BUTTON, &WebViewPanel::on_forward_button, this, m_button_forward->GetId());
Bind(wxEVT_BUTTON, &WebViewPanel::on_stop_button, this, m_button_stop->GetId());
Bind(wxEVT_BUTTON, &WebViewPanel::on_reload_button, this, m_button_reload->GetId());
Bind(wxEVT_BUTTON, &WebViewPanel::on_tools_clicked, this, m_button_tools->GetId());
Bind(wxEVT_TEXT_ENTER, &WebViewPanel::on_url, this, m_url->GetId());
// Connect the menu events
Bind(wxEVT_MENU, &WebViewPanel::on_view_source_request, this, viewSource->GetId());
Bind(wxEVT_MENU, &WebViewPanel::on_view_text_request, this, viewText->GetId());
Bind(wxEVT_MENU, &WebViewPanel::On_enable_context_menu, this, m_context_menu->GetId());
Bind(wxEVT_MENU, &WebViewPanel::On_enable_dev_tools, this, m_dev_tools->GetId());
Bind(wxEVT_MENU, &WebViewPanel::on_run_script_custom, this, m_script_custom->GetId());
Bind(wxEVT_MENU, &WebViewPanel::on_add_user_script, this, addUserScript->GetId());
#endif
//Connect the idle events
Bind(wxEVT_IDLE, &WebViewPanel::on_idle, this);
Bind(wxEVT_CLOSE_WINDOW, &WebViewPanel::on_close, this);
m_LoginUpdateTimer = nullptr;
}
WebViewPanel::~WebViewPanel()
{
SetEvtHandlerEnabled(false);
#ifdef DEBUG_URL_PANEL
delete m_tools_menu;
if (m_LoginUpdateTimer != nullptr) {
m_LoginUpdateTimer->Stop();
delete m_LoginUpdateTimer;
m_LoginUpdateTimer = NULL;
}
#endif
}
void WebViewPanel::load_url(const wxString& url)
{
this->Show();
this->Raise();
#ifdef DEBUG_URL_PANEL
m_url->SetLabelText(url);
#endif
m_browser->LoadURL(url);
m_browser->SetFocus();
}
void WebViewPanel::load_default_url_delayed()
{
assert(!m_default_url.empty());
m_load_default_url = true;
}
void WebViewPanel::load_error_page()
{
load_url(wxString::Format("file://%s/web/connection_failed.html", from_u8(resources_dir())));
}
void WebViewPanel::on_show(wxShowEvent& evt)
{
if (evt.IsShown() && m_load_default_url)
{
m_load_default_url = false;
load_url(m_default_url);
}
// TODO: add check that any url was loaded
}
void WebViewPanel::on_idle(wxIdleEvent& WXUNUSED(evt))
{
if (m_browser->IsBusy())
wxSetCursor(wxCURSOR_ARROWWAIT);
else
wxSetCursor(wxNullCursor);
#ifdef DEBUG_URL_PANEL
m_button_stop->Enable(m_browser->IsBusy());
#endif
}
/**
* Callback invoked when user entered an URL and pressed enter
*/
void WebViewPanel::on_url(wxCommandEvent& WXUNUSED(evt))
{
#ifdef DEBUG_URL_PANEL
m_browser->LoadURL(m_url->GetValue());
m_browser->SetFocus();
#endif
}
/**
* Callback invoked when user pressed the "back" button
*/
void WebViewPanel::on_back_button(wxCommandEvent& WXUNUSED(evt))
{
m_browser->GoBack();
}
/**
* Callback invoked when user pressed the "forward" button
*/
void WebViewPanel::on_forward_button(wxCommandEvent& WXUNUSED(evt))
{
m_browser->GoForward();
}
/**
* Callback invoked when user pressed the "stop" button
*/
void WebViewPanel::on_stop_button(wxCommandEvent& WXUNUSED(evt))
{
m_browser->Stop();
}
/**
* Callback invoked when user pressed the "reload" button
*/
void WebViewPanel::on_reload_button(wxCommandEvent& WXUNUSED(evt))
{
m_browser->Reload();
}
void WebViewPanel::on_close(wxCloseEvent& evt)
{
this->Hide();
}
void WebViewPanel::on_script_message(wxWebViewEvent& evt)
{
}
/**
* Invoked when user selects the "View Source" menu item
*/
void WebViewPanel::on_view_source_request(wxCommandEvent& WXUNUSED(evt))
{
SourceViewDialog dlg(this, m_browser->GetPageSource());
dlg.ShowModal();
}
/**
* Invoked when user selects the "View Text" menu item
*/
void WebViewPanel::on_view_text_request(wxCommandEvent& WXUNUSED(evt))
{
wxDialog textViewDialog(this, wxID_ANY, "Page Text",
wxDefaultPosition, wxSize(700, 500),
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, m_browser->GetPageText(),
wxDefaultPosition, wxDefaultSize,
wxTE_MULTILINE |
wxTE_RICH |
wxTE_READONLY);
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(text, 1, wxEXPAND);
SetSizer(sizer);
textViewDialog.ShowModal();
}
/**
* Invoked when user selects the "Menu" item
*/
void WebViewPanel::on_tools_clicked(wxCommandEvent& WXUNUSED(evt))
{
#ifdef DEBUG_URL_PANEL
m_context_menu->Check(m_browser->IsContextMenuEnabled());
m_dev_tools->Check(m_browser->IsAccessToDevToolsEnabled());
wxPoint position = ScreenToClient(wxGetMousePosition());
PopupMenu(m_tools_menu, position.x, position.y);
#endif
}
void WebViewPanel::run_script(const wxString& javascript)
{
// Remember the script we run in any case, so the next time the user opens
// the "Run Script" dialog box, it is shown there for convenient updating.
m_javascript = javascript;
if (!m_browser) return;
bool res = WebView::run_script(m_browser, javascript);
BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << " " << res;
}
void WebViewPanel::on_run_script_custom(wxCommandEvent& WXUNUSED(evt))
{
wxTextEntryDialog dialog
(
this,
"Please enter JavaScript code to execute",
wxGetTextFromUserPromptStr,
m_javascript,
wxOK | wxCANCEL | wxCENTRE | wxTE_MULTILINE
);
if (dialog.ShowModal() != wxID_OK)
return;
run_script(dialog.GetValue());
}
void WebViewPanel::on_add_user_script(wxCommandEvent& WXUNUSED(evt))
{
wxString userScript = "window.wx_test_var = 'wxWidgets webview sample';";
wxTextEntryDialog dialog
(
this,
"Enter the JavaScript code to run as the initialization script that runs before any script in the HTML document.",
wxGetTextFromUserPromptStr,
userScript,
wxOK | wxCANCEL | wxCENTRE | wxTE_MULTILINE
);
if (dialog.ShowModal() != wxID_OK)
return;
if (!m_browser->AddUserScript(dialog.GetValue()))
wxLogError("Could not add user script");
}
void WebViewPanel::on_set_custom_user_agent(wxCommandEvent& WXUNUSED(evt))
{
wxString customUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_1_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.1 Mobile/15E148 Safari/604.1";
wxTextEntryDialog dialog
(
this,
"Enter the custom user agent string you would like to use.",
wxGetTextFromUserPromptStr,
customUserAgent,
wxOK | wxCANCEL | wxCENTRE
);
if (dialog.ShowModal() != wxID_OK)
return;
if (!m_browser->SetUserAgent(customUserAgent))
wxLogError("Could not set custom user agent");
}
void WebViewPanel::on_clear_selection(wxCommandEvent& WXUNUSED(evt))
{
m_browser->ClearSelection();
}
void WebViewPanel::on_delete_selection(wxCommandEvent& WXUNUSED(evt))
{
m_browser->DeleteSelection();
}
void WebViewPanel::on_select_all(wxCommandEvent& WXUNUSED(evt))
{
m_browser->SelectAll();
}
void WebViewPanel::On_enable_context_menu(wxCommandEvent& evt)
{
m_browser->EnableContextMenu(evt.IsChecked());
}
void WebViewPanel::On_enable_dev_tools(wxCommandEvent& evt)
{
m_browser->EnableAccessToDevTools(evt.IsChecked());
}
/**
* Callback invoked when a loading error occurs
*/
void WebViewPanel::on_error(wxWebViewEvent& evt)
{
#define WX_ERROR_CASE(type) \
case type: \
category = #type; \
break;
wxString category;
switch (evt.GetInt())
{
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_CONNECTION);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_CERTIFICATE);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_AUTH);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_SECURITY);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_NOT_FOUND);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_REQUEST);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_USER_CANCELLED);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_OTHER);
}
BOOST_LOG_TRIVIAL(error) << "WebView error: " << category;
load_error_page();
#ifdef DEBUG_URL_PANEL
m_info->ShowMessage(_L("An error occurred loading ") + evt.GetURL() + "\n" +
"'" + category + "'", wxICON_ERROR);
#endif
}
void WebViewPanel::sys_color_changed()
{
#ifdef _WIN32
wxGetApp().UpdateDarkUI(this);
#endif
}
SourceViewDialog::SourceViewDialog(wxWindow* parent, wxString source) :
wxDialog(parent, wxID_ANY, "Source Code",
wxDefaultPosition, wxSize(700,500),
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, source,
wxDefaultPosition, wxDefaultSize,
wxTE_MULTILINE |
wxTE_RICH |
wxTE_READONLY);
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(text, 1, wxEXPAND);
SetSizer(sizer);
}
ConnectRequestHandler::ConnectRequestHandler()
{
m_actions["REQUEST_ACCESS_TOKEN"] = std::bind(&ConnectRequestHandler::on_request_access_token, this);
m_actions["REQUEST_CONFIG"] = std::bind(&ConnectRequestHandler::on_request_config, this);
m_actions["UPDATE_SELECTED_PRINTER"] = std::bind(&ConnectRequestHandler::on_request_update_selected_printer_action, this);
m_actions["WEBAPP_READY"] = std::bind(&ConnectRequestHandler::request_compatible_printers, this);
}
ConnectRequestHandler::~ConnectRequestHandler()
{
}
void ConnectRequestHandler::handle_message(const std::string& message)
{
// read msg and choose action
/*
v0:
{"type":"request","detail":{"action":"requestAccessToken"}}
v1:
{"action":"REQUEST_ACCESS_TOKEN"}
*/
std::string action_string;
m_message_data = message;
try {
std::stringstream ss(m_message_data);
pt::ptree ptree;
pt::read_json(ss, ptree);
// v1:
if (const auto action = ptree.get_optional<std::string>("action"); action) {
action_string = *action;
}
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Could not parse _prusaConnect message. " << e.what();
return;
}
if (action_string.empty()) {
BOOST_LOG_TRIVIAL(error) << "Recieved invalid message from _prusaConnect (missing action). Message: " << message;
return;
}
assert(m_actions.find(action_string) != m_actions.end()); // this assert means there is a action that has no handling.
if (m_actions.find(action_string) != m_actions.end()) {
m_actions[action_string]();
}
}
void ConnectRequestHandler::resend_config()
{
on_request_config();
}
void ConnectRequestHandler::on_request_access_token()
{
std::string token = wxGetApp().plater()->get_user_account()->get_access_token();
wxString script = GUI::format_wxstr("window._prusaConnect_v1.setAccessToken(\'%1%\')", token);
run_script_bridge(script);
}
void ConnectRequestHandler::on_request_config()
{
/*
accessToken?: string;
clientVersion?: string;
colorMode?: "LIGHT" | "DARK";
language?: ConnectLanguage;
sessionId?: string;
*/
const std::string token = wxGetApp().plater()->get_user_account()->get_access_token();
//const std::string sesh = wxGetApp().plater()->get_user_account()->get_shared_session_key();
const std::string dark_mode = wxGetApp().dark_mode() ? "DARK" : "LIGHT";
const wxString language = GUI::wxGetApp().current_language_code();
const std::string init_options = GUI::format("{\"accessToken\": \"%1%\" , \"clientVersion\": \"%2%\", \"colorMode\": \"%3%\", \"language\": \"%4%\"}", token, SLIC3R_VERSION, dark_mode, language);
wxString script = GUI::format_wxstr("window._prusaConnect_v1.init(%1%)", init_options);
run_script_bridge(script);
}
ConnectWebViewPanel::ConnectWebViewPanel(wxWindow* parent)
: WebViewPanel(parent, L"https://dev.connect.prusa3d.com/connect-slicer-app/")
{
}
void ConnectWebViewPanel::on_script_message(wxWebViewEvent& evt)
{
BOOST_LOG_TRIVIAL(error) << "recieved message from PrusaConnect FE: " << evt.GetString();
handle_message(into_u8(evt.GetString()));
}
void ConnectWebViewPanel::logout()
{
wxString script = L"window._prusaConnect_v1.logout()";
run_script(script);
}
void ConnectWebViewPanel::sys_color_changed()
{
resend_config();
}
void ConnectWebViewPanel::on_request_update_selected_printer_action()
{
assert(!m_message_data.empty());
wxGetApp().handle_connect_request_printer_pick(m_message_data);
}
PrinterWebViewPanel::PrinterWebViewPanel(wxWindow* parent, const wxString& default_url)
: WebViewPanel(parent, default_url)
{
m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrinterWebViewPanel::on_loaded, this);
}
void PrinterWebViewPanel::on_loaded(wxWebViewEvent& evt)
{
if (evt.GetURL().IsEmpty())
return;
if (!m_api_key.empty()) {
send_api_key();
} else if (!m_usr.empty() && !m_psk.empty()) {
send_credentials();
}
}
void PrinterWebViewPanel::send_api_key()
{
if (m_api_key_sent)
return;
m_api_key_sent = true;
wxString key = from_u8(m_api_key);
wxString script = wxString::Format(R"(
// Check if window.fetch exists before overriding
if (window.fetch) {
const originalFetch = window.fetch;
window.fetch = function(input, init = {}) {
init.headers = init.headers || {};
init.headers['X-API-Key'] = '%s';
return originalFetch(input, init);
};
}
)",
key);
m_browser->RemoveAllUserScripts();
m_browser->AddUserScript(script);
m_browser->Reload();
}
void PrinterWebViewPanel::send_credentials()
{
if (m_api_key_sent)
return;
m_api_key_sent = true;
wxString usr = from_u8(m_usr);
wxString psk = from_u8(m_psk);
wxString script = wxString::Format(R"(
// Check if window.fetch exists before overriding
if (window.fetch) {
const originalFetch = window.fetch;
window.fetch = function(input, init = {}) {
init.headers = init.headers || {};
init.headers['X-API-Key'] = 'Basic ' + btoa(`%s:%s`);
return originalFetch(input, init);
};
}
)", usr, psk);
m_browser->RemoveAllUserScripts();
m_browser->AddUserScript(script);
m_browser->Reload();
}
void PrinterWebViewPanel::sys_color_changed()
{
}
WebViewDialog::WebViewDialog(wxWindow* parent, const wxString& url, const wxString& dialog_name, const wxSize& size)
: wxDialog(parent, wxID_ANY, dialog_name, wxDefaultPosition, size, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
// Create the webview
m_browser = WebView::CreateWebView(this, url);
if (m_browser == nullptr) {
wxLogError("Could not init m_browser");
return;
}
SetSizer(topsizer);
topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
Bind(wxEVT_SHOW, &WebViewDialog::on_show, this);
Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WebViewDialog::on_script_message, this, m_browser->GetId());
}
WebViewDialog::~WebViewDialog()
{
}
void WebViewDialog::run_script(const wxString& javascript)
{
if (!m_browser)
return;
bool res = WebView::run_script(m_browser, javascript);
}
PrinterPickWebViewDialog::PrinterPickWebViewDialog(wxWindow* parent, std::string& ret_val)
: WebViewDialog(parent, L"https://dev.connect.prusa3d.com/connect-slicer-app/printer-list", _L("Choose a printer"), wxSize(std::max(parent->GetClientSize().x / 2, 100 * wxGetApp().em_unit()), std::max(parent->GetClientSize().y / 2, 50 * wxGetApp().em_unit())))
, m_ret_val(ret_val)
{
Centre();
}
void PrinterPickWebViewDialog::on_show(wxShowEvent& evt)
{
/*
if (evt.IsShown()) {
std::string token = wxGetApp().plater()->get_user_account()->get_access_token();
wxString script = GUI::format_wxstr("window.setAccessToken(\'%1%\')", token);
// TODO: should this be happening every OnShow?
run_script(script);
}
*/
}
void PrinterPickWebViewDialog::on_script_message(wxWebViewEvent& evt)
{
handle_message(into_u8(evt.GetString()));
}
void PrinterPickWebViewDialog::on_request_update_selected_printer_action()
{
m_ret_val = m_message_data;
this->EndModal(wxID_OK);
}
void PrinterPickWebViewDialog::request_compatible_printers()
{
//PrinterParams: {
//material: Material;
//nozzleDiameter: number;
//printerType: string;
//}
const Preset& selected_printer = wxGetApp().preset_bundle->printers.get_selected_preset();
const Preset& selected_filament = wxGetApp().preset_bundle->filaments.get_selected_preset();
const std::string nozzle_diameter_serialized = dynamic_cast<const ConfigOptionFloats*>(selected_printer.config.option("nozzle_diameter"))->serialize();
const std::string filament_type_serialized = selected_filament.config.option("filament_type")->serialize();
const std::string printer_model_serialized = selected_printer.config.option("printer_model")->serialize();
const std::string printer_type = wxGetApp().plater()->get_user_account()->get_printer_type_from_name(printer_model_serialized);
// assert(!filament_type_serialized.empty() && !nozzle_diameter_serialized.empty() && !printer_type.empty());
const std::string request = GUI::format(
"{"
"\"material\": \"%1%\", "
"\"nozzleDiameter\": %2%, "
"\"printerType\": \"%3%\" "
"}", filament_type_serialized, nozzle_diameter_serialized, printer_type);
wxString script = GUI::format_wxstr("window._prusaConnect_v1.requestCompatiblePrinter(%1%)", request);
run_script(script);
}
} // GUI
} // Slic3r

View File

@ -0,0 +1,202 @@
#ifndef slic3r_WebViewDialog_hpp_
#define slic3r_WebViewDialog_hpp_
#include "wx/artprov.h"
#include "wx/cmdline.h"
#include "wx/notifmsg.h"
#include "wx/settings.h"
#include "wx/webview.h"
#if wxUSE_WEBVIEW_EDGE
#include "wx/msw/webview_edge.h"
#endif
#include "wx/webviewarchivehandler.h"
#include "wx/webviewfshandler.h"
#include "wx/numdlg.h"
#include "wx/infobar.h"
#include "wx/filesys.h"
#include "wx/fs_arc.h"
#include "wx/fs_mem.h"
#include "wx/stdpaths.h"
#include <wx/panel.h>
#include <wx/tbarbase.h>
#include "wx/textctrl.h"
#include <wx/timer.h>
#include <map>
namespace Slic3r {
namespace GUI {
//#define DEBUG_URL_PANEL
class WebViewPanel : public wxPanel
{
public:
WebViewPanel(wxWindow *parent, const wxString& default_url);
virtual ~WebViewPanel();
void load_url(const wxString& url);
void load_default_url_delayed();
void load_error_page();
void on_show(wxShowEvent& evt);
virtual void on_script_message(wxWebViewEvent& evt);
void on_idle(wxIdleEvent& evt);
void on_url(wxCommandEvent& evt);
void on_back_button(wxCommandEvent& evt);
void on_forward_button(wxCommandEvent& evt);
void on_stop_button(wxCommandEvent& evt);
void on_reload_button(wxCommandEvent& evt);
void on_view_source_request(wxCommandEvent& evt);
void on_view_text_request(wxCommandEvent& evt);
void on_tools_clicked(wxCommandEvent& evt);
void on_error(wxWebViewEvent& evt);
void run_script(const wxString& javascript);
void on_run_script_custom(wxCommandEvent& evt);
void on_add_user_script(wxCommandEvent& evt);
void on_set_custom_user_agent(wxCommandEvent& evt);
void on_clear_selection(wxCommandEvent& evt);
void on_delete_selection(wxCommandEvent& evt);
void on_select_all(wxCommandEvent& evt);
void On_enable_context_menu(wxCommandEvent& evt);
void On_enable_dev_tools(wxCommandEvent& evt);
void on_close(wxCloseEvent& evt);
wxTimer * m_LoginUpdateTimer{nullptr};
wxString get_default_url() const { return m_default_url; }
void set_default_url(const wxString& url) { m_default_url = url; }
virtual void sys_color_changed();
protected:
wxWebView* m_browser;
bool m_load_default_url { false };
#ifdef DEBUG_URL_PANEL
wxBoxSizer *bSizer_toolbar;
wxButton * m_button_back;
wxButton * m_button_forward;
wxButton * m_button_stop;
wxButton * m_button_reload;
wxTextCtrl *m_url;
wxButton * m_button_tools;
wxMenu* m_tools_menu;
wxMenuItem* m_script_custom;
wxInfoBar *m_info;
wxStaticText* m_info_text;
wxMenuItem* m_context_menu;
wxMenuItem* m_dev_tools;
#endif
long m_zoomFactor;
// Last executed JavaScript snippet, for convenience.
wxString m_javascript;
wxString m_response_js;
wxString m_default_url;
//DECLARE_EVENT_TABLE()
};
class ConnectRequestHandler
{
public:
ConnectRequestHandler();
~ConnectRequestHandler();
void handle_message(const std::string& message);
void resend_config();
protected:
// action callbacs stored in m_actions
virtual void on_request_access_token();
virtual void on_request_config();
virtual void on_request_update_selected_printer_action() = 0;
virtual void run_script_bridge(const wxString& script) = 0;
virtual void request_compatible_printers() = 0;
std::map<std::string, std::function<void(void)>> m_actions;
std::string m_message_data;
};
class ConnectWebViewPanel : public WebViewPanel, public ConnectRequestHandler
{
public:
ConnectWebViewPanel(wxWindow* parent);
void on_script_message(wxWebViewEvent& evt) override;
void logout();
void sys_color_changed() override;
protected:
void on_request_update_selected_printer_action() override;
void request_compatible_printers() override {}
void run_script_bridge(const wxString& script) override {run_script(script); }
};
class PrinterWebViewPanel : public WebViewPanel
{
public:
PrinterWebViewPanel(wxWindow* parent, const wxString& default_url);
void on_loaded(wxWebViewEvent& evt);
void send_api_key();
void send_credentials();
void set_api_key(const std::string& key) { m_api_key = key; }
void set_credentials(const std::string& usr, const std::string& psk) { m_usr = usr; m_psk = psk; }
void clear() { m_api_key.clear(); m_usr.clear(); m_psk.clear(); m_api_key_sent = false; }
void sys_color_changed() override;
private:
std::string m_api_key;
std::string m_usr;
std::string m_psk;
bool m_api_key_sent {false};
};
class WebViewDialog : public wxDialog
{
public:
WebViewDialog(wxWindow* parent, const wxString& url, const wxString& dialog_name, const wxSize& size);
virtual ~WebViewDialog();
virtual void on_show(wxShowEvent& evt) = 0;
virtual void on_script_message(wxWebViewEvent& evt) = 0;
void run_script(const wxString& javascript);
protected:
wxWebView* m_browser;
};
class PrinterPickWebViewDialog : public WebViewDialog, public ConnectRequestHandler
{
public:
PrinterPickWebViewDialog(wxWindow* parent, std::string& ret_val);
void on_show(wxShowEvent& evt) override;
void on_script_message(wxWebViewEvent& evt) override;
protected:
void on_request_update_selected_printer_action() override;
void request_compatible_printers() override;
void run_script_bridge(const wxString& script) override { run_script(script); }
private:
std::string& m_ret_val;
};
class SourceViewDialog : public wxDialog
{
public:
SourceViewDialog(wxWindow* parent, wxString source);
};
} // GUI
} // Slic3r
#endif /* slic3r_Tab_hpp_ */

View File

@ -86,6 +86,12 @@ void TextInput::Create(wxWindow * parent,
if (!icon.IsEmpty()) {
this->drop_down_icon = ScalableBitmap(this, icon.ToStdString(), 16);
this->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& event) {
const wxPoint pos = event.GetLogicalPosition(wxClientDC(this));
if (OnClickDropDownIcon && dd_icon_rect.Contains(pos))
OnClickDropDownIcon();
event.Skip();
});
}
messureSize();
}
@ -142,6 +148,16 @@ void TextInput::SetSelection(long from, long to)
text_ctrl->SetSelection(from, to);
}
void TextInput::SysColorsChanged()
{
if (auto parent = this->GetParent()) {
SetBackgroundColour(parent->GetBackgroundColour());
SetForegroundColour(parent->GetForegroundColour());
if (this->drop_down_icon.bmp().IsOk())
this->drop_down_icon.sys_color_changed();
}
}
void TextInput::SetIcon(const wxBitmapBundle& icon_in)
{
icon = icon_in;
@ -285,6 +301,7 @@ void TextInput::render(wxDC& dc)
wxSize szIcon = drop_down_icon.GetSize();
pt_r.x -= szIcon.x + 2;
pt_r.y = (size.y - szIcon.y) / 2;
dd_icon_rect = wxRect(pt_r, szIcon);
dc.DrawBitmap(drop_down_icon.get_bitmap(), pt_r);
}

View File

@ -17,6 +17,9 @@ class TextInput : public wxNavigationEnabled<StaticBox>
static const int TextInputWidth = 200;
static const int TextInputHeight = 50;
wxRect dd_icon_rect;
std::function<void()> OnClickDropDownIcon{ nullptr };
public:
TextInput();
@ -71,6 +74,10 @@ public:
void SetSelection(long from, long to);
void SysColorsChanged();
void SetOnDropDownIcon(std::function<void()> click_drop_down_icon_fn) { OnClickDropDownIcon = click_drop_down_icon_fn; }
protected:
virtual void OnEdit() {}

View File

@ -208,7 +208,7 @@ void WifiConfigDialog::rescan_networks(bool select)
std::string current = m_wifi_scanner->get_current_ssid();
const auto& map = m_wifi_scanner->get_map();
m_ssid_combo->Clear();
for (const auto &pair : map) {
for (const auto& pair : map) {
m_ssid_combo->Append(pair.first);
// select ssid of current network (if connected)
if (current == pair.first)

View File

@ -9,6 +9,7 @@
#include <cmath>
#include <wx/sizer.h>
#include <wx/accel.h>
#include <boost/algorithm/string/replace.hpp>
@ -51,6 +52,14 @@ void sys_color_changed_menu(wxMenu* menu)
}
#endif /* no __linux__ */
#ifndef __APPLE__
std::vector<wxAcceleratorEntry*>& accelerator_entries_cache()
{
static std::vector<wxAcceleratorEntry*> entries;
return entries;
}
#endif
void enable_menu_item(wxUpdateUIEvent& evt, std::function<bool()> const cb_condition, wxMenuItem* item, wxWindow* win)
{
const bool enable = cb_condition();
@ -78,8 +87,20 @@ wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const
event_handler->Bind(wxEVT_MENU, cb, id);
else
#endif // __WXMSW__
#ifndef __APPLE__
if (parent)
parent->Bind(wxEVT_MENU, cb, id);
else
#endif // n__APPLE__
menu->Bind(wxEVT_MENU, cb, id);
#ifndef __APPLE__
if (wxAcceleratorEntry* entry = wxAcceleratorEntry::Create(string)) {
entry->SetMenuItem(item);
accelerator_entries_cache().push_back(entry);
}
#endif
if (parent) {
parent->Bind(wxEVT_UPDATE_UI, [cb_condition, item, parent](wxUpdateUIEvent& evt) {
enable_menu_item(evt, cb_condition, item, parent); }, id);
@ -809,6 +830,22 @@ ScalableBitmap(parent, icon_name, icon_size.x, icon_size.y, grayscale)
{
}
ScalableBitmap::ScalableBitmap(wxWindow* parent, boost::filesystem::path& icon_path, const wxSize icon_size)
:m_parent(parent), m_bmp_width(icon_size.x), m_bmp_height(icon_size.y)
{
wxString path = Slic3r::GUI::from_u8(icon_path.string());
wxBitmap bitmap;
const std::string ext = icon_path.extension().string();
if (ext == ".png") {
bitmap.LoadFile(path, wxBITMAP_TYPE_PNG);
wxBitmap::Rescale(bitmap, icon_size);
m_bmp = wxBitmapBundle(bitmap);
}
else if (ext == ".svg") {
m_bmp = wxBitmapBundle::FromSVGFile(path, icon_size);
}
}
void ScalableBitmap::sys_color_changed()
{
m_bmp = *get_bmp_bundle(m_icon_name, m_bmp_width, m_bmp_height);
@ -904,10 +941,20 @@ int ScalableButton::GetBitmapHeight()
#endif
}
wxSize ScalableButton::GetBitmapSize()
{
#ifdef __APPLE__
return wxSize(GetBitmap().GetScaledWidth(), GetBitmap().GetScaledHeight());
#else
return wxSize(GetBitmap().GetWidth(), GetBitmap().GetHeight());
#endif
}
void ScalableButton::sys_color_changed()
{
Slic3r::GUI::wxGetApp().UpdateDarkUI(this, m_has_border);
if (m_current_icon_name.empty())
return;
wxBitmapBundle bmp = *get_bmp_bundle(m_current_icon_name, m_bmp_width, m_bmp_height);
SetBitmap(bmp);
SetBitmapCurrent(bmp);

View File

@ -18,6 +18,7 @@
#include <vector>
#include <functional>
#include <boost/filesystem.hpp>
#ifndef __linux__
@ -26,6 +27,11 @@ void sys_color_changed_menu(wxMenu* menu);
inline void sys_color_changed_menu(wxMenu* /* menu */) {}
#endif // no __linux__
#ifndef __APPLE__
// Caching wxAcceleratorEntries to use them during mainframe creation
std::vector<wxAcceleratorEntry*>& accelerator_entries_cache();
#endif // no __APPLE__
wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description,
std::function<void(wxCommandEvent& event)> cb, wxBitmapBundle* icon, wxEvtHandler* event_handler = nullptr,
std::function<bool()> const cb_condition = []() { return true;}, wxWindow* parent = nullptr, int insert_pos = wxNOT_FOUND);
@ -162,6 +168,10 @@ public:
const wxSize icon_size,
const bool grayscale = false);
ScalableBitmap( wxWindow *parent,
boost::filesystem::path& icon_path,
const wxSize icon_size);
~ScalableBitmap() {}
void sys_color_changed();
@ -176,6 +186,7 @@ public:
wxSize GetSize() const { return get_preferred_size(m_bmp, m_parent); }
int GetWidth() const { return GetSize().GetWidth(); }
int GetHeight() const { return GetSize().GetHeight(); }
bool IsOk() const { return m_bmp.IsOk(); }
private:
wxWindow* m_parent{ nullptr };
@ -258,6 +269,7 @@ public:
bool SetBitmap_(const std::string& bmp_name);
void SetBitmapDisabled_(const ScalableBitmap &bmp);
int GetBitmapHeight();
wxSize GetBitmapSize();
virtual void sys_color_changed();

View File

@ -557,6 +557,12 @@ Http& Http::set_post_body(const std::string &body)
return *this;
}
Http& Http::set_referer(const std::string& referer)
{
if (p) { ::curl_easy_setopt(p->curl, CURLOPT_REFERER, referer.c_str()); }
return *this;
}
Http& Http::set_put_body(const fs::path &path)
{
if (p) { p->set_put_body(path);}
@ -587,6 +593,22 @@ Http& Http::on_ip_resolve(IPResolveFn fn)
return *this;
}
Http& Http::cookie_file(const std::string& file_path)
{
if (p) {
::curl_easy_setopt(p->curl, CURLOPT_COOKIEFILE, file_path.c_str());
}
return *this;
}
Http& Http::cookie_jar(const std::string& file_path)
{
if (p) {
::curl_easy_setopt(p->curl, CURLOPT_COOKIEJAR, file_path.c_str());
}
return *this;
}
Http::Ptr Http::perform()
{
auto self = std::make_shared<Http>(std::move(*this));

View File

@ -128,6 +128,10 @@ public:
// Called if curl_easy_getinfo resolved just used IP address.
Http& on_ip_resolve(IPResolveFn fn);
Http& cookie_file(const std::string& file_path);
Http& cookie_jar(const std::string& file_path);
Http& set_referer(const std::string& referer);
// Starts performing the request in a background thread
Ptr perform();
// Starts performing the request on the current thread

View File

@ -5,12 +5,15 @@
#ifndef slic3r_MacDarkMode_hpp_
#define slic3r_MacDarkMode_hpp_
#include <wx/event.h>
namespace Slic3r {
namespace GUI {
#if __APPLE__
extern bool mac_dark_mode();
extern double mac_max_scaling_factor();
void WKWebView_evaluateJavaScript(void * web, wxString const & script, void (*callback)(wxString const &));
#endif

View File

@ -1,5 +1,7 @@
#import "MacDarkMode.hpp"
#include "wx/osx/core/cfstring.h"
#import <algorithm>
#import <Cocoa/Cocoa.h>
@ -33,6 +35,16 @@ double mac_max_scaling_factor()
return scaling;
}
void WKWebView_evaluateJavaScript(void * web, wxString const & script, void (*callback)(wxString const &))
{
[(WKWebView*)web evaluateJavaScript:wxCFStringRef(script).AsNSString() completionHandler: ^(id result, NSError *error) {
if (callback && error != nil) {
wxString err = wxCFStringRef(error.localizedFailureReason).AsString();
callback(err);
}
}];
}
}
}

View File

@ -710,7 +710,7 @@ bool PrusaLink::get_storage(wxArrayString& storage_path, wxArrayString& storage_
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get storage at: %2%") % name % url;
wxString wlang = GUI::wxGetApp().current_language_code();
std::string lang = GUI::format(wlang.SubString(0, 1));
std::string lang = GUI::into_u8(wlang.SubString(0, 1));
auto http = Http::get(std::move(url));
set_auth(http);
@ -1161,7 +1161,7 @@ void PrusaConnect::set_http_post_header_args(Http& http, PrintHostPostUploadActi
{
// Language for accept message
wxString wlang = GUI::wxGetApp().current_language_code();
std::string lang = GUI::format(wlang.SubString(0, 1));
std::string lang = GUI::into_u8(wlang.SubString(0, 1));
http.header("Accept-Language", lang);
// Post action
if (post_action == PrintHostPostUploadAction::StartPrint) {

View File

@ -29,6 +29,7 @@
#include "Repetier.hpp"
#include "MKS.hpp"
#include "Moonraker.hpp"
#include "PrusaConnect.hpp"
#include "../GUI/PrintHostDialogs.hpp"
namespace fs = boost::filesystem;
@ -63,6 +64,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
case htRepetier: return new Repetier(config);
case htPrusaLink: return new PrusaLink(config);
case htPrusaConnect: return new PrusaConnect(config);
case htPrusaConnectNew: return new PrusaConnectNew(config);
case htMKS: return new MKS(config);
case htMoonraker: return new Moonraker(config);
default: return nullptr;

View File

@ -0,0 +1,328 @@
#include "PrusaConnect.hpp"
#include "Http.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/UserAccount.hpp"
#include <boost/log/trivial.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/filesystem.hpp>
#include <curl/curl.h>
namespace fs = boost::filesystem;
namespace pt = boost::property_tree;
namespace Slic3r {
namespace
{
std::string escape_string(const std::string& unescaped)
{
std::string ret_val;
CURL* curl = curl_easy_init();
if (curl) {
char* decoded = curl_easy_escape(curl, unescaped.c_str(), unescaped.size());
if (decoded) {
ret_val = std::string(decoded);
curl_free(decoded);
}
curl_easy_cleanup(curl);
}
return ret_val;
}
std::string escape_path_by_element(const boost::filesystem::path& path)
{
std::string ret_val = escape_string(path.filename().string());
boost::filesystem::path parent(path.parent_path());
while (!parent.empty() && parent.string() != "/") // "/" check is for case "/file.gcode" was inserted. Then boost takes "/" as parent_path.
{
ret_val = escape_string(parent.filename().string()) + "/" + ret_val;
parent = parent.parent_path();
}
return ret_val;
}
}
PrusaConnectNew::PrusaConnectNew(DynamicPrintConfig *config)
: m_uuid(config->opt_string("print_host"))
, m_team_id(config->opt_string("printhost_apikey"))
{}
const char* PrusaConnectNew::get_name() const { return "PrusaConnectNew"; }
bool PrusaConnectNew::test(wxString& curl_msg) const
{
// Test is not used by upload and gets list of files on a device.
const std::string name = get_name();
std::string url = GUI::format("https://dev.connect.prusa3d.com/app/teams/%1%/files?printer_uuid=%2%", m_team_id, m_uuid);
const std::string access_token = GUI::wxGetApp().plater()->get_user_account()->get_access_token();
BOOST_LOG_TRIVIAL(info) << GUI::format("%1%: Get files/raw at: %2%", name, url);
bool res = true;
auto http = Http::get(std::move(url));
http.header("Authorization", "Bearer " + access_token);
http.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
res = false;
curl_msg = format_error(body, error, status);
})
.on_complete([&, this](std::string body, unsigned) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Got files/raw: %2%") % name % body;
})
.perform_sync();
return res;
}
bool PrusaConnectNew::init_upload(PrintHostUpload upload_data, std::string& out) const
{
// Register upload. Then upload must be performed immediately with returned "id"
bool res = true;
boost::system::error_code ec;
boost::uintmax_t size = boost::filesystem::file_size(upload_data.source_path, ec);
const std::string name = get_name();
const std::string file_size = std::to_string(size);
const std::string access_token = GUI::wxGetApp().plater()->get_user_account()->get_access_token();
const std::string escaped_upload_path = escape_path_by_element(upload_data.upload_path);
const std::string escaped_upload_filename = escape_path_by_element(upload_data.upload_path.filename());
std::string url = GUI::format("%1%/app/users/teams/%2%/uploads", get_host(), m_team_id);
const std::string request_body_json = GUI::format(
"{"
"\"filename\": \"%1%\", "
"\"size\": %2%, "
"\"path\": \"%3%\", "
"\"force\": true, "
"\"printer_uuid\": \"%4%\""
"}"
, escaped_upload_filename
, file_size
, upload_data.storage + "/" + escaped_upload_path
, m_uuid
);
BOOST_LOG_TRIVIAL(info) << "Register upload to "<< name<<". Url: " << url << "\nBody: " << request_body_json;
Http http = Http::post(std::move(url));
http.header("Authorization", "Bearer " + access_token)
.header("Content-Type", "application/json")
.set_post_body(request_body_json)
.on_complete([&](std::string body, unsigned status) {
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: File upload registered: HTTP %2%: %3%") % name % status % body;
out = body;
})
.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << body;
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error registering file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
res = false;
out = GUI::into_u8(format_error(body, error, status));
})
.perform_sync();
return res;
}
bool PrusaConnectNew::upload(PrintHostUpload upload_data, ProgressFn progress_fn, ErrorFn error_fn, InfoFn info_fn) const
{
std::string init_out;
if (!init_upload(upload_data, init_out))
{
error_fn(std::move(GUI::from_u8(init_out)));
return false;
}
// init reply format: {"id": 1234, "team_id": 12345, "name": "filename.gcode", "size": 123, "hash": "QhE0LD76vihC-F11Jfx9rEqGsk4.", "state": "INITIATED", "source": "CONNECT_USER", "path": "/usb/filename.bgcode"}
std::string upload_id;
try
{
std::stringstream ss(init_out);
pt::ptree ptree;
pt::read_json(ss, ptree);
const auto id_opt = ptree.get_optional<std::string>("id");
if (!id_opt) {
error_fn(std::move(_L("Failed to extract upload id from server reply.")));
return false;
}
upload_id = *id_opt;
}
catch (const std::exception&)
{
error_fn(std::move(_L("Failed to extract upload id from server reply.")));
return false;
}
const std::string name = get_name();
const std::string access_token = GUI::wxGetApp().plater()->get_user_account()->get_access_token();
const std::string escaped_upload_path = escape_string(upload_data.storage + "/" + upload_data.upload_path.string());
const std::string to_print = upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false";
const std::string to_queue = upload_data.post_action == PrintHostPostUploadAction::QueuePrint ? "true" : "false";
std::string url = GUI::format("%1%/app/teams/%2%/files/raw?upload_id=%3%&force=true&printer_uuid=%4%&path=%5%&to_print=%6%&to_queue=%7%", get_host(), m_team_id, upload_id, m_uuid, escaped_upload_path, to_print, to_queue);
bool res = true;
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%")
% name
% upload_data.source_path
% url
% upload_data.upload_path.filename().string()
% upload_data.upload_path.parent_path().string()
% (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false");
Http http = Http::put(std::move(url));
http.set_put_body(upload_data.source_path)
.header("Content-Type", "text/x.gcode")
.header("Authorization", "Bearer " + access_token)
.on_complete([&](std::string body, unsigned status) {
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
})
.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
error_fn(format_error(body, error, status));
res = false;
})
.on_progress([&](Http::Progress progress, bool& cancel) {
progress_fn(std::move(progress), cancel);
if (cancel) {
// Upload was canceled
BOOST_LOG_TRIVIAL(info) << name << ": Upload canceled";
res = false;
}
})
.perform_sync();
return res;
}
bool PrusaConnectNew::get_storage(wxArrayString& storage_path, wxArrayString& storage_name) const
{
const char* name = get_name();
bool res = true;
std::string url = GUI::format("%1%/app/printers/%2%/storages", get_host(), m_uuid);
const std::string access_token = GUI::wxGetApp().plater()->get_user_account()->get_access_token();
wxString error_msg;
struct StorageInfo {
wxString path;
wxString name;
bool read_only = false;
long long free_space = -1;
};
std::vector<StorageInfo> storage;
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get storage at: %2%") % name % url;
wxString wlang = GUI::wxGetApp().current_language_code();
std::string lang = GUI::format(wlang.SubString(0, 1));
auto http = Http::get(std::move(url));
http.header("Authorization", "Bearer " + access_token)
.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting storage: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
error_msg = L"\n\n" + boost::nowide::widen(error);
res = false;
// If status is 0, the communication with the printer has failed completely (most likely a timeout), if the status is <= 400, it is an error returned by the pritner.
// If 0, we can show error to the user now, as we know the communication has failed. (res = true will do the trick.)
// if not 0, we must not show error, as not all printers support api/v1/storage endpoint.
// So we must be extra careful here, or we might be showing errors on perfectly fine communication.
if (status == 0)
res = true;
})
.on_complete([&](std::string body, unsigned) {
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got storage: %2%") % name % body;
// {"storages": [{"mountpoint": "/usb", "name": "usb", "free_space": 16340844544, "type": "USB", "is_sfn": true, "read_only": false, "file_count": 1}]}
try
{
std::stringstream ss(body);
pt::ptree ptree;
pt::read_json(ss, ptree);
// what if there is more structure added in the future? Enumerate all elements?
if (ptree.front().first != "storages") {
res = false;
return;
}
// each storage has own subtree of storage_list
for (const auto& section : ptree.front().second) {
const auto name = section.second.get_optional<std::string>("name");
const auto path = section.second.get_optional<std::string>("mountpoint");
const auto space = section.second.get_optional<std::string>("free_space");
const auto read_only = section.second.get_optional<bool>("read_only");
const auto ro = section.second.get_optional<bool>("ro"); // In PrusaLink 0.7.0RC2 "read_only" value is stored under "ro".
const auto available = section.second.get_optional<bool>("available");
if (path && (!available || *available)) {
StorageInfo si;
si.path = boost::nowide::widen(*path);
si.name = name ? boost::nowide::widen(*name) : wxString();
// If read_only is missing, assume it is NOT read only.
// si.read_only = read_only ? *read_only : false; // version without "ro"
si.read_only = (read_only ? *read_only : (ro ? *ro : false));
si.free_space = space ? std::stoll(*space) : 1; // If free_space is missing, assume there is free space.
storage.emplace_back(std::move(si));
}
}
}
catch (const std::exception&)
{
res = false;
}
})
.perform_sync();
for (const auto& si : storage) {
if (!si.read_only && si.free_space > 0) {
storage_path.push_back(si.path);
storage_name.push_back(si.name);
}
}
if (res && storage_path.empty()) {
if (!storage.empty()) { // otherwise error_msg is already filled
error_msg = L"\n\n" + _L("Storages found") + L": \n";
for (const auto& si : storage) {
error_msg += GUI::format_wxstr(si.read_only ?
// TRN %1% = storage path
_L("%1% : read only") :
// TRN %1% = storage path
_L("%1% : no free space"), si.path) + L"\n";
}
}
// TRN %1% = host
std::string message = GUI::format(_L("Upload has failed. There is no suitable storage found at %1%. "), get_host()) + GUI::into_u8(error_msg);
BOOST_LOG_TRIVIAL(error) << message;
throw Slic3r::IOError(message);
}
return res;
}
wxString PrusaConnectNew::get_test_ok_msg() const
{
return _L("Test OK.");
}
wxString PrusaConnectNew::get_test_failed_msg(wxString& msg) const
{
return _L("Test NOK.");
}
std::string PrusaConnectNew::get_team_id(const std::string& data) const
{
boost::property_tree::ptree ptree;
try {
std::stringstream ss(data);
boost::property_tree::read_json(ss, ptree);
}
catch (const std::exception&) {
return {};
}
const auto team_id = ptree.get_optional<std::string>("team_id");
if (team_id)
{
return *team_id;
}
return {};
}
}

View File

@ -0,0 +1,49 @@
#ifndef slic3r_PrusaConnect_hpp_
#define slic3r_PrusaConnect_hpp_
#include "PrintHost.hpp"
#include "libslic3r/PrintConfig.hpp"
/*
#include <string>
#include <wx/string.h>
#include <boost/optional.hpp>
#include <boost/asio/ip/address.hpp>
*/
namespace Slic3r {
class DynamicPrintConfig;
class Http;
class PrusaConnectNew : public PrintHost
{
public:
PrusaConnectNew(DynamicPrintConfig *config);
~PrusaConnectNew() override = default;
const char* get_name() const override;
virtual bool test(wxString &curl_msg) const override;
wxString get_test_ok_msg () const override;
wxString get_test_failed_msg (wxString &msg) const override;
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
bool has_auto_discovery() const override { return true; }
bool can_test() const override { return true; }
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint | PrintHostPostUploadAction::QueuePrint; }
std::string get_host() const override { return "https://dev.connect.prusa3d.com"; }
bool get_storage(wxArrayString& storage_path, wxArrayString& storage_name) const override;
//const std::string& get_apikey() const { return m_apikey; }
//const std::string& get_cafile() const { return m_cafile; }
private:
std::string m_uuid;
std::string m_team_id;
bool init_upload(PrintHostUpload upload_data, std::string& out) const;
std::string get_team_id(const std::string& data) const;
};
}
#endif

View File

@ -0,0 +1,87 @@
#include "Secrets.hpp"
#include <stdio.h>
#if wxUSE_SECRETSTORE
#include <wx/secretstore.h>
#endif
namespace Slic3r {
#if wxUSE_SECRETSTORE
static bool PrintResult(bool ok)
{
wxPuts(ok ? "ok" : "ERROR");
return ok;
}
bool SelfTest(wxSecretStore& store, const wxString& service)
{
wxPrintf("Running the tests...\n");
const wxString userTest("test");
const wxSecretValue secret1(6, "secret");
wxPrintf("Storing the password:\t");
bool ok = store.Save(service, userTest, secret1);
if ( !PrintResult(ok) )
{
// The rest of the tests will probably fail too, no need to continue.
wxPrintf("Bailing out.\n");
return false;
}
wxPrintf("Loading the password:\t");
wxSecretValue secret;
wxString user;
ok = PrintResult(store.Load(service, user, secret) &&
user == userTest &&
secret == secret1);
// Overwriting the password should work.
const wxSecretValue secret2(6, "privet");
wxPrintf("Changing the password:\t");
if ( PrintResult(store.Save(service, user, secret2)) )
{
wxPrintf("Reloading the password:\t");
if ( !PrintResult(store.Load(service, user, secret) &&
secret == secret2) )
ok = false;
}
else
ok = false;
wxPrintf("Deleting the password:\t");
if ( !PrintResult(store.Delete(service)) )
ok = false;
// This is supposed to fail now.
wxPrintf("Deleting it again:\t");
if ( !PrintResult(!store.Delete(service)) )
ok = false;
// And loading should fail too.
wxPrintf("Loading after deleting:\t");
if ( !PrintResult(!store.Load(service, user, secret)) )
ok = false;
if ( ok )
wxPrintf("All tests passed!\n");
return ok;
}
#endif //wxUSE_SECRETSTORE
bool check_secrets()
{
#if wxUSE_SECRETSTORE
wxSecretStore store = wxSecretStore::GetDefault();
return SelfTest(store, "prusaslicer");
#else
printf("wxSecret not supported.\n");
return true;
#endif
}
} // namespace Slic3r

View File

@ -0,0 +1,10 @@
#ifndef PRUSASLICER_SECRETS_HPP
#define PRUSASLICER_SECRETS_HPP
namespace Slic3r {
bool check_secrets();
}
#endif // PRUSASLICER_SECRETS_HPP

View File

@ -4,10 +4,12 @@ add_executable(${_TEST_NAME}_tests
slic3r_jobs_tests.cpp
slic3r_version_tests.cpp
slic3r_arrangejob_tests.cpp
secretstore_tests.cpp
)
# mold linker for successful linking needs also to link TBB library and link it before libslic3r.
target_link_libraries(${_TEST_NAME}_tests test_common TBB::tbb TBB::tbbmalloc libslic3r_gui libslic3r)
if (MSVC)
target_link_libraries(${_TEST_NAME}_tests Setupapi.lib)
endif ()

View File

@ -0,0 +1,8 @@
#include "catch2/catch.hpp"
#include "slic3r/Utils/Secrets.hpp"
TEST_CASE("Secret store test", "[secretstore]") {
REQUIRE(Slic3r::check_secrets());
}