diff --git a/CMakeLists.txt b/CMakeLists.txt index 25aff34d46..a6e6e2c8ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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() diff --git a/deps/+WebView2/WebView2.cmake b/deps/+WebView2/WebView2.cmake new file mode 100644 index 0000000000..dff90d5936 --- /dev/null +++ b/deps/+WebView2/WebView2.cmake @@ -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 () \ No newline at end of file diff --git a/deps/+wxWidgets/wxWidgets.cmake b/deps/+wxWidgets/wxWidgets.cmake index ac15a48d0e..b621bb4973 100644 --- a/deps/+wxWidgets/wxWidgets.cmake +++ b/deps/+wxWidgets/wxWidgets.cmake @@ -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) diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 175b64a619..04c8c8e1d2 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -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) diff --git a/resources/icons/connect_gcode.svg b/resources/icons/connect_gcode.svg new file mode 100644 index 0000000000..fb57be9c46 --- /dev/null +++ b/resources/icons/connect_gcode.svg @@ -0,0 +1,65 @@ + +image/svg+xml + + + + + + + diff --git a/resources/icons/login.svg b/resources/icons/login.svg new file mode 100644 index 0000000000..0b01b66613 --- /dev/null +++ b/resources/icons/login.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/icons/logout.svg b/resources/icons/logout.svg new file mode 100644 index 0000000000..b2aa004057 --- /dev/null +++ b/resources/icons/logout.svg @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/resources/icons/printer_available.svg b/resources/icons/printer_available.svg new file mode 100644 index 0000000000..12e295098b --- /dev/null +++ b/resources/icons/printer_available.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + diff --git a/resources/icons/printer_busy.svg b/resources/icons/printer_busy.svg new file mode 100644 index 0000000000..32c8149174 --- /dev/null +++ b/resources/icons/printer_busy.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + diff --git a/resources/icons/printer_offline.svg b/resources/icons/printer_offline.svg new file mode 100644 index 0000000000..c56326470f --- /dev/null +++ b/resources/icons/printer_offline.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + diff --git a/resources/icons/sla_printer_available.svg b/resources/icons/sla_printer_available.svg new file mode 100644 index 0000000000..47db35e290 --- /dev/null +++ b/resources/icons/sla_printer_available.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + diff --git a/resources/icons/sla_printer_busy.svg b/resources/icons/sla_printer_busy.svg new file mode 100644 index 0000000000..fc4dcf3bc6 --- /dev/null +++ b/resources/icons/sla_printer_busy.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + diff --git a/resources/icons/sla_printer_offline.svg b/resources/icons/sla_printer_offline.svg new file mode 100644 index 0000000000..eb91f6fe5c --- /dev/null +++ b/resources/icons/sla_printer_offline.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + diff --git a/resources/icons/user.svg b/resources/icons/user.svg new file mode 100644 index 0000000000..b750021fc4 --- /dev/null +++ b/resources/icons/user.svg @@ -0,0 +1,18 @@ + + + + + Abstract user icon + + + + + \ No newline at end of file diff --git a/resources/web/connection_failed.html b/resources/web/connection_failed.html new file mode 100644 index 0000000000..72cb686232 --- /dev/null +++ b/resources/web/connection_failed.html @@ -0,0 +1,27 @@ + + + + + + Connection failed + + + +
+

Connection failed

+

Something went wrong.

