
Custom URL Registration: - Windows - writes to registers. - Linux - desktop integration file. - Macos - info.plist.in creates registration and is controlled only via app config. Registration is first made in Config Wizard. Or is triggered from Preferences. Path to downloads folder can be set. URL link starts new instance of PS which sends data to running instance via SingleInstance structures if exists. New progress notification is introduced with pause and stop buttons. Downloader writes downloaded data by chunks. Support for zip files is introduced. Zip files can be opened, downloaded or drag'n'droped in PS. Archive dialog is opened. Then if more than 1 project is selected, only geometry is loaded. Opening of 3mf project now supports openning project in new PS instance.
363 lines
No EOL
13 KiB
C++
363 lines
No EOL
13 KiB
C++
#include "FileArchiveDialog.hpp"
|
|
|
|
#include "I18N.hpp"
|
|
#include "GUI_App.hpp"
|
|
#include "GUI.hpp"
|
|
#include "MainFrame.hpp"
|
|
#include "ExtraRenderers.hpp"
|
|
#include "format.hpp"
|
|
#include <regex>
|
|
#include <boost/log/trivial.hpp>
|
|
#include <boost/nowide/convert.hpp>
|
|
|
|
namespace Slic3r {
|
|
namespace GUI {
|
|
|
|
ArchiveViewModel::ArchiveViewModel(wxWindow* parent)
|
|
:m_parent(parent)
|
|
{}
|
|
ArchiveViewModel::~ArchiveViewModel()
|
|
{}
|
|
|
|
std::shared_ptr<ArchiveViewNode> ArchiveViewModel::AddFile(std::shared_ptr<ArchiveViewNode> parent, const wxString& name, bool container)
|
|
{
|
|
std::shared_ptr<ArchiveViewNode> node = std::make_shared<ArchiveViewNode>(ArchiveViewNode(name));
|
|
node->set_container(container);
|
|
|
|
if (parent.get() != nullptr) {
|
|
parent->get_children().push_back(node);
|
|
node->set_parent(parent);
|
|
parent->set_is_folder(true);
|
|
} else {
|
|
m_top_children.emplace_back(node);
|
|
}
|
|
|
|
wxDataViewItem child = wxDataViewItem((void*)node.get());
|
|
wxDataViewItem parent_item= wxDataViewItem((void*)parent.get());
|
|
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()
|
|
{
|
|
// There should be no pictures rendered
|
|
}
|
|
|
|
void ArchiveViewModel::Delete(const wxDataViewItem& item)
|
|
{
|
|
assert(item.IsOk());
|
|
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
|
assert(node->get_parent() != nullptr);
|
|
for (std::shared_ptr<ArchiveViewNode> child : node->get_children())
|
|
{
|
|
Delete(wxDataViewItem((void*)child.get()));
|
|
}
|
|
delete [] node;
|
|
}
|
|
void ArchiveViewModel::Clear()
|
|
{
|
|
}
|
|
|
|
wxDataViewItem ArchiveViewModel::GetParent(const wxDataViewItem& item) const
|
|
{
|
|
assert(item.IsOk());
|
|
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
|
return wxDataViewItem((void*)node->get_parent().get());
|
|
}
|
|
unsigned int ArchiveViewModel::GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const
|
|
{
|
|
if (!parent.IsOk()) {
|
|
for (std::shared_ptr<ArchiveViewNode>child : m_top_children) {
|
|
array.push_back(wxDataViewItem((void*)child.get()));
|
|
}
|
|
return m_top_children.size();
|
|
}
|
|
|
|
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(parent.GetID());
|
|
for (std::shared_ptr<ArchiveViewNode> child : node->get_children()) {
|
|
array.push_back(wxDataViewItem((void*)child.get()));
|
|
}
|
|
return node->get_children().size();
|
|
}
|
|
|
|
void ArchiveViewModel::GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const
|
|
{
|
|
assert(item.IsOk());
|
|
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(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<ArchiveViewNode*>(item.GetID());
|
|
node->set_toggle(false);
|
|
if (node->get_parent().get() != nullptr)
|
|
untoggle_folders(wxDataViewItem((void*)node->get_parent().get()));
|
|
}
|
|
|
|
bool ArchiveViewModel::SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col)
|
|
{
|
|
assert(item.IsOk());
|
|
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
|
if (col == 0) {
|
|
node->set_toggle(variant.GetBool());
|
|
// if folder recursivelly check all children
|
|
for (std::shared_ptr<ArchiveViewNode> child : node->get_children()) {
|
|
SetValue(variant, wxDataViewItem((void*)child.get()), col);
|
|
}
|
|
if(!variant.GetBool() && node->get_parent())
|
|
untoggle_folders(wxDataViewItem((void*)node->get_parent().get()));
|
|
} else {
|
|
node->set_name(variant.GetString());
|
|
}
|
|
m_parent->Refresh();
|
|
return true;
|
|
}
|
|
bool ArchiveViewModel::IsEnabled(const wxDataViewItem& item, unsigned int col) const
|
|
{
|
|
// As of now, all items are always enabled.
|
|
// Returning false for col 1 would gray out text.
|
|
return true;
|
|
}
|
|
|
|
bool ArchiveViewModel::IsContainer(const wxDataViewItem& item) const
|
|
{
|
|
if(!item.IsOk())
|
|
return true;
|
|
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(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(wxWindow* parent_window, mz_zip_archive* archive, std::vector<boost::filesystem::path>& selected_paths)
|
|
: DPIDialog(parent_window, 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<std::shared_ptr<ArchiveViewNode>> stack;
|
|
|
|
std::function<void(std::vector<std::shared_ptr<ArchiveViewNode> >&, size_t)> reduce_stack = [] (std::vector<std::shared_ptr<ArchiveViewNode>>& 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<size_t(const boost::filesystem::path&, std::vector<std::shared_ptr<ArchiveViewNode>>&)> adjust_stack = [&adjust_stack, &reduce_stack, &avc = m_avc](const boost::filesystem::path& const_file, std::vector<std::shared_ptr<ArchiveViewNode>>& stack)->size_t {
|
|
boost::filesystem::path file(const_file);
|
|
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() ? std::shared_ptr<ArchiveViewNode>(nullptr) : stack.back()), GUI::format_wxstr(file.filename().string()), true)); // filename string to wstring?
|
|
return struct_size + 1;
|
|
};
|
|
|
|
const std::regex pattern_drop(".*[.](stl|obj|amf|3mf|prusa|step|stp)", std::regex::icase);
|
|
mz_uint num_entries = mz_zip_reader_get_num_files(archive);
|
|
mz_zip_archive_file_stat stat;
|
|
std::vector<boost::filesystem::path> filtered_entries;
|
|
for (mz_uint i = 0; i < num_entries; ++i) {
|
|
if (mz_zip_reader_file_stat(archive, i, &stat)) {
|
|
wxString wname = boost::nowide::widen(stat.m_filename);
|
|
std::string name = GUI::format(wname);
|
|
//std::replace(name.begin(), name.end(), '\\', '/');
|
|
boost::filesystem::path path(name);
|
|
if (!path.has_extension())
|
|
continue;
|
|
// filter out MACOS specific hidden files
|
|
if (boost::algorithm::starts_with(path.string(), "__MACOSX"))
|
|
continue;
|
|
filtered_entries.emplace_back(std::move(path));
|
|
}
|
|
}
|
|
// sorting files will help adjust_stack function to not create multiple same folders
|
|
std::sort(filtered_entries.begin(), filtered_entries.end(), [](const boost::filesystem::path& p1, const boost::filesystem::path& p2){ return p1.string() > p2.string(); });
|
|
for (const boost::filesystem::path& path : filtered_entries)
|
|
{
|
|
std::shared_ptr<ArchiveViewNode> parent(nullptr);
|
|
|
|
adjust_stack(path, stack);
|
|
if (!stack.empty())
|
|
parent = stack.back();
|
|
if (std::regex_match(path.extension().string(), pattern_drop)) { // this leaves out non-compatible files
|
|
m_avc->get_model()->AddFile(parent, GUI::format_wxstr(path.filename().string()), false)->set_fullpath(/*std::move(path)*/path); // filename string to wstring?
|
|
}
|
|
}
|
|
wxBoxSizer* btn_sizer = new wxBoxSizer(wxHORIZONTAL);
|
|
|
|
wxButton* btn_all = new wxButton(this, wxID_ANY, "All");
|
|
btn_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_all_button(); });
|
|
btn_sizer->Add(btn_all, 0, wxLeft);
|
|
|
|
wxButton* btn_none = new wxButton(this, wxID_ANY, "None");
|
|
btn_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_none_button(); });
|
|
btn_sizer->Add(btn_none, 0, wxLeft);
|
|
|
|
btn_sizer->AddStretchSpacer();
|
|
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, wxRIGHT);
|
|
|
|
wxButton* cancel_btn = new wxButton(this, wxID_CANCEL, "Cancel");
|
|
cancel_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { this->EndModal(wxID_CANCEL); });
|
|
btn_sizer->Add(cancel_btn, 0, wxRIGHT);
|
|
|
|
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();
|
|
BOOST_LOG_TRIVIAL(error) << "on_dpi_changed";
|
|
//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<void(ArchiveViewNode*)> 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 (std::shared_ptr<ArchiveViewNode> child : node->get_children())
|
|
deep_fill(child.get());
|
|
}
|
|
};
|
|
|
|
for (const auto& item : top_items)
|
|
{
|
|
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
|
deep_fill(node);
|
|
}
|
|
this->EndModal(wxID_OK);
|
|
}
|
|
|
|
void FileArchiveDialog::on_all_button()
|
|
{
|
|
|
|
wxDataViewItemArray top_items;
|
|
m_avc->get_model()->GetChildren(wxDataViewItem(nullptr), top_items);
|
|
|
|
std::function<void(ArchiveViewNode*)> deep_fill = [&deep_fill](ArchiveViewNode* node) {
|
|
if (node == nullptr)
|
|
return;
|
|
node->set_toggle(true);
|
|
if (!node->get_children().empty()) {
|
|
for (std::shared_ptr<ArchiveViewNode> child : node->get_children())
|
|
deep_fill(child.get());
|
|
}
|
|
};
|
|
|
|
for (const auto& item : top_items)
|
|
{
|
|
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
|
deep_fill(node);
|
|
// Fix for linux, where Refresh or Update wont help to redraw toggle checkboxes.
|
|
// It should be enough to call ValueChanged for top items.
|
|
m_avc->get_model()->ValueChanged(item, 0);
|
|
}
|
|
|
|
Refresh();
|
|
}
|
|
|
|
void FileArchiveDialog::on_none_button()
|
|
{
|
|
wxDataViewItemArray top_items;
|
|
m_avc->get_model()->GetChildren(wxDataViewItem(nullptr), top_items);
|
|
|
|
std::function<void(ArchiveViewNode*)> deep_fill = [&deep_fill](ArchiveViewNode* node) {
|
|
if (node == nullptr)
|
|
return;
|
|
node->set_toggle(false);
|
|
if (!node->get_children().empty()) {
|
|
for (std::shared_ptr<ArchiveViewNode> child : node->get_children())
|
|
deep_fill(child.get());
|
|
}
|
|
};
|
|
|
|
for (const auto& item : top_items)
|
|
{
|
|
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
|
deep_fill(node);
|
|
// Fix for linux, where Refresh or Update wont help to redraw toggle checkboxes.
|
|
// It should be enough to call ValueChanged for top items.
|
|
m_avc->get_model()->ValueChanged(item, 0);
|
|
}
|
|
|
|
this->Refresh();
|
|
}
|
|
|
|
} // namespace GUI
|
|
} // namespace Slic3r
|