From ae71ebd9085141d6e6646114d5425637ed2791be Mon Sep 17 00:00:00 2001 From: alves Date: Wed, 4 Feb 2026 15:19:52 +0800 Subject: [PATCH] feature add download dialog for download file. --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/BBLStatusBarSend.cpp | 1 + src/slic3r/GUI/DownloadManager.cpp | 87 ++++- src/slic3r/GUI/DownloadManager.hpp | 4 + src/slic3r/GUI/GenericDownloadDialog.cpp | 423 +++++++++++++++++++++++ src/slic3r/GUI/GenericDownloadDialog.hpp | 113 ++++++ 6 files changed, 616 insertions(+), 14 deletions(-) create mode 100644 src/slic3r/GUI/GenericDownloadDialog.cpp create mode 100644 src/slic3r/GUI/GenericDownloadDialog.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 8122c67092..41a2916983 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -506,6 +506,8 @@ set(SLIC3R_GUI_SOURCES GUI/SSWCP.hpp GUI/DownloadManager.cpp GUI/DownloadManager.hpp + GUI/GenericDownloadDialog.cpp + GUI/GenericDownloadDialog.hpp GUI/WebPresetDialog.hpp GUI/WebPresetDialog.cpp GUI/WebSMUserLoginDialog.cpp diff --git a/src/slic3r/GUI/BBLStatusBarSend.cpp b/src/slic3r/GUI/BBLStatusBarSend.cpp index 3976ae7bb1..71ab16ff08 100644 --- a/src/slic3r/GUI/BBLStatusBarSend.cpp +++ b/src/slic3r/GUI/BBLStatusBarSend.cpp @@ -57,6 +57,7 @@ BBLStatusBarSend::BBLStatusBarSend(wxWindow *parent, int id) m_cancelbutton->SetBorderColor(btn_bd_white); m_cancelbutton->SetTextColor(btn_txt_white); m_cancelbutton->SetCornerRadius(m_self->FromDIP(12)); + m_cancelbutton->SetCursor(wxCURSOR_HAND); m_cancelbutton->Bind(wxEVT_BUTTON, [this](wxCommandEvent &evt) { m_was_cancelled = true; diff --git a/src/slic3r/GUI/DownloadManager.cpp b/src/slic3r/GUI/DownloadManager.cpp index 345ebf8dd0..72faffd178 100644 --- a/src/slic3r/GUI/DownloadManager.cpp +++ b/src/slic3r/GUI/DownloadManager.cpp @@ -3,10 +3,37 @@ #include #include #include +#include #include namespace Slic3r { namespace GUI { +// ============================================================================ +// Helper Functions +// ============================================================================ + +std::string DownloadManager::get_unique_file_path(const boost::filesystem::path& file_path) +{ + if (!boost::filesystem::exists(file_path)) { + return file_path.string(); + } + + boost::filesystem::path parent_dir = file_path.parent_path(); + std::string filename = file_path.filename().string(); + std::string extension = file_path.extension().string(); + std::string name_without_ext = filename.substr(0, filename.size() - extension.size()); + + size_t version = 1; + boost::filesystem::path unique_path; + do { + std::string new_filename = name_without_ext + "(" + std::to_string(version) + ")" + extension; + unique_path = parent_dir / new_filename; + version++; + } while (boost::filesystem::exists(unique_path) && version < 10000); // Safety limit + + return unique_path.string(); +} + // ============================================================================ // WCP Download Interface (for Web-to-PC communication) // ============================================================================ @@ -22,11 +49,16 @@ size_t DownloadManager::start_wcp_download(const std::string& file_url, boost::filesystem::path dest_folder(downloadPath); boost::filesystem::create_directories(dest_folder); boost::filesystem::path dest_file = dest_folder / file_name; - std::string dest_path = dest_file.string(); + + // Generate unique file path if file already exists + std::string dest_path = get_unique_file_path(dest_file); + + // Update file_name if it was changed due to duplicate + std::string actual_file_name = boost::filesystem::path(dest_path).filename().string(); auto task = std::make_shared(task_id, file_url, - file_name, + actual_file_name, dest_path, wcp_instance, use_original_event_id); @@ -51,11 +83,14 @@ size_t DownloadManager::start_internal_download(const std::string& file_url, boost::filesystem::path dest_file_path(dest_path); boost::filesystem::create_directories(dest_file_path.parent_path()); + + // Generate unique file path if file already exists + std::string unique_dest_path = get_unique_file_path(dest_file_path); auto task = std::make_shared(task_id, file_url, file_name, - dest_path, + unique_dest_path, std::move(callbacks)); task->state = DownloadTaskState::Downloading; @@ -75,7 +110,9 @@ size_t DownloadManager::start_internal_download(const std::string& file_url, boost::filesystem::create_directories(dest_folder); boost::filesystem::path dest_file = dest_folder / file_name; - std::string dest_path = dest_file.string(); + + // Generate unique file path if file already exists + std::string dest_path = get_unique_file_path(dest_file); return start_internal_download(file_url, file_name, dest_path, std::move(callbacks)); } @@ -133,12 +170,22 @@ void DownloadManager::start_download_impl(std::shared_ptr task) { // Save file boost::nowide::ofstream file(task->dest_path, std::ios::binary); if (!file.is_open()) { - send_error_update(task, "Failed to open file for writing"); + std::string error_msg = "Failed to open file for writing: " + task->dest_path; + BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg; + send_error_update(task, error_msg); cleanup_task(task->task_id); return; } file.write(body.c_str(), body.size()); + if (file.fail()) { + std::string error_msg = "Failed to write file: " + task->dest_path; + BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg << ", body size: " << body.size(); + file.close(); + send_error_update(task, error_msg); + cleanup_task(task->task_id); + return; + } file.close(); task->state = DownloadTaskState::Completed; @@ -146,7 +193,9 @@ void DownloadManager::start_download_impl(std::shared_ptr task) { send_complete_update(task, task->dest_path); cleanup_task(task->task_id); } catch (std::exception& e) { - send_error_update(task, e.what()); + std::string error_msg = std::string("File write exception: ") + e.what(); + BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg << ", file: " << task->dest_path; + send_error_update(task, error_msg); cleanup_task(task->task_id); } }); @@ -155,6 +204,11 @@ void DownloadManager::start_download_impl(std::shared_ptr task) { // Step 4: Set error callback http.on_error([this, task](std::string body, std::string error, unsigned status) { wxGetApp().CallAfter([this, task, error, status]() { + std::string error_msg = boost::str(boost::format("HTTP error: %1% (status: %2%)") % error % status); + BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg + << ", URL: " << task->file_url + << ", file: " << task->file_name + << ", dest: " << task->dest_path; task->state = DownloadTaskState::Error; task->error_message = error; send_error_update(task, error); @@ -166,6 +220,11 @@ void DownloadManager::start_download_impl(std::shared_ptr task) { task->http_object = http.perform(); } catch (std::exception& e) { + std::string error_msg = std::string("Download exception: ") + e.what(); + BOOST_LOG_TRIVIAL(error) << "DownloadManager: " << error_msg + << ", URL: " << task->file_url + << ", file: " << task->file_name + << ", dest: " << task->dest_path; task->state = DownloadTaskState::Error; task->error_message = e.what(); send_error_update(task, e.what()); @@ -197,14 +256,14 @@ bool DownloadManager::cancel_download(size_t task_id) { if (task->is_wcp_download()) { wcp_to_destroy = task->wcp_instance.lock(); } else { - // For internal downloads, call error callback if available - if (task->callbacks.on_error) { - wxGetApp().CallAfter([task]() { - if (task->callbacks.on_error) { - task->callbacks.on_error(task->task_id, "Download canceled"); - } - }); - } + // For internal downloads, don't call error callback if task is being canceled + // during destruction (e.g., when dialog is closing). The callback may reference + // a destroyed dialog object, causing a crash. + // Note: We clear the callbacks before cleanup to prevent any delayed callbacks + // from accessing destroyed objects. + task->callbacks.on_error = nullptr; + task->callbacks.on_progress = nullptr; + task->callbacks.on_complete = nullptr; } cleanup_task(task_id); diff --git a/src/slic3r/GUI/DownloadManager.hpp b/src/slic3r/GUI/DownloadManager.hpp index c61ad574e6..22e993ae0f 100644 --- a/src/slic3r/GUI/DownloadManager.hpp +++ b/src/slic3r/GUI/DownloadManager.hpp @@ -198,6 +198,10 @@ private: // Clean up completed task void cleanup_task(size_t task_id); + + // Generate unique file path if file already exists + // Returns path like "file(1).zip", "file(2).zip" etc. + static std::string get_unique_file_path(const boost::filesystem::path& file_path); }; }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/GenericDownloadDialog.cpp b/src/slic3r/GUI/GenericDownloadDialog.cpp new file mode 100644 index 0000000000..4607027942 --- /dev/null +++ b/src/slic3r/GUI/GenericDownloadDialog.cpp @@ -0,0 +1,423 @@ +#include "GenericDownloadDialog.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/Utils.hpp" +#include "GUI.hpp" +#include "I18N.hpp" +#include "wxExtensions.hpp" +#include "slic3r/GUI/MainFrame.hpp" +#include "GUI_App.hpp" +#include "slic3r/GUI/DownloadManager.hpp" + +namespace Slic3r { +namespace GUI { + +GenericDownloadDialog::GenericDownloadDialog(wxString title, + const std::string& file_url, + const std::string& file_name, + const std::string& dest_path, + wxWindow* parent) + : DPIDialog(parent ? parent : static_cast(wxGetApp().mainframe), + wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX) + , m_title(title) + , m_file_url(file_url) + , m_file_name(file_name) + , m_dest_path(dest_path) +{ + std::string icon_path = (boost::format("%1%/images/Snapmaker_OrcaTitle.ico") % resources_dir()).str(); + SetIcon(wxIcon(encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO)); + + SetBackgroundColour(*wxWHITE); + setup_ui(); + + Bind(wxEVT_CLOSE_WINDOW, &GenericDownloadDialog::on_close, this); + wxGetApp().UpdateDlgDarkUI(this); + +} + +GenericDownloadDialog::~GenericDownloadDialog() +{ + m_is_destroying = true; + m_task_id = 0; +} + +void GenericDownloadDialog::setup_ui() +{ + wxBoxSizer *m_sizer_main = new wxBoxSizer(wxVERTICAL); + auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1)); + m_line_top->SetBackgroundColour(wxColour(166, 169, 170)); + m_sizer_main->Add(m_line_top, 0, wxEXPAND, 0); + + m_simplebook_status = new wxSimplebook(this); + m_simplebook_status->SetSize(wxSize(FromDIP(420), FromDIP(100))); + m_simplebook_status->SetMinSize(wxSize(FromDIP(420), FromDIP(100))); + m_simplebook_status->SetMaxSize(wxSize(FromDIP(420), FromDIP(250))); + + // Progress page + m_status_bar = std::make_shared(m_simplebook_status); + m_panel_download = m_status_bar->get_panel(); + m_panel_download->SetSize(wxSize(FromDIP(400), FromDIP(70))); + m_panel_download->SetMinSize(wxSize(FromDIP(400), FromDIP(70))); + m_panel_download->SetMaxSize(wxSize(FromDIP(400), FromDIP(70))); + + // Complete page + m_panel_complete = new wxPanel(m_simplebook_status, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + wxBoxSizer* sizer_complete = new wxBoxSizer(wxVERTICAL); + + m_complete_text = new wxStaticText(m_panel_complete, wxID_ANY, _L("Download completed successfully!"), + wxDefaultPosition, wxDefaultSize, 0); + m_complete_text->SetForegroundColour(*wxBLACK); + m_complete_text->Wrap(FromDIP(360)); + sizer_complete->Add(m_complete_text, 0, wxALIGN_CENTER | wxALL, 5); + + StateColor btn_close_bg(std::pair(wxColour(0x90, 0x90, 0x90), StateColor::Disabled), + std::pair(wxColour(206, 206, 206), StateColor::Pressed), + std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(wxColour(231, 231, 231), StateColor::Normal)); + + StateColor btn_close_bd(std::pair(wxColour(255, 255, 254), StateColor::Disabled), + std::pair(wxColour(38, 46, 48), StateColor::Enabled)); + + StateColor btn_close_txt(std::pair(wxColour("#FFFFFE"), StateColor::Disabled), + std::pair(wxColour(36, 36, 36), StateColor::Normal)); + + m_close_button = new Button(m_panel_complete, _L("Close")); + m_close_button->SetSize(wxSize(FromDIP(80), FromDIP(28))); + m_close_button->SetMinSize(wxSize(FromDIP(80), FromDIP(28))); + m_close_button->SetMaxSize(wxSize(FromDIP(80), FromDIP(28))); + + m_close_button->SetBackgroundColour(*wxWHITE); + m_close_button->SetBackgroundColor(btn_close_bg); + m_close_button->SetBorderColor(btn_close_bd); + m_close_button->SetTextColor(btn_close_txt); + m_close_button->SetCornerRadius(FromDIP(12)); + m_close_button->SetCursor(wxCURSOR_HAND); + m_close_button->Bind(wxEVT_BUTTON, &GenericDownloadDialog::on_close_clicked, this); + + sizer_complete->Add(m_close_button, 0, wxALIGN_CENTER | wxALL, 5); + + m_panel_complete->SetSizer(sizer_complete); + m_panel_complete->Layout(); + sizer_complete->Fit(m_panel_complete); + + // Error page + m_panel_error = new wxPanel(m_simplebook_status, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + wxBoxSizer* sizer_error = new wxBoxSizer(wxVERTICAL); + + // Simple label for error message display + m_error_text = new wxStaticText(m_panel_error, wxID_ANY, wxEmptyString, + wxDefaultPosition, wxSize(FromDIP(380), -1), + wxALIGN_LEFT | wxST_ELLIPSIZE_END); + m_error_text->SetForegroundColour(*wxBLACK); + m_error_text->Wrap(FromDIP(380)); + sizer_error->Add(m_error_text, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, FromDIP(10)); + + // Button sizer aligned to right + wxBoxSizer* sizer_buttons = new wxBoxSizer(wxHORIZONTAL); + sizer_buttons->AddStretchSpacer(); + + StateColor btn_retry_bg(std::pair(wxColour(255, 255, 255), StateColor::Disabled), + std::pair(wxColour(206, 206, 206), StateColor::Pressed), + std::pair(wxColour(23, 99, 226), StateColor::Hovered), // Same as Normal + std::pair(wxColour(23, 99, 226), StateColor::Normal)); + + // Set border color to same as background to avoid corner color issues + StateColor btn_retry_bd(std::pair(wxColour(255, 255, 255), StateColor::Disabled), + std::pair(wxColour(23, 99, 226), StateColor::Enabled)); // Same as background + + StateColor btn_retry_txt(std::pair(wxColour("#FFFFFE"), StateColor::Disabled), + std::pair(wxColour(255, 255, 255), StateColor::Normal)); + + m_retry_button = new Button(m_panel_error, _L("Retry")); + m_retry_button->SetSize(wxSize(FromDIP(80), FromDIP(28))); + m_retry_button->SetMinSize(wxSize(FromDIP(80), FromDIP(28))); + m_retry_button->SetMaxSize(wxSize(FromDIP(80), FromDIP(28))); + // Set window background color to white to ensure rounded corners are white + m_retry_button->SetBackgroundColour(*wxWHITE); + m_retry_button->SetBackgroundColor(btn_retry_bg); + m_retry_button->SetBorderColor(btn_retry_bg); + m_retry_button->SetTextColor(btn_retry_txt); + m_retry_button->SetCornerRadius(FromDIP(12)); + m_retry_button->SetCursor(wxCURSOR_HAND); + m_retry_button->Bind(wxEVT_BUTTON, &GenericDownloadDialog::on_retry_clicked, this); + + // Setup StateColor for determine button (gray style) + StateColor btn_determine_bg(std::pair(wxColour(255, 255, 255), StateColor::Disabled), + std::pair(wxColour(206, 206, 206), StateColor::Pressed), + std::pair(wxColour(238, 238, 238), StateColor::Hovered), + std::pair(wxColour(231, 231, 231), StateColor::Normal)); + + StateColor btn_determine_bd(std::pair(wxColour(255, 255, 255), StateColor::Disabled), + std::pair(wxColour(38, 46, 48), StateColor::Enabled)); + + StateColor btn_determine_txt(std::pair(wxColour("#FFFFFE"), StateColor::Disabled), + std::pair(wxColour(36, 36, 36), StateColor::Normal)); + + m_error_close_button = new Button(m_panel_error, _L("Determine")); + m_error_close_button->SetSize(wxSize(FromDIP(80), FromDIP(28))); + m_error_close_button->SetMinSize(wxSize(FromDIP(80), FromDIP(28))); + m_error_close_button->SetMaxSize(wxSize(FromDIP(80), FromDIP(28))); + // Set window background color to white to ensure rounded corners are white + m_error_close_button->SetBackgroundColour(*wxWHITE); + m_error_close_button->SetBackgroundColor(btn_determine_bg); + m_error_close_button->SetBorderColor(btn_determine_bg); + m_error_close_button->SetTextColor(btn_determine_txt); + m_error_close_button->SetCornerRadius(FromDIP(12)); + m_error_close_button->SetCursor(wxCURSOR_HAND); + m_error_close_button->Bind(wxEVT_BUTTON, &GenericDownloadDialog::on_close_clicked, this); + sizer_buttons->Add(m_error_close_button, 0, 0); + sizer_buttons->AddSpacer(FromDIP(6)); + sizer_buttons->Add(m_retry_button, 0, 0); + + sizer_error->AddSpacer(FromDIP(40)); + sizer_error->Add(sizer_buttons, 0, wxALIGN_RIGHT | wxRIGHT, FromDIP(24)); + + + m_panel_error->SetSizer(sizer_error); + m_panel_error->Layout(); + sizer_error->Fit(m_panel_error); + + + m_sizer_main->Add(m_simplebook_status, 0, wxALL, FromDIP(16)); + + m_simplebook_status->AddPage(m_panel_download, wxEmptyString, true); + m_simplebook_status->AddPage(m_panel_complete, wxEmptyString, false); + m_simplebook_status->AddPage(m_panel_error, wxEmptyString, false); + + SetSizer(m_sizer_main); + Layout(); + Fit(); + CentreOnParent(); +} + +wxString GenericDownloadDialog::format_text(wxStaticText* st, wxString str, int warp) +{ + if (wxGetApp().app_config->get("language") != "zh_CN") { + return str; + } + + wxString out_txt = str; + wxString count_txt = ""; + + for (int i = 0; i < str.length(); i++) { + auto text_size = st->GetTextExtent(count_txt); + if (text_size.x < warp) { + count_txt += str[i]; + } else { + out_txt.insert(i - 1, '\n'); + count_txt = ""; + } + } + return out_txt; +} + +void GenericDownloadDialog::start_download() +{ + show_progress_page(); + m_status_bar->set_progress(0); + m_status_bar->set_status_text(_L("Preparing download...")); + m_status_bar->change_button_label(_L("Cancel")); + m_status_bar->set_cancel_callback_fina([this]() { + if (m_task_id > 0) { + DownloadManager::getInstance().cancel_download(m_task_id); + m_task_id = 0; // Mark as canceled to prevent duplicate cancellation + } + }); + + // Create download callbacks + DownloadCallbacks callbacks; + callbacks.on_progress = [this](size_t task_id, int percent, size_t downloaded, size_t total) { + on_download_progress(task_id, percent, downloaded, total); + }; + callbacks.on_complete = [this](size_t task_id, const std::string& file_path) { + on_download_complete(task_id, file_path); + }; + callbacks.on_error = [this](size_t task_id, const std::string& error) { + on_download_error(task_id, error); + }; + + // Start download + if (m_dest_path.empty()) { + m_task_id = DownloadManager::getInstance().start_internal_download( + m_file_url, m_file_name, std::move(callbacks)); + } else { + m_task_id = DownloadManager::getInstance().start_internal_download( + m_file_url, m_file_name, m_dest_path, std::move(callbacks)); + } +} + +int GenericDownloadDialog::ShowModal() +{ + start_download(); + return DPIDialog::ShowModal(); +} + +void GenericDownloadDialog::on_download_progress(size_t task_id, int percent, size_t downloaded, size_t total) +{ + wxGetApp().CallAfter([this, percent, downloaded, total]() { + // Check if dialog is being destroyed + if (m_is_destroying || IsBeingDeleted()) { + return; + } + + update_progress(percent); + + // Format status text + wxString status_text; + if (total > 0) { + double downloaded_mb = downloaded / (1024.0 * 1024.0); + double total_mb = total / (1024.0 * 1024.0); + status_text = wxString::Format(_L("Downloading: %.1f MB / %.1f MB (%d%%)"), + downloaded_mb, total_mb, percent); + } else { + double downloaded_mb = downloaded / (1024.0 * 1024.0); + status_text = wxString::Format(_L("Downloading: %.1f MB..."), downloaded_mb); + } + m_status_bar->set_status_text(status_text); + + // Call user callback if set + if (m_on_progress) { + m_on_progress(m_task_id, percent, downloaded, total); + } + }); +} + +void GenericDownloadDialog::on_download_complete(size_t task_id, const std::string& file_path) +{ + wxGetApp().CallAfter([this, file_path]() { + // Check if dialog is being destroyed + if (m_is_destroying || IsBeingDeleted()) { + return; + } + + m_download_success = true; + m_file_path = file_path; + show_complete_page(); + + // Mark task as completed - no need to cancel it + m_task_id = 0; + + // Call user callback if set + if (m_on_complete) { + m_on_complete(m_task_id, file_path); + } + }); +} + +void GenericDownloadDialog::on_download_error(size_t task_id, const std::string& error) +{ + // Log detailed error information for debugging + BOOST_LOG_TRIVIAL(error) << boost::format("GenericDownloadDialog: Download failed for file '%1%' from URL '%2%'. Error: %3%") + % m_file_name % m_file_url % error; + + wxGetApp().CallAfter([this, error]() { + // Check if dialog is being destroyed + if (m_is_destroying || IsBeingDeleted()) { + return; + } + + m_download_success = false; + m_error_message = error; + show_error_page(error); + + // Mark task as completed (failed) - no need to cancel it + m_task_id = 0; + + // Call user callback if set + if (m_on_error) { + m_on_error(m_task_id, error); + } + }); +} + +void GenericDownloadDialog::on_retry_clicked(wxCommandEvent& event) +{ + if (m_on_retry) { + m_on_retry(); + } + start_download(); + event.Skip(); +} + +void GenericDownloadDialog::on_close_clicked(wxCommandEvent& event) +{ + if (m_task_id > 0) { + DownloadManager::getInstance().cancel_download(m_task_id); + m_task_id = 0; + } + EndModal(m_download_success ? wxID_OK : wxID_CANCEL); + event.Skip(); +} + +void GenericDownloadDialog::on_close(wxCloseEvent& event) +{ + if (m_task_id > 0) { + DownloadManager::getInstance().cancel_download(m_task_id); + m_task_id = 0; + } + event.Skip(); +} + +void GenericDownloadDialog::show_progress_page() +{ + m_simplebook_status->SetSelection(0); + m_status_bar->set_progress(0); + m_status_bar->show_cancel_button(); +} + +void GenericDownloadDialog::show_complete_page() +{ + m_simplebook_status->SetSelection(1); + m_status_bar->hide_cancel_button(); +} + +void GenericDownloadDialog::show_error_page(const std::string& error_msg) +{ + m_simplebook_status->SetSelection(2); + + // Display simple error message: filename + "Download failed" + wxString filename = wxString::FromUTF8(m_file_name.c_str()); + wxString error_text = filename + " - Download failed"; + + // Set error text in simple label + m_error_text->SetLabel(error_text); + m_error_text->Wrap(FromDIP(380)); + + m_panel_error->Layout(); + m_simplebook_status->Layout(); + Layout(); + Fit(); + m_status_bar->hide_cancel_button(); +} + +void GenericDownloadDialog::update_progress(int percent, const wxString& status_text) +{ + m_status_bar->set_progress(percent); + if (!status_text.IsEmpty()) { + m_status_bar->set_status_text(status_text); + } +} + +void GenericDownloadDialog::on_dpi_changed(const wxRect &suggested_rect) +{ + // Handle DPI changes if needed +} + +}} // namespace Slic3r::GUI + diff --git a/src/slic3r/GUI/GenericDownloadDialog.hpp b/src/slic3r/GUI/GenericDownloadDialog.hpp new file mode 100644 index 0000000000..8ff71587c1 --- /dev/null +++ b/src/slic3r/GUI/GenericDownloadDialog.hpp @@ -0,0 +1,113 @@ +#ifndef slic3r_GenericDownloadDialog_hpp_ +#define slic3r_GenericDownloadDialog_hpp_ + +#include +#include +#include +#include + +#include "GUI_Utils.hpp" +#include +#include +#include "BBLStatusBar.hpp" +#include "BBLStatusBarSend.hpp" +#include "Jobs/Worker.hpp" +#include "slic3r/GUI/DownloadManager.hpp" +#include "Widgets/Button.hpp" + +class wxBoxSizer; +class wxPanel; +class wxStaticText; +class wxHyperlinkCtrl; + +namespace Slic3r { +namespace GUI { + +// Generic download dialog for custom download tasks with progress display +class GenericDownloadDialog : public DPIDialog +{ +public: + // Callback types + using DownloadCallback = std::function; + using CompleteCallback = std::function; + using ErrorCallback = std::function; + using RetryCallback = std::function; + + GenericDownloadDialog(wxString title, + const std::string& file_url, + const std::string& file_name, + const std::string& dest_path = "", + wxWindow* parent = nullptr); + ~GenericDownloadDialog(); + + // Start download + void start_download(); + + // Set callbacks (optional) + void set_on_progress(DownloadCallback callback) { m_on_progress = callback; } + void set_on_complete(CompleteCallback callback) { m_on_complete = callback; } + void set_on_error(ErrorCallback callback) { m_on_error = callback; } + void set_on_retry(RetryCallback callback) { m_on_retry = callback; } + + // Get download result + bool is_success() const { return m_download_success; } + std::string get_file_path() const { return m_file_path; } + std::string get_error_message() const { return m_error_message; } + + // Show modal and return result + int ShowModal() override; + +protected: + void on_close(wxCloseEvent& event); + void on_dpi_changed(const wxRect &suggested_rect) override; + wxString format_text(wxStaticText* st, wxString str, int warp); + + // Event handlers + void on_download_progress(size_t task_id, int percent, size_t downloaded, size_t total); + void on_download_complete(size_t task_id, const std::string& file_path); + void on_download_error(size_t task_id, const std::string& error); + void on_retry_clicked(wxCommandEvent& event); + void on_close_clicked(wxCommandEvent& event); + +private: + void setup_ui(); + void show_progress_page(); + void show_complete_page(); + void show_error_page(const std::string& error_msg); + void update_progress(int percent, const wxString& status_text = ""); + + wxString m_title; + std::string m_file_url; + std::string m_file_name; + std::string m_dest_path; + size_t m_task_id{0}; + bool m_download_success{false}; + std::string m_file_path; + std::string m_error_message; + + // Callbacks + DownloadCallback m_on_progress; + CompleteCallback m_on_complete; + ErrorCallback m_on_error; + RetryCallback m_on_retry; + + // UI components + wxSimplebook* m_simplebook_status{nullptr}; + std::shared_ptr m_status_bar; + wxPanel* m_panel_download{nullptr}; + wxPanel* m_panel_complete{nullptr}; + wxPanel* m_panel_error{nullptr}; + + wxStaticText* m_complete_text{nullptr}; + wxStaticText* m_error_text{nullptr}; + Button* m_retry_button{nullptr}; + Button* m_close_button{nullptr}; + Button* m_error_close_button{nullptr}; + + std::atomic m_is_destroying{false}; +}; + +}} // namespace Slic3r::GUI + +#endif // slic3r_GenericDownloadDialog_hpp_ +