Merge remote-tracking branch 'private/dk_280'
@ -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
@ -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 ()
|
14
deps/+wxWidgets/wxWidgets.cmake
vendored
@ -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
@ -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)
|
||||
|
65
resources/icons/connect_gcode.svg
Normal 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
@ -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 |
16
resources/icons/logout.svg
Normal 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 |
16
resources/icons/printer_available.svg
Normal 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 |
16
resources/icons/printer_busy.svg
Normal 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 |
16
resources/icons/printer_offline.svg
Normal 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 |
16
resources/icons/sla_printer_available.svg
Normal 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 |
16
resources/icons/sla_printer_busy.svg
Normal 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 |
16
resources/icons/sla_printer_offline.svg
Normal 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
@ -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 |
27
resources/web/connection_failed.html
Normal 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>
|
@ -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})
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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>
|
||||
|
@ -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; }
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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(""));
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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__
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -17,6 +17,8 @@
|
||||
#ifndef slic3r_FreqChangedParams_hpp_
|
||||
#define slic3r_FreqChangedParams_hpp_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Event.hpp"
|
||||
|
||||
class wxButton;
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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?
|
||||
|
@ -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);
|
||||
|
||||
|
95
src/slic3r/GUI/LoginDialog.cpp
Normal 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
|
38
src/slic3r/GUI/LoginDialog.hpp
Normal 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
|
@ -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
|
||||
|
@ -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__
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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};
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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__
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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; }
|
||||
|
@ -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
|
||||
|
||||
|
@ -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(); }
|
||||
};
|
||||
|
@ -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();
|
||||
|
@ -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 };
|
||||
};
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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
@ -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
@ -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_
|
@ -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);
|
||||
|
370
src/slic3r/GUI/UserAccount.cpp
Normal 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
|
136
src/slic3r/GUI/UserAccount.hpp
Normal 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_
|
497
src/slic3r/GUI/UserAccountCommunication.cpp
Normal 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
|
92
src/slic3r/GUI/UserAccountCommunication.hpp
Normal 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
|
222
src/slic3r/GUI/UserAccountSession.cpp
Normal 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
|
196
src/slic3r/GUI/UserAccountSession.hpp
Normal 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
@ -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;
|
||||
}
|
||||
}
|
16
src/slic3r/GUI/WebView.hpp
Normal 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_
|
711
src/slic3r/GUI/WebViewDialog.cpp
Normal 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
|
202
src/slic3r/GUI/WebViewDialog.hpp
Normal 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_ */
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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() {}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
328
src/slic3r/Utils/PrusaConnect.cpp
Normal 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 {};
|
||||
}
|
||||
|
||||
}
|
49
src/slic3r/Utils/PrusaConnect.hpp
Normal 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
|
87
src/slic3r/Utils/Secrets.cpp
Normal 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
|
10
src/slic3r/Utils/Secrets.hpp
Normal file
@ -0,0 +1,10 @@
|
||||
#ifndef PRUSASLICER_SECRETS_HPP
|
||||
#define PRUSASLICER_SECRETS_HPP
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
bool check_secrets();
|
||||
|
||||
}
|
||||
|
||||
#endif // PRUSASLICER_SECRETS_HPP
|
@ -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 ()
|
||||
|
8
tests/slic3rutils/secretstore_tests.cpp
Normal file
@ -0,0 +1,8 @@
|
||||
#include "catch2/catch.hpp"
|
||||
|
||||
#include "slic3r/Utils/Secrets.hpp"
|
||||
|
||||
TEST_CASE("Secret store test", "[secretstore]") {
|
||||
|
||||
REQUIRE(Slic3r::check_secrets());
|
||||
}
|