+
+ + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9bbddbff90..dd5b70c5a5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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}) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 114ac7489a..82900f4ab8 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -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"); diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index c68588032d..1ad7e44c72 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 24bbe7a5b1..46126a621c 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -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; } diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index bc428a73f2..3f26b3f35c 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -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 { diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 533c693812..13f1185031 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -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(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. diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index b4fb5ce27d..b7d241abcf 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -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 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; + } } } diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 1e6aa1caa1..ddd22f348a 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -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 secret_callback = nullptr); // Enable / disable the "- default -" preset. void set_default_suppressed(bool default_suppressed); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 391db4309e..8f57c70f80 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -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("")); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index c14230d4f4..c48a8d6bb9 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -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 { diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index fea794c943..479144dab9 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -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) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 596fa2b697..26e1ddbe5e 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -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 &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 diff --git a/src/slic3r/GUI/ConfigWizard.hpp b/src/slic3r/GUI/ConfigWizard.hpp index 43e3b103af..1425b7cfa4 100644 --- a/src/slic3r/GUI/ConfigWizard.hpp +++ b/src/slic3r/GUI/ConfigWizard.hpp @@ -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__ }; } diff --git a/src/slic3r/GUI/DownloaderFileGet.cpp b/src/slic3r/GUI/DownloaderFileGet.cpp index ef9b0256e0..7a7c7044c2 100644 --- a/src/slic3r/GUI/DownloaderFileGet.cpp +++ b/src/slic3r/GUI/DownloaderFileGet.cpp @@ -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); } diff --git a/src/slic3r/GUI/FrequentlyChangedParameters.hpp b/src/slic3r/GUI/FrequentlyChangedParameters.hpp index 08d07afa05..0be8fb0415 100644 --- a/src/slic3r/GUI/FrequentlyChangedParameters.hpp +++ b/src/slic3r/GUI/FrequentlyChangedParameters.hpp @@ -17,6 +17,8 @@ #ifndef slic3r_FreqChangedParams_hpp_ #define slic3r_FreqChangedParams_hpp_ +#include + #include "Event.hpp" class wxButton; diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 6bbe209f32..0c129803a5 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -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(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(); diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 0660ca7cc9..5f714aae30 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -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(); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index a5646bb52e..115eba8351 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -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 @@ -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(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(mainframe->m_tabpanel)->UpdateMode(); -#endif + dynamic_cast(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(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 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*>(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 diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 6d14123af3..50c8c25773 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -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 m_mode_palette; @@ -247,7 +248,7 @@ public: std::vector get_mode_palette(); void set_mode_palette(const std::vector &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 m_login_dialog; }; DECLARE_APP(GUI_App) diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 45381e8f75..bed6c6368e 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -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); diff --git a/src/slic3r/GUI/InstanceCheck.cpp b/src/slic3r/GUI/InstanceCheck.cpp index a8f8adfdc9..595b71fd1a 100644 --- a/src/slic3r/GUI/InstanceCheck.cpp +++ b/src/slic3r/GUI/InstanceCheck.cpp @@ -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? diff --git a/src/slic3r/GUI/InstanceCheck.hpp b/src/slic3r/GUI/InstanceCheck.hpp index 3d9d2e0fb2..d70f4a90a5 100644 --- a/src/slic3r/GUI/InstanceCheck.hpp +++ b/src/slic3r/GUI/InstanceCheck.hpp @@ -48,8 +48,10 @@ class MainFrame; using LoadFromOtherInstanceEvent = Event>; using StartDownloadOtherInstanceEvent = Event>; +using LoginOtherInstanceEvent = Event; 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); diff --git a/src/slic3r/GUI/LoginDialog.cpp b/src/slic3r/GUI/LoginDialog.cpp new file mode 100644 index 0000000000..f106ddd61f --- /dev/null +++ b/src/slic3r/GUI/LoginDialog.cpp @@ -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 \ No newline at end of file diff --git a/src/slic3r/GUI/LoginDialog.hpp b/src/slic3r/GUI/LoginDialog.hpp new file mode 100644 index 0000000000..debd0c4418 --- /dev/null +++ b/src/slic3r/GUI/LoginDialog.hpp @@ -0,0 +1,38 @@ +#ifndef slic3r_LoginDialog_hpp_ +#define slic3r_LoginDialog_hpp_ + +#include "UserAccount.hpp" + +#include "GUI_Utils.hpp" + +#include +#include +#include +#include + +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 \ No newline at end of file diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 7c28b9eb36..ea28670ec6 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -20,9 +20,13 @@ #include #include #include +#include //#include #include #include +#if wxUSE_SECRETSTORE +#include +#endif #include #include @@ -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 @@ -175,19 +181,34 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S else init_menubar_as_editor(); +#ifndef __APPLE__ + std::vector& 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(m_tabpanel)->InsertPage(0, m_plater, _L("Plater"), std::string("plater"), true); - else #endif + dynamic_cast(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(m_tabpanel)->InsertPage(0, m_plater_page, _L("Plater"), std::string("plater"), true); - else #endif + dynamic_cast(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(m_tabpanel->GetPageCount())) { Tab* old_tab = dynamic_cast(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(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(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(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(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(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(m_tabpanel)->AddPage(panel, panel->title(), bmp_name); - else #endif + dynamic_cast(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(m_tabpanel)->Rescale(); + dynamic_cast(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(m_tabpanel)->OnColorsChanged(); -#endif #endif + dynamic_cast(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(m_tabpanel)->UpdateModeMarkers(); -#endif -#endif - - // update mode markers on side_bar - wxGetApp().sidebar().update_mode_markers(); + dynamic_cast(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(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 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(m_tabpanel)->Rescale(); + dynamic_cast(m_tabpanel)->Rescale(); #endif // update Tabs diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 37fcfc3ab2..4206c4496e 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -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 m_taskbar_icon; #endif // __APPLE__ diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index d74b28af60..f95a367545 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -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 diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 69902545a3..8d599efd8c 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -16,6 +17,10 @@ #include #include #include +#if wxUSE_SECRETSTORE +#include +#endif +#include #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& 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(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{ "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); diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.hpp b/src/slic3r/GUI/PhysicalPrinterDialog.hpp index 0de8df235f..2a26e1bf1e 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.hpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.hpp @@ -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}; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index bbfbc57088..cd446795b3 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -35,6 +35,8 @@ #include #include #include +#include +#include #include #include @@ -50,6 +52,9 @@ #include #include #include +#if wxUSE_SECRETSTORE +#include +#endif #include @@ -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 notification_manager; + std::unique_ptr 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(q)) + , user_account(std::make_unique(q, wxGetApp().app_config, wxGetApp().get_instance_hash_string())) , m_worker{q, std::make_unique(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("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 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 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(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& 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(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(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index ac596a9dcb..7455227597 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -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(); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index ee4c06f4a1..04c3f04da6 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -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__ } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index c40978b03d..bcd79ed69e 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -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 Connect 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& states) +{ + PrinterStatesCount states_cnt; + + for (size_t i = 0; i < states.size(); i++) { + if (states[i] == 0) + continue; + + ConnectPrinterState state = static_cast(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("%1%" , states_cnt.available_cnt), + format("%1%" , states_cnt.offline_cnt), + format("%1%", states_cnt.busy_cnt), + format("%1%", 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) { diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 421d96d481..5a9821b7de 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -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; } diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 4fcd2d0f9d..a35388a88a 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -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 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(this->FindWindowById(wxID_CANCEL, this)), true); - for (wxWindow* win : std::vector {search_line, search_list, check_category, check_english}) + for (wxWindow* win : std::vector {search_list, check_category, check_english}) if (win) GUI::wxGetApp().UpdateDarkUI(win); #endif diff --git a/src/slic3r/GUI/Search.hpp b/src/slic3r/GUI/Search.hpp index 571448d492..87187247f3 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -27,6 +27,7 @@ #include "Widgets/CheckBox.hpp" class CheckBox; +class TextInput; namespace Slic3r { @@ -91,6 +92,8 @@ class OptionsSearcher std::map groups_and_categories; PrinterTechnology printer_technology {ptAny}; ConfigOptionMode mode{ comUndef }; + TextInput* search_input { nullptr }; + SearchDialog* search_dialog { nullptr }; std::vector