From a98efb954c966e66d4c5bf9e44b74e66831cdb66 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 10 Jun 2022 10:43:53 +0200 Subject: [PATCH] File Archive Dialog (first prototype) --- src/downloader/DownloaderApp.cpp | 3 +- src/downloader/InstanceSend.cpp | 5 +- src/slic3r/GUI/FileArchiveDialog.cpp | 276 ++++++++++++++++++++++++++- src/slic3r/GUI/FileArchiveDialog.hpp | 95 ++++++++- src/slic3r/GUI/Plater.cpp | 107 ++++++----- src/slic3r/GUI/Plater.hpp | 2 +- 6 files changed, 424 insertions(+), 64 deletions(-) diff --git a/src/downloader/DownloaderApp.cpp b/src/downloader/DownloaderApp.cpp index bfdf36b920..bdcc743afa 100644 --- a/src/downloader/DownloaderApp.cpp +++ b/src/downloader/DownloaderApp.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -155,7 +156,7 @@ DownloadFrame::DownloadFrame(const wxString& title, const wxPoint& pos, const wx data_sizer->Add(m_dataview, 1, wxEXPAND | wxBOTTOM); - m_dest_folder = boost::filesystem::path("C:\\Users\\User\\Downloads"); + m_dest_folder = boost::dll::program_location().parent_path();//boost::filesystem::path("C:\\Users\\User\\Downloads"); //m_log_label = new wxStaticText(this, wxID_ANY, "Log:"); diff --git a/src/downloader/InstanceSend.cpp b/src/downloader/InstanceSend.cpp index b23f6c1a50..b93490fe3f 100644 --- a/src/downloader/InstanceSend.cpp +++ b/src/downloader/InstanceSend.cpp @@ -6,6 +6,7 @@ #endif //WIN32 #include +#include #include namespace Downloader { @@ -229,7 +230,9 @@ bool SlicerSend::start_with_path(const wxString& path) const // "C:\\Users\\User\\Downloads\\PrusaSlicer-2.4.2+win64-202204251110\\prusa-slicer.exe " std::string escaped = escape_strings_cstyle({ boost::nowide::narrow(path) }); //return execute_command(boost::nowide::widen(escaped)); - return execute_command("C:\\Users\\User\\Downloads\\PrusaSlicer-2.4.2+win64-202204251110\\prusa-slicer.exe " + boost::nowide::widen(escaped)); + std::string binary = (boost::dll::program_location().parent_path() / "prusa-slicer.exe").string() + " "; + //return execute_command("C:\\Users\\User\\Downloads\\PrusaSlicer-2.4.2+win64-202204251110\\prusa-slicer.exe " + boost::nowide::widen(escaped)); + return execute_command(boost::nowide::widen(binary) + boost::nowide::widen(escaped)); } bool SlicerSend::start_or_send(const wxString& path) const diff --git a/src/slic3r/GUI/FileArchiveDialog.cpp b/src/slic3r/GUI/FileArchiveDialog.cpp index 5c7b747da4..522e83daed 100644 --- a/src/slic3r/GUI/FileArchiveDialog.cpp +++ b/src/slic3r/GUI/FileArchiveDialog.cpp @@ -1,22 +1,294 @@ #include "FileArchiveDialog.hpp" +//#include "UnsavedChangesDialog.hpp" #include "I18N.hpp" #include "GUI_App.hpp" #include "GUI.hpp" #include "MainFrame.hpp" +#include "ExtraRenderers.hpp" namespace Slic3r { namespace GUI { -FileArchiveDialog::FileArchiveDialog(const std::vector& files) +ArchiveViewModel::ArchiveViewModel(wxWindow* parent) + :m_parent(parent) +{} +ArchiveViewModel::~ArchiveViewModel() +{} +//wxDataViewItem ArchiveViewModel::AddFolder(wxDataViewItem& parent, wxString name) +//{ +// // TODO +// return wxDataViewItem(nullptr); +//} +//wxDataViewItem ArchiveViewModel::AddFile(wxDataViewItem& parent, wxString name) +//{ +// ArchiveViewNode* node = new ArchiveViewNode(name); +// +// wxDataViewItem child((void*)node); +// +// ItemAdded(parent, child); +// m_ctrl->Expand(parent); +// return child; +//} + +ArchiveViewNode* ArchiveViewModel::AddFile(ArchiveViewNode* parent, wxString name) +{ + ArchiveViewNode* node = new ArchiveViewNode(name); + if (parent != nullptr) { + parent->get_children().push_back(node); + node->set_parent(parent); + parent->set_is_folder(true); + } else { + m_top_children.push_back(node); + } + + wxDataViewItem child((void*)node); + wxDataViewItem parent_item = wxDataViewItem((void*)parent); + ItemAdded(parent_item, child); + if (parent) + m_ctrl->Expand(parent_item); + return node; +} + +wxString ArchiveViewModel::GetColumnType(unsigned int col) const +{ + if (col == 0) + return "bool"; + return "string";//"DataViewBitmapText"; +} + +void ArchiveViewModel::Rescale() +{ + // TODO +} + +void ArchiveViewModel::Delete(const wxDataViewItem& item) +{ + // TODO + assert(item.IsOk()); + ArchiveViewNode* node = static_cast(item.GetID()); + assert(node->get_parent() != nullptr); + for (ArchiveViewNode* child : node->get_children()) + { + Delete(wxDataViewItem((void*)child)); + } + delete [] node; +} +void ArchiveViewModel::Clear() +{ + // TODO +} + +wxDataViewItem ArchiveViewModel::GetParent(const wxDataViewItem& item) const +{ + assert(item.IsOk()); + ArchiveViewNode* node = static_cast(item.GetID()); + return wxDataViewItem(node->get_parent()); +} +unsigned int ArchiveViewModel::GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const +{ + if (!parent.IsOk()) { + for (ArchiveViewNode* child : m_top_children) { + array.push_back(wxDataViewItem((void*)child)); + } + return m_top_children.size(); + } + + ArchiveViewNode* node = static_cast(parent.GetID()); + for (ArchiveViewNode* child : node->get_children()) { + array.push_back(wxDataViewItem((void*)child)); + } + return node->get_children().size(); +} + +void ArchiveViewModel::GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const +{ + assert(item.IsOk()); + ArchiveViewNode* node = static_cast(item.GetID()); + if (col == 0) { + variant = node->get_toggle(); + } else { + variant = node->get_name(); + } +} + +void ArchiveViewModel::untoggle_folders(const wxDataViewItem& item) +{ + assert(item.IsOk()); + ArchiveViewNode* node = static_cast(item.GetID()); + node->set_toggle(false); + if (node->get_parent()) + untoggle_folders(wxDataViewItem((void*)node->get_parent())); +} + +bool ArchiveViewModel::SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) +{ + assert(item.IsOk()); + ArchiveViewNode* node = static_cast(item.GetID()); + if (col == 0) { + node->set_toggle(variant.GetBool()); + // if folder recursivelly check all children + for (ArchiveViewNode* child : node->get_children()) { + SetValue(variant, wxDataViewItem((void*)child), col); + } + if(!variant.GetBool() && node->get_parent()) + untoggle_folders(wxDataViewItem((void*)node->get_parent())); + } else { + node->set_name(variant.GetString()); + } + m_parent->Refresh(); + return true; +} +bool ArchiveViewModel::IsEnabled(const wxDataViewItem& item, unsigned int col) const +{ + return true; +} +bool ArchiveViewModel::IsContainer(const wxDataViewItem& item) const +{ + if(!item.IsOk()) + return true; + ArchiveViewNode* node = static_cast(item.GetID()); + return node->is_container(); +} + +ArchiveViewCtrl::ArchiveViewCtrl(wxWindow* parent, wxSize size) + : wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, size, wxDV_VARIABLE_LINE_HEIGHT | wxDV_ROW_LINES +#ifdef _WIN32 + | wxBORDER_SIMPLE +#endif + ) + //, m_em_unit(em_unit(parent)) +{ + wxGetApp().UpdateDVCDarkUI(this); + + m_model = new ArchiveViewModel(parent); + this->AssociateModel(m_model); + m_model->SetAssociatedControl(this); +} + +ArchiveViewCtrl::~ArchiveViewCtrl() +{ + if (m_model) { + m_model->Clear(); + m_model->DecRef(); + } +} + +FileArchiveDialog::FileArchiveDialog( mz_zip_archive* archive, std::vector& selected_paths) : DPIDialog(static_cast(wxGetApp().mainframe), wxID_ANY, _(L("Archive preview")), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 40 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX) + , m_selected_paths (selected_paths) { -} + int em = em_unit(); + + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + + m_avc = new ArchiveViewCtrl(this, wxSize(60 * em, 30 * em)); + m_avc->AppendToggleColumn(L"\u2714", 0, wxDATAVIEW_CELL_ACTIVATABLE, 6 * em); + m_avc->AppendTextColumn("filename", 1); + + + std::vector stack; + + std::function&, size_t)> reduce_stack = [] (std::vector& stack, size_t size) { + if (size == 0) { + stack.clear(); + return; + } + while (stack.size() > size) + stack.pop_back(); + }; + // recursively stores whole structure of file onto function stack and synchoronize with stack object. + std::function&)> adjust_stack = [&adjust_stack, &reduce_stack, &avc = m_avc](boost::filesystem::path& file, std::vector& stack)->size_t { + size_t struct_size = file.has_parent_path() ? adjust_stack(file.parent_path(), stack) : 0; + + if (stack.size() > struct_size && (file.has_extension() || file.filename().string() != stack[struct_size]->get_name())) + { + reduce_stack(stack, struct_size); + } + if (!file.has_extension() && stack.size() == struct_size) + stack.push_back(avc->get_model()->AddFile(stack.empty() ? nullptr : stack.back(), boost::nowide::widen(file.filename().string()))); + return struct_size + 1; + }; + + + mz_uint num_entries = mz_zip_reader_get_num_files(archive); + mz_zip_archive_file_stat stat; + for (mz_uint i = 0; i < num_entries; ++i) { + if (mz_zip_reader_file_stat(archive, i, &stat)) { + + std::string name(stat.m_filename); + //std::replace(name.begin(), name.end(), '\\', '/'); + boost::filesystem::path path(name); + if (!path.has_extension()) + continue; + ArchiveViewNode* parent = nullptr; + + adjust_stack(path, stack); + if (!stack.empty()) + parent = stack.back(); + + m_avc->get_model()->AddFile(parent, boost::nowide::widen(path.filename().string()))->set_fullpath(std::move(path)); + } + } + + wxBoxSizer* btn_sizer = new wxBoxSizer(wxHORIZONTAL); + wxButton* btn_run = new wxButton(this, wxID_OK, "Open"); + btn_run->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_open_button(); }); + btn_sizer->Add(btn_run, 0, wxLEFT | wxRIGHT); + + //ScalableButton* cancel_btn = new ScalableButton(this, wxID_CANCEL, "cross", _L("Cancel"), wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true, 24); + //btn_sizer->Add(cancel_btn, 1, wxLEFT | wxRIGHT, 5); + //cancel_btn->Bind(wxEVT_BUTTON, [this](wxEvent&) { this->EndModal(wxID_CANCEL); }); + + topSizer->Add(m_avc, 1, wxEXPAND | wxALL, 10); + topSizer->Add(btn_sizer, 0, wxEXPAND | wxALL, 10); + this->SetMinSize(wxSize(80 * em, 30 * em)); + this->SetSizer(topSizer); +} void FileArchiveDialog::on_dpi_changed(const wxRect& suggested_rect) { + int em = em_unit(); + + //msw_buttons_rescale(this, em, { wxID_CANCEL, m_save_btn_id, m_move_btn_id, m_continue_btn_id }); + //for (auto btn : { m_save_btn, m_transfer_btn, m_discard_btn }) + // if (btn) btn->msw_rescale(); + + const wxSize& size = wxSize(70 * em, 30 * em); + SetMinSize(size); + + //m_tree->Rescale(em); + + Fit(); + Refresh(); +} + +void FileArchiveDialog::on_open_button() +{ + wxDataViewItemArray top_items; + m_avc->get_model()->GetChildren(wxDataViewItem(nullptr), top_items); + + std::function deep_fill = [&paths = m_selected_paths, &deep_fill](ArchiveViewNode* node){ + if (node == nullptr) + return; + if (node->get_children().empty()) { + if (node->get_toggle()) + paths.emplace_back(node->get_fullpath()); + } else { + for (ArchiveViewNode* child : node->get_children()) + deep_fill(child); + } + }; + + for (const auto& item : top_items) + { + ArchiveViewNode* node = static_cast(item.GetID()); + deep_fill(node); + } + this->EndModal(wxID_OK); } } // namespace GUI diff --git a/src/slic3r/GUI/FileArchiveDialog.hpp b/src/slic3r/GUI/FileArchiveDialog.hpp index 8f48d2a998..70c507c94a 100644 --- a/src/slic3r/GUI/FileArchiveDialog.hpp +++ b/src/slic3r/GUI/FileArchiveDialog.hpp @@ -2,29 +2,110 @@ #define slic3r_GUI_FileArchiveDialog_hpp_ #include "GUI_Utils.hpp" +#include "libslic3r/miniz_extension.hpp" + #include namespace Slic3r { namespace GUI { +class ArchiveViewCtrl; + +class ArchiveViewNode +{ +public: + ArchiveViewNode(const wxString& name) : m_name(name) {} + + std::vector& get_children() { return m_children; } + void set_parent(ArchiveViewNode* parent) {m_parent = parent; } + ArchiveViewNode* get_parent() const { return m_parent; } + bool is_container() const { return !m_children.empty(); } + wxString get_name() const { return m_name; } + void set_name(const wxString& name) { m_name = name; } + bool get_toggle() const {return m_toggle; } + void set_toggle(bool toggle) {m_toggle = toggle; } + bool get_is_folder() const { return m_folder; } + void set_is_folder(bool is_folder) { m_folder = is_folder; } + void set_fullpath(boost::filesystem::path path) { m_fullpath = path; } + boost::filesystem::path get_fullpath() const { return m_fullpath; } +private: +private: + wxString m_name; + ArchiveViewNode* m_parent { nullptr }; + std::vector m_children; // MEMORY! + + bool m_toggle { false }; + bool m_folder { false }; + boost::filesystem::path m_fullpath; +}; + +class ArchiveViewModel : public wxDataViewModel +{ +public: + ArchiveViewModel(wxWindow* parent); + ~ArchiveViewModel(); + + /* wxDataViewItem AddFolder(wxDataViewItem& parent, wxString name); + wxDataViewItem AddFile(wxDataViewItem& parent, wxString name);*/ + + ArchiveViewNode* AddFile(ArchiveViewNode* parent, wxString name); + + wxString GetColumnType(unsigned int col) const override; + unsigned int GetColumnCount() const override { return 2; } + + void Rescale(); + void Delete(const wxDataViewItem& item); + void Clear(); + + wxDataViewItem GetParent(const wxDataViewItem& item) const override; + unsigned int GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const override; + + void SetAssociatedControl(ArchiveViewCtrl* ctrl) { m_ctrl = ctrl; } + + void GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const override; + bool SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) override; + + void untoggle_folders(const wxDataViewItem& item); + + bool IsEnabled(const wxDataViewItem& item, unsigned int col) const override; + bool IsContainer(const wxDataViewItem& item) const override; + // Is the container just a header or an item with all columns + // In our case it is an item with all columns + bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } + +protected: + wxWindow* m_parent { nullptr }; + ArchiveViewCtrl* m_ctrl { nullptr }; + std::vector m_top_children; +}; + +class ArchiveViewCtrl : public wxDataViewCtrl +{ + public: + ArchiveViewCtrl(wxWindow* parent, wxSize size); + ~ArchiveViewCtrl(); + + ArchiveViewModel* get_model() const {return m_model; } +protected: + ArchiveViewModel* m_model; +}; + + class FileArchiveDialog : public DPIDialog { public: - FileArchiveDialog(const std::vector& files); + FileArchiveDialog(mz_zip_archive* archive, std::vector& selected_paths); protected: void on_dpi_changed(const wxRect& suggested_rect) override; + void on_open_button(); + std::vector& m_selected_paths; + ArchiveViewCtrl* m_avc; }; - - - - - - } // namespace GU } // namespace Slic3r #endif // slic3r_GUI_FileArchiveDialog_hpp_ \ No newline at end of file diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5d9365aba1..22308f0161 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -5454,76 +5454,78 @@ std::vector Plater::load_files(const std::vector& input_fil return p->load_files(paths, load_model, load_config, imperial_units); } -void Plater::preview_archive(const boost::filesystem::path& archive_path) +bool Plater::preview_archive(const boost::filesystem::path& archive_path) { - BOOST_LOG_TRIVIAL(error) << "preview zip archive: " << archive_path; - mz_zip_archive archive; mz_zip_zero_struct(&archive); if (!open_zip_reader(&archive, archive_path.string())) - return ; + return false; - mz_uint num_entries = mz_zip_reader_get_num_files(&archive); - - //BOOST_LOG_TRIVIAL(error) << "entries: " << num_entries; - + mz_uint num_entries = mz_zip_reader_get_num_files(&archive); mz_zip_archive_file_stat stat; - //bool config_found = false; - //for (mz_uint i = 0; i < num_entries; ++i) { - // if (mz_zip_reader_file_stat(&archive, i, &stat)) { - // std::string name(stat.m_filename); - // //std::replace(name.begin(), name.end(), '\\', '/'); + std::vector selected_paths; + std::vector unzipped_paths; + FileArchiveDialog dlg(&archive, selected_paths); + if (dlg.ShowModal() == wxID_OK) + { + std::string archive_path_string = archive_path.string(); + archive_path_string = archive_path_string.substr(0, archive_path_string.size() - 4); + /* boost::filesystem::path archive_dir(archive_path_string); + + if (!boost::filesystem::is_directory(archive_dir) && + !boost::filesystem::create_directory(archive_dir)) { + close_zip_reader(&archive); + return; + }*/ - // BOOST_LOG_TRIVIAL(error) << name; - // - // } - //} - - std::string archive_path_string = archive_path.string(); - archive_path_string = archive_path_string.substr(0, archive_path_string.size() - 4); - boost::filesystem::path archive_dir(archive_path_string); + boost::filesystem::path archive_dir("C:\\Users\\User\\AppData\\Local\\Temp"); + //m_result = {filename="C:\\Users\\User\\AppData\\Local\\Temp\\.7596.gcode.tmp" id=3 moves={ size=9639 } ...} + + for (mz_uint i = 0; i < num_entries; ++i) { + if (mz_zip_reader_file_stat(&archive, i, &stat)) { + std::string name(stat.m_filename); + for (const auto& path : selected_paths) { + if (path.string() == name) { + try + { + std::replace(name.begin(), name.end(), '\\', '/'); + // rename if file exists + std::string filename = path.filename().string(); + std::string extension = boost::filesystem::extension(path); + std::string just_filename = filename.substr(0, filename.size() - extension.size()); + std::string final_filename = just_filename; + size_t version = 0; + while (boost::filesystem::exists(archive_dir / (final_filename + extension))) + { + ++version; + final_filename = just_filename + "(" + std::to_string(version) + ")"; + } + filename = final_filename + extension; - if (!boost::filesystem::is_directory(archive_dir) && - !boost::filesystem::create_directory(archive_dir)) - return; - - std::replace(archive_path_string.begin(), archive_path_string.end(), '\\', '/'); - - std::vector files; - - // we first loop the entries to read from the archive the .model file only, in order to extract the version from it - for (mz_uint i = 0; i < num_entries; ++i) { - if (mz_zip_reader_file_stat(&archive, i, &stat)) { - std::string name(stat.m_filename); - std::replace(name.begin(), name.end(), '\\', '/'); - files.push_back(name); - if (boost::algorithm::iends_with(name, ".stl")) { - try - { - std::string final_path = archive_path_string + "/" + name; - bool res = mz_zip_reader_extract_file_to_file(&archive, stat.m_filename, final_path.c_str(), 0); - //bool res = mz_zip_reader_extract_file_to_callback(&archive, stat.m_filename, [](void* pOpaque, mz_uint64 file_ofs, const void* pBuf, size_t n)->size_t {}, &data, 0); - } - catch (const std::exception& e) - { - // ensure the zip archive is closed and rethrow the exception - close_zip_reader(&archive); - throw Slic3r::FileIOError(e.what()); + std::string final = (archive_dir / filename).string(); + + unzipped_paths.emplace_back(final); + bool res = mz_zip_reader_extract_file_to_file(&archive, stat.m_filename, final.c_str(), 0); + } + catch (const std::exception& e) + { + // ensure the zip archive is closed and rethrow the exception + close_zip_reader(&archive); + throw Slic3r::FileIOError(e.what()); + } + } } } } } - FileArchiveDialog dlg(files); - dlg.ShowModal(); - close_zip_reader(&archive); - + return !load_files(unzipped_paths).empty(); } enum class LoadType : unsigned char @@ -5699,7 +5701,8 @@ bool Plater::load_files(const wxArrayString& filenames) return true; } else if (boost::algorithm::iends_with(filename, ".zip")) { - preview_archive(*it); + return preview_archive(*it); + } } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index b76be352cb..27a99788cf 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -177,7 +177,7 @@ public: bool load_files(const wxArrayString& filenames); void check_selected_presets_visibility(PrinterTechnology loaded_printer_technology); - void preview_archive(const boost::filesystem::path& input_file); + bool preview_archive(const boost::filesystem::path& input_file); const wxString& get_last_loaded_gcode() const { return m_last_loaded_gcode; }