diff --git a/resources/localization/list.txt b/resources/localization/list.txt index 77b7fe8c83..6a1701f0dd 100644 --- a/resources/localization/list.txt +++ b/resources/localization/list.txt @@ -87,6 +87,7 @@ src/slic3r/GUI/Jobs/SLAImportDialog.hpp src/slic3r/GUI/Jobs/SLAImportJob.cpp src/slic3r/GUI/KBShortcutsDialog.cpp src/slic3r/GUI/MainFrame.cpp +src/slic3r/GUI/UpdatesUIManager.cpp src/slic3r/GUI/Mouse3DController.cpp src/slic3r/GUI/MsgDialog.cpp src/slic3r/GUI/NotificationManager.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 9b3303d5e8..0cf5b4aa5d 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -126,6 +126,8 @@ set(SLIC3R_GUI_SOURCES GUI/IconManager.hpp GUI/MainFrame.cpp GUI/MainFrame.hpp + GUI/UpdatesUIManager.cpp + GUI/UpdatesUIManager.hpp GUI/FrequentlyChangedParameters.cpp GUI/FrequentlyChangedParameters.hpp GUI/Sidebar.cpp diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 6074a2765b..d3b9a689c8 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -13,6 +13,7 @@ #include "GUI_ObjectManipulation.hpp" #include "GUI_Factories.hpp" #include "TopBar.hpp" +#include "UpdatesUIManager.hpp" #include "format.hpp" // Localization headers: include libslic3r version first so everything in this file @@ -2499,6 +2500,7 @@ wxMenu* GUI_App::get_config_menu() local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip); local_menu->Append(config_id_base + ConfigMenuSnapshots, _L("&Configuration Snapshots") + dots, _L("Inspect / activate configuration snapshots")); local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot")); + local_menu->Append(config_id_base + ConfigMenuManageUpdateConf, _L("Manage Configuration Updates"), _L("Manage Configuration Updates")); local_menu->Append(config_id_base + ConfigMenuUpdateConf, _L("Check for Configuration Updates"), _L("Check for configuration updates")); local_menu->Append(config_id_base + ConfigMenuUpdateApp, _L("Check for Application Updates"), _L("Check for new version of application")); #if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION) @@ -2530,6 +2532,9 @@ wxMenu* GUI_App::get_config_menu() case ConfigMenuWizard: run_wizard(ConfigWizard::RR_USER); break; + case ConfigMenuManageUpdateConf: + manage_updates(); + break; case ConfigMenuUpdateConf: check_updates(true); break; @@ -3387,6 +3392,12 @@ bool GUI_App::config_wizard_startup() return false; } +void GUI_App::manage_updates() +{ + ManageUpdatesDialog dlg(plater()->get_preset_archive_database()); + dlg.ShowModal(); +} + bool GUI_App::check_updates(const bool verbose) { PresetUpdater::UpdateResult updater_result; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index e79fe849da..ae78a07023 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -96,6 +96,7 @@ enum ConfigMenuIDs { ConfigMenuWizard, ConfigMenuSnapshots, ConfigMenuTakeSnapshot, + ConfigMenuManageUpdateConf, ConfigMenuUpdateConf, ConfigMenuUpdateApp, ConfigMenuDesktopIntegration, @@ -441,6 +442,7 @@ private: bool select_language(); bool config_wizard_startup(); + void manage_updates(); // Returns true if the configuration is fine. // Returns true if the configuration is not compatible and the user decided to rather close the slicer instead of reconfiguring. bool check_updates(const bool verbose); diff --git a/src/slic3r/GUI/UpdatesUIManager.cpp b/src/slic3r/GUI/UpdatesUIManager.cpp new file mode 100644 index 0000000000..c5bdce81c1 --- /dev/null +++ b/src/slic3r/GUI/UpdatesUIManager.cpp @@ -0,0 +1,294 @@ +///|/ Copyright (c) Prusa Research 2018 - 2023 Oleksandra Iushchenko @YuSanka +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ +#include "UpdatesUIManager.hpp" +#include "I18N.hpp" +#include "wxExtensions.hpp" +#include "PresetArchiveDatabase.hpp" + +#include "GUI.hpp" +#include "GUI_App.hpp" +#include "MainFrame.hpp" +#include "format.hpp" + +#include "Widgets/CheckBox.hpp" + +namespace fs = boost::filesystem; + +namespace Slic3r { +namespace GUI { + +UIManager::UIManager(wxWindow* parent, PresetArchiveDatabase* pad, int em) : + m_parent(parent) + ,m_pad(pad) + ,m_main_sizer(new wxBoxSizer(wxVERTICAL)) +{ + auto online_label = new wxStaticText(m_parent, wxID_ANY, _L("Online Repositories")); + online_label->SetFont(wxGetApp().bold_font()); + + m_main_sizer->Add(online_label, 0, wxTOP | wxLEFT, 2 * em); + + m_online_sizer = new wxFlexGridSizer(4, 0.75 * em, 1.5 * em); + m_online_sizer->AddGrowableCol(2); + m_online_sizer->AddGrowableCol(3); + m_online_sizer->SetFlexibleDirection(/*wxHORIZONTAL*/wxBOTH); + + m_main_sizer->Add(m_online_sizer, 0, wxALL, 2 * em); + + m_main_sizer->AddSpacer(em); + + auto offline_label = new wxStaticText(m_parent, wxID_ANY, _L("Offline Repositories")); + offline_label->SetFont(wxGetApp().bold_font()); + + m_main_sizer->Add(offline_label, 0, wxTOP | wxLEFT, 2 * em); + + m_offline_sizer = new wxFlexGridSizer(6, 0.75 * em, 1.5 * em); + m_offline_sizer->AddGrowableCol(1); + m_offline_sizer->AddGrowableCol(2); + m_offline_sizer->AddGrowableCol(4); + m_offline_sizer->SetFlexibleDirection(wxHORIZONTAL); + + m_main_sizer->Add(m_offline_sizer, 0, wxALL, 2 * em); + + fill_entries(); + fill_grids(); +} + +void UIManager::fill_entries() +{ + m_online_entries.clear(); + m_offline_entries.clear(); + + const ArchiveRepositoryVector& archs = m_pad->get_archives(); + const std::vector& used_archs = m_pad->get_used_archives(); + + for (const auto& archive : archs) { + const auto& data = archive->get_manifest(); + if (data.local_path.empty()) { + // online repo + bool is_used = std::find(used_archs.begin(), used_archs.end(), data.id) != used_archs.end(); + m_online_entries.push_back({is_used, data.id, data.name, data.description, data.visibility }); + } + else { + // offline repo + bool is_used = std::find(used_archs.begin(), used_archs.end(), data.id) != used_archs.end(); + m_offline_entries.push_back({is_used, data.id, data.name, data.description, data.local_path.filename().string(), fs::exists(data.local_path)}); + } + } + +#if 0 // ysFIXME_delete + // Next code is just for testing + + if (m_offline_entries.empty()) + m_offline_entries = { + {true, "333", "Prusa AFS" , "Prusa FDM Prusa FDM Prusa FDM" , "/path/field/file1.zip", false}, + {false, "444", "Prusa Trilab" , "Prusa sla Prusa sla Prusa sla" , "/path/field/file2.zip", true}, + }; +#endif +} + + +void UIManager::fill_grids() +{ + // clear grids + m_online_sizer->Clear(true); + m_offline_sizer->Clear(true); + + // Fill Online repository + + if (!m_online_entries.empty()) { + + auto add = [this](wxWindow* win) { m_online_sizer->Add(win, 0, wxALIGN_CENTER_VERTICAL); }; + + // header + + for (const wxString& l : std::initializer_list{ _L("Use"), "", _L("Name"), _L("Descrition") }) { + auto text = new wxStaticText(m_parent, wxID_ANY, l); + text->SetFont(wxGetApp().bold_font()); + add(text); + } + + // data + + for (const auto& entry : m_online_entries) + { + auto chb = CheckBox::GetNewWin(m_parent, ""); + CheckBox::SetValue(chb, entry.use); + chb->Bind(wxEVT_CHECKBOX, [this, chb, &entry](wxCommandEvent e) { + if (CheckBox::GetValue(chb)) + m_online_selections.emplace(entry.id); + else + m_online_selections.erase(entry.id); + }); + add(chb); + + if (entry.visibility.empty()) + add(new wxStaticText(m_parent, wxID_ANY, "")); + else { + wxStaticBitmap* bmp = new wxStaticBitmap(m_parent, wxID_ANY, *get_bmp_bundle("info")); + bmp->SetToolTip(from_u8(entry.visibility)); + add(bmp); + } + + add(new wxStaticText(m_parent, wxID_ANY, from_u8(entry.name))); + + add(new wxStaticText(m_parent, wxID_ANY, from_u8(entry.description))); + } + } + + if (!m_offline_entries.empty()) { + + auto add = [this](wxWindow* win) { m_offline_sizer->Add(win, 0, wxALIGN_CENTER_VERTICAL); }; + + // header + + for (const wxString& l : std::initializer_list{ _L("Use"), _L("Name"), _L("Descrition"), "", _L("Sourse file"), "" }) { + auto text = new wxStaticText(m_parent, wxID_ANY, l); + text->SetFont(wxGetApp().bold_font()); + add(text); + } + + // data + + for (const auto& entry : m_offline_entries) + { + auto chb = CheckBox::GetNewWin(m_parent, ""); + CheckBox::SetValue(chb, entry.use); + chb->Bind(wxEVT_CHECKBOX, [this, chb, &entry](wxCommandEvent e) { + if (CheckBox::GetValue(chb)) + m_offline_selections.emplace(entry.id); + else + m_offline_selections.erase(entry.id); + }); + add(chb); + + add(new wxStaticText(m_parent, wxID_ANY, from_u8(entry.name))); + + add(new wxStaticText(m_parent, wxID_ANY, from_u8(entry.description))); + + { + wxStaticBitmap* bmp = new wxStaticBitmap(m_parent, wxID_ANY, *get_bmp_bundle(entry.is_ok ? "tick_mark" : "exclamation")); + bmp->SetToolTip(entry.is_ok ? _L("Exists") : _L("Doesn't exist")); + add(bmp); + } + + add(new wxStaticText(m_parent, wxID_ANY, from_u8(entry.source))); + + { + ScalableButton* btn = new ScalableButton(m_parent, wxID_ANY, "", " " + _L("Remove") + " "); + wxGetApp().UpdateDarkUI(btn, true); + btn->Bind(wxEVT_BUTTON, [this, &entry](wxCommandEvent& event) { remove_offline_repos(entry.id); }); + add(btn); + } + } + } + + { + ScalableButton* btn = new ScalableButton(m_parent, wxID_ANY, "", " " + _L("Load") + "... "); + wxGetApp().UpdateDarkUI(btn, true); + btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { load_offline_repos(); }); + m_offline_sizer->Add(btn); + } + +} + +void UIManager::update() +{ + fill_entries(); + fill_grids(); + + m_main_sizer->Layout(); +} + +void UIManager::remove_offline_repos(const std::string& id) +{ + m_pad->remove_local_archive(id); + + update(); +} + +void UIManager::load_offline_repos() +{ + wxArrayString input_files; + wxFileDialog dialog(m_parent, _L("Choose one or more YIP-files") + ":", + from_u8(wxGetApp().app_config->get_last_dir()), "", + file_wildcards(FT_ZIP), wxFD_OPEN | /*wxFD_MULTIPLE | */wxFD_FILE_MUST_EXIST); + + if (dialog.ShowModal() == wxID_OK) + dialog.GetPaths(input_files); + + if (input_files.IsEmpty()) + return; + + // Iterate through the input files + for (size_t i = 0; i < input_files.size(); ++i) { + std::string input_file = into_u8(input_files.Item(i)); + try { + fs::path input_path = fs::path(input_file); + m_pad->add_local_archive(input_path); + } + catch (fs::filesystem_error const& e) { + std::cerr << e.what() << '\n'; + } + } + + update(); +} + +void UIManager::set_used_archives() +{ + std::vector used_ids; + for (const std::string& id : m_online_selections) + used_ids.push_back(id); + for (const std::string& id : m_offline_selections) + used_ids.push_back(id); + + m_pad->set_used_archives(used_ids); +} + + +ManageUpdatesDialog::ManageUpdatesDialog(PresetArchiveDatabase* pad) + : DPIDialog(static_cast(wxGetApp().mainframe), wxID_ANY, + format_wxstr("%1% - %2%", SLIC3R_APP_NAME, _L("Manage Updates")), + wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +{ + this->SetFont(wxGetApp().normal_font()); + const int em = em_unit(); + + m_manager = std::make_unique(this, pad, em); + + auto sizer = m_manager->get_sizer(); + + wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK | wxCLOSE); + wxGetApp().SetWindowVariantForButton(buttons->GetCancelButton()); + wxGetApp().UpdateDlgDarkUI(this, true); + this->SetEscapeId(wxID_CLOSE); + this->Bind(wxEVT_BUTTON, &ManageUpdatesDialog::onCloseDialog, this, wxID_CLOSE); + this->Bind(wxEVT_BUTTON, &ManageUpdatesDialog::onOkDialog, this, wxID_OK); + sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, em); + + SetSizer(sizer); + sizer->SetSizeHints(this); +} + +void ManageUpdatesDialog::on_dpi_changed(const wxRect &suggested_rect) +{ + SetMinSize(GetBestSize()); + Fit(); + Refresh(); +} + +void ManageUpdatesDialog::onCloseDialog(wxEvent &) +{ + this->EndModal(wxID_CLOSE); +} + +void ManageUpdatesDialog::onOkDialog(wxEvent&) +{ + m_manager->set_used_archives(); + this->EndModal(wxID_CLOSE); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/UpdatesUIManager.hpp b/src/slic3r/GUI/UpdatesUIManager.hpp new file mode 100644 index 0000000000..3ad9ce2289 --- /dev/null +++ b/src/slic3r/GUI/UpdatesUIManager.hpp @@ -0,0 +1,93 @@ +///|/ Copyright (c) Prusa Research 2018 - 2020 Oleksandra Iushchenko @YuSanka +///|/ +#ifndef slic3r_GUI_UpdatesUIManager_hpp_ +#define slic3r_GUI_UpdatesUIManager_hpp_ + +#include "GUI_Utils.hpp" + +class wxWindow; +class wxEvent; +class wxSizer; +class wxFlexGridSizer; + +namespace Slic3r { +namespace GUI { + +class PresetArchiveDatabase; + +class UIManager +{ + struct OnlineEntry { + OnlineEntry(bool use, const std::string &id, const std::string &name, const std::string &description, const std::string &visibility) : + use(use), id(id), name(name), description(description), visibility(visibility) {} + + bool use; + std::string id; + std::string name; + std::string description; + std::string visibility; + }; + + struct OfflineEntry { + OfflineEntry(bool use, const std::string &id, const std::string &name, const std::string &description, const std::string &source, bool is_ok) : + use(use), id(id), name(name), description(description), source(source), is_ok(is_ok) {} + + bool use; + std::string id; + std::string name; + std::string description; + std::string source; + bool is_ok; + }; + + PresetArchiveDatabase* m_pad { nullptr }; + wxWindow* m_parent { nullptr }; + wxSizer* m_main_sizer { nullptr }; + + wxFlexGridSizer* m_online_sizer { nullptr }; + wxFlexGridSizer* m_offline_sizer { nullptr }; + + std::vector m_online_entries; + std::vector m_offline_entries; + + std::set m_online_selections; + std::set m_offline_selections; + + void fill_entries(); + void fill_grids(); + + void update(); + + void remove_offline_repos(const std::string& id); + void load_offline_repos(); + +public: + UIManager() {} + UIManager(wxWindow* parent, PresetArchiveDatabase* pad, int em); + ~UIManager() {} + + wxSizer* get_sizer() { return m_main_sizer; } + void set_used_archives(); +}; + +class ManageUpdatesDialog : public DPIDialog +{ +public: + ManageUpdatesDialog(PresetArchiveDatabase* pad); + ~ManageUpdatesDialog() {} + +protected: + void on_dpi_changed(const wxRect &suggested_rect) override; + +private: + + std::unique_ptr m_manager { nullptr }; + + void onCloseDialog(wxEvent &); + void onOkDialog(wxEvent &); +}; + +} // namespace GUI +} // namespace Slic3r + +#endif