Merge branch 'master' into fs_emboss

# Conflicts:
#	src/libslic3r/Technologies.hpp
#	src/slic3r/CMakeLists.txt
#	src/slic3r/GUI/GUI_App.cpp
#	src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp
This commit is contained in:
Filip Sykala 2022-05-31 11:14:53 +02:00
commit e0d5505413
18 changed files with 793 additions and 218 deletions

2
.gitignore vendored
View File

@ -18,4 +18,4 @@ local-lib
build-linux/*
deps/build-linux/*
**/.DS_Store
/.idea/
**/.idea/

View File

@ -1,7 +1,7 @@
#include <cassert>
#include "PresetBundle.hpp"
#include "libslic3r.h"
#include "PresetBundle.hpp"
#include "Utils.hpp"
#include "Model.hpp"
#include "format.hpp"
@ -453,6 +453,11 @@ void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::
presets.get_edited_preset().config.apply_only(presets.get_selected_preset().config, unselected_options);
}
#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
if (type == Preset::TYPE_PRINTER)
copy_bed_model_and_texture_if_needed(presets.get_edited_preset().config);
#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
// Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini
presets.save_current_preset(new_name);
// Mark the print & filament enabled if they are compatible with the currently selected preset.
@ -1860,4 +1865,32 @@ void PresetBundle::set_default_suppressed(bool default_suppressed)
printers.set_default_suppressed(default_suppressed);
}
#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
void copy_bed_model_and_texture_if_needed(DynamicPrintConfig& config)
{
const boost::filesystem::path user_dir = boost::filesystem::absolute(boost::filesystem::path(data_dir()) / "printer").make_preferred();
const boost::filesystem::path res_dir = boost::filesystem::absolute(boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
auto do_copy = [&user_dir, &res_dir](ConfigOptionString* cfg, const std::string& type) {
if (cfg == nullptr || cfg->value.empty())
return;
const boost::filesystem::path src_dir = boost::filesystem::absolute(boost::filesystem::path(cfg->value)).make_preferred().parent_path();
if (src_dir != user_dir && src_dir.parent_path() != res_dir) {
const std::string dst_value = (user_dir / boost::filesystem::path(cfg->value).filename()).string();
std::string error;
if (copy_file_inner(cfg->value, dst_value, error) == SUCCESS)
cfg->value = dst_value;
else {
BOOST_LOG_TRIVIAL(error) << "Copying from " << cfg->value << " to " << dst_value << " failed. Unable to set custom bed " << type << ". [" << error << "]";
cfg->value = "";
}
}
};
do_copy(config.option<ConfigOptionString>("bed_custom_texture"), "texture");
do_copy(config.option<ConfigOptionString>("bed_custom_model"), "model");
}
#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
} // namespace Slic3r

View File

@ -178,6 +178,12 @@ private:
ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute)
#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
// Copies bed texture and model files to 'data_dir()\printer' folder, if needed
// and updates the config accordingly
extern void copy_bed_model_and_texture_if_needed(DynamicPrintConfig& config);
#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
} // namespace Slic3r
#endif /* slic3r_PresetBundle_hpp_ */

View File

@ -288,7 +288,7 @@ template<unsigned MAX_ITER>
struct RotfinderBoilerplate {
static constexpr unsigned MAX_TRIES = MAX_ITER;
int status = 0;
int status = 0, prev_status = 0;
TriangleMesh mesh;
unsigned max_tries;
const RotOptimizeParams &params;
@ -314,13 +314,20 @@ struct RotfinderBoilerplate {
RotfinderBoilerplate(const ModelObject &mo, const RotOptimizeParams &p)
: mesh{get_mesh_to_rotate(mo)}
, params{p}
, max_tries(p.accuracy() * MAX_TRIES)
{
, params{p}
{}
void statusfn() {
int s = status * 100 / max_tries;
if (s != prev_status) {
params.statuscb()(s);
prev_status = s;
}
++status;
}
void statusfn() { params.statuscb()(++status * 100.0 / max_tries); }
bool stopcond() { return ! params.statuscb()(-1); }
};

View File

@ -91,8 +91,7 @@
#define ENABLE_USED_FILAMENT_POST_PROCESS (1 && ENABLE_2_5_0_ALPHA1)
// Enable gizmo grabbers to share common models
#define ENABLE_GIZMO_GRABBER_REFACTOR (1 && ENABLE_2_5_0_ALPHA1)
// Disable association to 3mf and stl files if the application is run on Windows 8 or later
#define ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER (1 && ENABLE_2_5_0_ALPHA1)
// Enable copy of custom bed model and texture
#define ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE (1 && ENABLE_2_5_0_ALPHA1)
#endif // _prusaslicer_technologies_h_

View File

@ -180,6 +180,7 @@ set(SLIC3R_GUI_SOURCES
GUI/Jobs/Worker.hpp
GUI/Jobs/BoostThreadWorker.hpp
GUI/Jobs/BoostThreadWorker.cpp
GUI/Jobs/UIThreadWorker.hpp
GUI/Jobs/BusyCursorJob.hpp
GUI/Jobs/PlaterWorker.hpp
GUI/Jobs/ArrangeJob.hpp
@ -268,6 +269,8 @@ set(SLIC3R_GUI_SOURCES
Utils/TCPConsole.hpp
Utils/MKS.cpp
Utils/MKS.hpp
Utils/WinRegistry.cpp
Utils/WinRegistry.hpp
Utils/WxFontUtils.cpp
Utils/WxFontUtils.hpp
)

View File

@ -1934,9 +1934,6 @@ void ConfigWizard::priv::load_pages()
index->add_page(page_update);
index->add_page(page_reload_from_disk);
#ifdef _WIN32
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
if (page_files_association != nullptr)
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
index->add_page(page_files_association);
#endif // _WIN32
index->add_page(page_mode);
@ -2750,9 +2747,6 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0");
#ifdef _WIN32
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
if (page_files_association != nullptr) {
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
app_config->set("associate_3mf", page_files_association->associate_3mf() ? "1" : "0");
app_config->set("associate_stl", page_files_association->associate_stl() ? "1" : "0");
// app_config->set("associate_gcode", page_files_association->associate_gcode() ? "1" : "0");
@ -2767,15 +2761,6 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
// if (page_files_association->associate_gcode())
// wxGetApp().associate_gcode_files();
// }
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
}
else {
app_config->set("associate_3mf", "0");
app_config->set("associate_stl", "0");
// app_config->set("associate_gcode", "0");
}
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
#endif // _WIN32
page_mode->serialize_mode(app_config);
@ -2795,6 +2780,10 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
page_diams->apply_custom_config(*custom_config);
page_temps->apply_custom_config(*custom_config);
#if ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
copy_bed_model_and_texture_if_needed(*custom_config);
#endif // ENABLE_COPY_CUSTOM_BED_MODEL_AND_TEXTURE
const std::string profile_name = page_custom->profile_name();
preset_bundle->load_config_from_wizard(profile_name, *custom_config);
}
@ -2935,10 +2924,6 @@ ConfigWizard::ConfigWizard(wxWindow *parent)
p->add_page(p->page_update = new PageUpdate(this));
p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this));
#ifdef _WIN32
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
// file association is not possible anymore starting with Win 8
if (wxPlatformInfo::Get().GetOSMajorVersion() < 8)
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
p->add_page(p->page_files_association = new PageFilesAssociation(this));
#endif // _WIN32
p->add_page(p->page_mode = new PageMode(this));

View File

@ -991,12 +991,14 @@ void GCodeViewer::render()
render_toolpaths();
render_shells();
float legend_height = 0.0f;
if (!m_layers.empty()) {
render_legend(legend_height);
if (m_sequential_view.current.last != m_sequential_view.endpoints.last) {
m_sequential_view.marker.set_world_position(m_sequential_view.current_position);
m_sequential_view.marker.set_world_offset(m_sequential_view.current_offset);
m_sequential_view.render(legend_height);
}
}
#if ENABLE_GCODE_VIEWER_STATISTICS
render_statistics();
#endif // ENABLE_GCODE_VIEWER_STATISTICS

View File

@ -59,6 +59,7 @@
#include "../Utils/Process.hpp"
#include "../Utils/MacDarkMode.hpp"
#include "../Utils/AppUpdater.hpp"
#include "../Utils/WinRegistry.hpp"
#include "slic3r/Config/Snapshot.hpp"
#include "ConfigSnapshotDialog.hpp"
#include "FirmwareDialog.hpp"
@ -1204,17 +1205,10 @@ bool GUI_App::on_init_inner()
if (is_editor()) {
#ifdef __WXMSW__
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
// file association is not possible anymore starting with Win 8
if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) {
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
if (app_config->get("associate_3mf") == "1")
associate_3mf_files();
if (app_config->get("associate_stl") == "1")
associate_stl_files();
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
}
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
#endif // __WXMSW__
preset_updater = new PresetUpdater();
@ -1252,15 +1246,8 @@ bool GUI_App::on_init_inner()
}
else {
#ifdef __WXMSW__
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
// file association is not possible anymore starting with Win 8
if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) {
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
if (app_config->get("associate_gcode") == "1")
associate_gcode_files();
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
}
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
#endif // __WXMSW__
}
@ -2433,10 +2420,6 @@ void GUI_App::open_preferences(const std::string& highlight_option /*= std::stri
this->plater_->refresh_print();
#ifdef _WIN32
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
// file association is not possible anymore starting with Win 8
if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) {
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
if (is_editor()) {
if (app_config->get("associate_3mf") == "1")
associate_3mf_files();
@ -2447,9 +2430,6 @@ void GUI_App::open_preferences(const std::string& highlight_option /*= std::stri
if (app_config->get("associate_gcode") == "1")
associate_gcode_files();
}
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
}
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
#endif // _WIN32
if (mainframe->preferences_dialog->settings_layout_changed()) {
@ -3157,119 +3137,22 @@ bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* pa
#ifdef __WXMSW__
static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue)
{
// see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association
wchar_t szValueCurrent[1000];
DWORD dwType;
DWORD dwSize = sizeof(szValueCurrent);
int iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize);
bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND;
if ((iRC != ERROR_SUCCESS) && !bDidntExist)
// an error occurred
return false;
if (!bDidntExist) {
if (dwType != REG_SZ)
// invalid type
return false;
if (::wcscmp(szValueCurrent, pszValue) == 0)
// value already set
return false;
}
DWORD dwDisposition;
HKEY hkey;
iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition);
bool ret = false;
if (iRC == ERROR_SUCCESS) {
iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t));
if (iRC == ERROR_SUCCESS)
ret = true;
}
RegCloseKey(hkey);
return ret;
}
void GUI_App::associate_3mf_files()
{
wchar_t app_path[MAX_PATH];
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\"";
std::wstring prog_id = L"Prusa.Slicer.1";
std::wstring prog_desc = L"PrusaSlicer";
std::wstring prog_command = prog_path + L" \"%1\"";
std::wstring reg_base = L"Software\\Classes";
std::wstring reg_extension = reg_base + L"\\.3mf";
std::wstring reg_prog_id = reg_base + L"\\" + prog_id;
std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command";
bool is_new = false;
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
if (is_new)
// notify Windows only when any of the values gets changed
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
associate_file_type(L".3mf", L"Prusa.Slicer.1", L"PrusaSlicer", true);
}
void GUI_App::associate_stl_files()
{
wchar_t app_path[MAX_PATH];
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\"";
std::wstring prog_id = L"Prusa.Slicer.1";
std::wstring prog_desc = L"PrusaSlicer";
std::wstring prog_command = prog_path + L" \"%1\"";
std::wstring reg_base = L"Software\\Classes";
std::wstring reg_extension = reg_base + L"\\.stl";
std::wstring reg_prog_id = reg_base + L"\\" + prog_id;
std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command";
bool is_new = false;
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
if (is_new)
// notify Windows only when any of the values gets changed
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
associate_file_type(L".stl", L"Prusa.Slicer.1", L"PrusaSlicer", true);
}
void GUI_App::associate_gcode_files()
{
wchar_t app_path[MAX_PATH];
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
std::wstring prog_path = L"\"" + std::wstring(app_path) + L"\"";
std::wstring prog_id = L"PrusaSlicer.GCodeViewer.1";
std::wstring prog_desc = L"PrusaSlicerGCodeViewer";
std::wstring prog_command = prog_path + L" \"%1\"";
std::wstring reg_base = L"Software\\Classes";
std::wstring reg_extension = reg_base + L"\\.gcode";
std::wstring reg_prog_id = reg_base + L"\\" + prog_id;
std::wstring reg_prog_id_command = reg_prog_id + L"\\Shell\\Open\\Command";
bool is_new = false;
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
is_new |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
if (is_new)
// notify Windows only when any of the values gets changed
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
associate_file_type(L".gcode", L"PrusaSlicer.GCodeViewer.1", L"PrusaSlicerGCodeViewer", true);
}
#endif // __WXMSW__
void GUI_App::on_version_read(wxCommandEvent& evt)
{
app_config->set("version_online", into_u8(evt.GetString()));

View File

@ -983,10 +983,11 @@ void Preview::load_print_as_fff(bool keep_z_range)
if (gcode_preview_data_valid) {
// Load the real G-code preview.
m_canvas->load_gcode_preview(*m_gcode_result, colors);
m_left_sizer->Show(m_bottom_toolbar_panel);
m_left_sizer->Layout();
Refresh();
zs = m_canvas->get_gcode_layers_zs();
if (!zs.empty())
m_left_sizer->Show(m_bottom_toolbar_panel);
m_loaded = true;
}
else if (wxGetApp().is_editor()) {

View File

@ -840,7 +840,9 @@ GLGizmoRotate3D::GLGizmoRotate3D(GLCanvas3D& parent, const std::string& icon_fil
GLGizmoRotate(parent, GLGizmoRotate::X),
GLGizmoRotate(parent, GLGizmoRotate::Y),
GLGizmoRotate(parent, GLGizmoRotate::Z) })
{}
{
load_rotoptimize_state();
}
bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event)
{

View File

@ -40,6 +40,12 @@ class PlaterWorker: public Worker {
{
wxWakeUpIdle();
ctl.update_status(st, msg);
// If the worker is not using additional threads, the UI
// is refreshed with this call. If the worker is running
// in it's own thread, the yield should not have any
// visible effects.
wxYieldIfNeeded();
}
bool was_canceled() const override { return ctl.was_canceled(); }

View File

@ -0,0 +1,125 @@
#ifndef UITHREADWORKER_HPP
#define UITHREADWORKER_HPP
#include <deque>
#include <queue>
#include "Worker.hpp"
#include "ProgressIndicator.hpp"
namespace Slic3r { namespace GUI {
// Implementation of a worker which does not create any additional threads.
class UIThreadWorker : public Worker, private Job::Ctl {
std::queue<std::unique_ptr<Job>, std::deque<std::unique_ptr<Job>>> m_jobqueue;
std::shared_ptr<ProgressIndicator> m_progress;
bool m_running = false;
bool m_canceled = false;
void process_front()
{
std::unique_ptr<Job> job;
if (!m_jobqueue.empty()) {
job = std::move(m_jobqueue.front());
m_jobqueue.pop();
}
if (job) {
std::exception_ptr eptr;
m_running = true;
try {
job->process(*this);
} catch (...) {
eptr= std::current_exception();
}
job->finalize(m_canceled, eptr);
// Unhandled exceptions are rethrown without mercy.
if (eptr)
std::rethrow_exception(eptr);
m_running = false;
m_canceled = false;
}
}
protected:
// Implement Job::Ctl interface:
void update_status(int st, const std::string &msg = "") override
{
if (m_progress) {
m_progress->set_progress(st);
m_progress->set_status_text(msg.c_str());
}
}
bool was_canceled() const override { return m_canceled; }
std::future<void> call_on_main_thread(std::function<void()> fn) override
{
return std::async(std::launch::deferred, [fn]{ fn(); });
}
public:
explicit UIThreadWorker(std::shared_ptr<ProgressIndicator> pri,
const std::string & /*name*/ = "")
: m_progress{pri}
{
if (m_progress)
m_progress->set_cancel_callback([this]() { cancel(); });
}
UIThreadWorker() = default;
bool push(std::unique_ptr<Job> job) override
{
m_canceled = false;
if (job)
m_jobqueue.push(std::move(job));
return bool(job);
}
bool is_idle() const override { return !m_running && m_jobqueue.empty(); }
void cancel() override { m_canceled = true; }
void cancel_all() override
{
m_canceled = true;
process_front();
while (!m_jobqueue.empty())
m_jobqueue.pop();
}
void process_events() override {
while (!m_jobqueue.empty())
process_front();
}
bool wait_for_current_job(unsigned /*timeout_ms*/ = 0) override {
process_front();
return true;
}
bool wait_for_idle(unsigned /*timeout_ms*/ = 0) override {
process_events();
return true;
}
ProgressIndicator * get_pri() { return m_progress.get(); }
const ProgressIndicator * get_pri() const { return m_progress.get(); }
};
}} // namespace Slic3r::GUI
#endif // UITHREADWORKER_HPP

View File

@ -1656,6 +1656,9 @@ struct Plater::priv
// objects would be frozen for the user. In case of arrange, an animation
// could be shown, or with the optimize orientations, partial results
// could be displayed.
//
// UIThreadWorker can be used as a replacement for BoostThreadWorker if
// no additional worker threads are desired (useful for debugging or profiling)
PlaterWorker<BoostThreadWorker> m_worker;
SLAImportDialog * m_sla_import_dlg;

View File

@ -79,9 +79,11 @@ void PreferencesDialog::show(const std::string& highlight_opt_key /*= std::strin
m_custom_toolbar_size = atoi(get_app_config()->get("custom_toolbar_size").c_str());
m_use_custom_toolbar_size = get_app_config()->get("use_custom_toolbar_size") == "1";
if (wxGetApp().is_editor()) {
// update colors for color pickers
update_color(m_sys_colour, wxGetApp().get_label_clr_sys());
update_color(m_mod_colour, wxGetApp().get_label_clr_modified());
}
this->ShowModal();
}
@ -230,10 +232,6 @@ void PreferencesDialog::build()
app_config->get("export_sources_full_pathnames") == "1");
#ifdef _WIN32
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
// file association is not possible anymore starting with Win 8
if (wxPlatformInfo::Get().GetOSMajorVersion() < 8) {
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
// Please keep in sync with ConfigWizard
append_bool_option(m_optgroup_general, "associate_3mf",
L("Associate .3mf files to PrusaSlicer"),
@ -244,9 +242,6 @@ void PreferencesDialog::build()
L("Associate .stl files to PrusaSlicer"),
L("If enabled, sets PrusaSlicer as default application to open .stl files."),
app_config->get("associate_stl") == "1");
#if ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
}
#endif // ENABLE_REMOVE_ASSOCIATION_TO_FILE_FOR_WINDOWS_8_AND_LATER
#endif // _WIN32
m_optgroup_general->append_separator();

View File

@ -0,0 +1,490 @@
#include "libslic3r/Technologies.hpp"
#include "WinRegistry.hpp"
#ifdef _WIN32
#include <shlobj.h>
#include <wincrypt.h>
#include <winternl.h>
#include <sddl.h>
namespace Slic3r {
// Helper class which automatically closes the handle when
// going out of scope
class AutoHandle
{
HANDLE m_handle{ nullptr };
public:
explicit AutoHandle(HANDLE handle) : m_handle(handle) {}
~AutoHandle() { if (m_handle != nullptr) ::CloseHandle(m_handle); }
HANDLE get() { return m_handle; }
};
// Helper class which automatically closes the key when
// going out of scope
class AutoRegKey
{
HKEY m_key{ nullptr };
public:
explicit AutoRegKey(HKEY key) : m_key(key) {}
~AutoRegKey() { if (m_key != nullptr) ::RegCloseKey(m_key); }
HKEY get() { return m_key; }
};
// returns true if the given value is set/modified into Windows registry
static bool set_into_win_registry(HKEY hkeyHive, const wchar_t* pszVar, const wchar_t* pszValue)
{
// see as reference: https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association
wchar_t szValueCurrent[1000];
DWORD dwType;
DWORD dwSize = sizeof(szValueCurrent);
LSTATUS iRC = ::RegGetValueW(hkeyHive, pszVar, nullptr, RRF_RT_ANY, &dwType, szValueCurrent, &dwSize);
const bool bDidntExist = iRC == ERROR_FILE_NOT_FOUND;
if (iRC != ERROR_SUCCESS && !bDidntExist)
// an error occurred
return false;
if (!bDidntExist) {
if (dwType != REG_SZ)
// invalid type
return false;
if (::wcscmp(szValueCurrent, pszValue) == 0)
// value already set
return false;
}
DWORD dwDisposition;
HKEY hkey;
iRC = ::RegCreateKeyExW(hkeyHive, pszVar, 0, 0, 0, KEY_ALL_ACCESS, nullptr, &hkey, &dwDisposition);
bool ret = false;
if (iRC == ERROR_SUCCESS) {
iRC = ::RegSetValueExW(hkey, L"", 0, REG_SZ, (BYTE*)pszValue, (::wcslen(pszValue) + 1) * sizeof(wchar_t));
if (iRC == ERROR_SUCCESS)
ret = true;
}
RegCloseKey(hkey);
return ret;
}
static std::wstring get_current_user_string_sid()
{
HANDLE rawProcessToken;
if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY,
&rawProcessToken))
return L"";
AutoHandle processToken(rawProcessToken);
DWORD userSize = 0;
if (!(!::GetTokenInformation(processToken.get(), TokenUser, nullptr, 0, &userSize) &&
GetLastError() == ERROR_INSUFFICIENT_BUFFER))
return L"";
std::vector<unsigned char> userBytes(userSize);
if (!::GetTokenInformation(processToken.get(), TokenUser, userBytes.data(), userSize, &userSize))
return L"";
wchar_t* rawSid = nullptr;
if (!::ConvertSidToStringSidW(reinterpret_cast<PTOKEN_USER>(userBytes.data())->User.Sid, &rawSid))
return L"";
return rawSid;
}
/*
* Create the string which becomes the input to the UserChoice hash.
*
* @see generate_user_choice_hash() for parameters.
*
* @return The formatted string, empty string on failure.
*
* NOTE: This uses the format as of Windows 10 20H2 (latest as of this writing),
* used at least since 1803.
* There was at least one older version, not currently supported: On Win10 RTM
* (build 10240, aka 1507) the hash function is the same, but the timestamp and
* User Experience string aren't included; instead (for protocols) the string
* ends with the exe path. The changelog of SetUserFTA suggests the algorithm
* changed in 1703, so there may be two versions: before 1703, and 1703 to now.
*/
static std::wstring format_user_choice_string(const wchar_t* aExt, const wchar_t* aUserSid, const wchar_t* aProgId, SYSTEMTIME aTimestamp)
{
aTimestamp.wSecond = 0;
aTimestamp.wMilliseconds = 0;
FILETIME fileTime = { 0 };
if (!::SystemTimeToFileTime(&aTimestamp, &fileTime))
return L"";
// This string is built into Windows as part of the UserChoice hash algorithm.
// It might vary across Windows SKUs (e.g. Windows 10 vs. Windows Server), or
// across builds of the same SKU, but this is the only currently known
// version. There isn't any known way of deriving it, so we assume this
// constant value. If we are wrong, we will not be able to generate correct
// UserChoice hashes.
const wchar_t* userExperience =
L"User Choice set via Windows User Experience "
L"{D18B6DD5-6124-4341-9318-804003BAFA0B}";
const wchar_t* userChoiceFmt =
L"%s%s%s"
L"%08lx"
L"%08lx"
L"%s";
int userChoiceLen = _scwprintf(userChoiceFmt, aExt, aUserSid, aProgId,
fileTime.dwHighDateTime, fileTime.dwLowDateTime, userExperience);
userChoiceLen += 1; // _scwprintf does not include the terminator
std::wstring userChoice(userChoiceLen, L'\0');
_snwprintf_s(userChoice.data(), userChoiceLen, _TRUNCATE, userChoiceFmt, aExt,
aUserSid, aProgId, fileTime.dwHighDateTime, fileTime.dwLowDateTime, userExperience);
::CharLowerW(userChoice.data());
return userChoice;
}
// @return The MD5 hash of the input, nullptr on failure.
static std::vector<DWORD> cng_md5(const unsigned char* bytes, ULONG bytesLen) {
constexpr ULONG MD5_BYTES = 16;
constexpr ULONG MD5_DWORDS = MD5_BYTES / sizeof(DWORD);
std::vector<DWORD> hash;
BCRYPT_ALG_HANDLE hAlg = nullptr;
if (NT_SUCCESS(::BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_MD5_ALGORITHM, nullptr, 0))) {
BCRYPT_HASH_HANDLE hHash = nullptr;
// As of Windows 7 the hash handle will manage its own object buffer when
// pbHashObject is nullptr and cbHashObject is 0.
if (NT_SUCCESS(::BCryptCreateHash(hAlg, &hHash, nullptr, 0, nullptr, 0, 0))) {
// BCryptHashData promises not to modify pbInput.
if (NT_SUCCESS(::BCryptHashData(hHash, const_cast<unsigned char*>(bytes), bytesLen, 0))) {
hash.resize(MD5_DWORDS);
if (!NT_SUCCESS(::BCryptFinishHash(hHash, reinterpret_cast<unsigned char*>(hash.data()),
MD5_DWORDS * sizeof(DWORD), 0)))
hash.clear();
}
::BCryptDestroyHash(hHash);
}
::BCryptCloseAlgorithmProvider(hAlg, 0);
}
return hash;
}
static inline DWORD word_swap(DWORD v)
{
return (v >> 16) | (v << 16);
}
// @return The input bytes encoded as base64, nullptr on failure.
static std::wstring crypto_api_base64_encode(const unsigned char* bytes, DWORD bytesLen) {
DWORD base64Len = 0;
if (!::CryptBinaryToStringW(bytes, bytesLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, nullptr, &base64Len))
return L"";
std::wstring base64(base64Len, L'\0');
if (!::CryptBinaryToStringW(bytes, bytesLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, base64.data(), &base64Len))
return L"";
return base64;
}
/*
* Generate the UserChoice Hash.
*
* This implementation is based on the references listed above.
* It is organized to show the logic as clearly as possible, but at some
* point the reasoning is just "this is how it works".
*
* @param inputString A null-terminated string to hash.
*
* @return The base64-encoded hash, or empty string on failure.
*/
static std::wstring hash_string(const wchar_t* inputString)
{
auto inputBytes = reinterpret_cast<const unsigned char*>(inputString);
int inputByteCount = (::lstrlenW(inputString) + 1) * sizeof(wchar_t);
constexpr size_t DWORDS_PER_BLOCK = 2;
constexpr size_t BLOCK_SIZE = sizeof(DWORD) * DWORDS_PER_BLOCK;
// Incomplete blocks are ignored.
int blockCount = inputByteCount / BLOCK_SIZE;
if (blockCount == 0)
return L"";
// Compute an MD5 hash. md5[0] and md5[1] will be used as constant multipliers
// in the scramble below.
auto md5 = cng_md5(inputBytes, inputByteCount);
if (md5.empty())
return L"";
// The following loop effectively computes two checksums, scrambled like a
// hash after every DWORD is added.
// Constant multipliers for the scramble, one set for each DWORD in a block.
const DWORD C0s[DWORDS_PER_BLOCK][5] = {
{md5[0] | 1, 0xCF98B111uL, 0x87085B9FuL, 0x12CEB96DuL, 0x257E1D83uL},
{md5[1] | 1, 0xA27416F5uL, 0xD38396FFuL, 0x7C932B89uL, 0xBFA49F69uL} };
const DWORD C1s[DWORDS_PER_BLOCK][5] = {
{md5[0] | 1, 0xEF0569FBuL, 0x689B6B9FuL, 0x79F8A395uL, 0xC3EFEA97uL},
{md5[1] | 1, 0xC31713DBuL, 0xDDCD1F0FuL, 0x59C3AF2DuL, 0x35BD1EC9uL} };
// The checksums.
DWORD h0 = 0;
DWORD h1 = 0;
// Accumulated total of the checksum after each DWORD.
DWORD h0Acc = 0;
DWORD h1Acc = 0;
for (int i = 0; i < blockCount; ++i) {
for (int j = 0; j < DWORDS_PER_BLOCK; ++j) {
const DWORD* C0 = C0s[j];
const DWORD* C1 = C1s[j];
DWORD input;
memcpy(&input, &inputBytes[(i * DWORDS_PER_BLOCK + j) * sizeof(DWORD)], sizeof(DWORD));
h0 += input;
// Scramble 0
h0 *= C0[0];
h0 = word_swap(h0) * C0[1];
h0 = word_swap(h0) * C0[2];
h0 = word_swap(h0) * C0[3];
h0 = word_swap(h0) * C0[4];
h0Acc += h0;
h1 += input;
// Scramble 1
h1 = word_swap(h1) * C1[1] + h1 * C1[0];
h1 = (h1 >> 16) * C1[2] + h1 * C1[3];
h1 = word_swap(h1) * C1[4] + h1;
h1Acc += h1;
}
}
DWORD hash[2] = { h0 ^ h1, h0Acc ^ h1Acc };
return crypto_api_base64_encode(reinterpret_cast<const unsigned char*>(hash), sizeof(hash));
}
static std::wstring generate_user_choice_hash(const wchar_t* aExt, const wchar_t* aUserSid, const wchar_t* aProgId, SYSTEMTIME aTimestamp)
{
const std::wstring userChoice = format_user_choice_string(aExt, aUserSid, aProgId, aTimestamp);
if (userChoice.empty())
return L"";
return hash_string(userChoice.c_str());
}
static bool add_milliseconds_to_system_time(SYSTEMTIME& aSystemTime, ULONGLONG aIncrementMS)
{
FILETIME fileTime;
ULARGE_INTEGER fileTimeInt;
if (!::SystemTimeToFileTime(&aSystemTime, &fileTime))
return false;
fileTimeInt.LowPart = fileTime.dwLowDateTime;
fileTimeInt.HighPart = fileTime.dwHighDateTime;
// FILETIME is in units of 100ns.
fileTimeInt.QuadPart += aIncrementMS * 1000 * 10;
fileTime.dwLowDateTime = fileTimeInt.LowPart;
fileTime.dwHighDateTime = fileTimeInt.HighPart;
SYSTEMTIME tmpSystemTime;
if (!::FileTimeToSystemTime(&fileTime, &tmpSystemTime))
return false;
aSystemTime = tmpSystemTime;
return true;
}
// Compare two SYSTEMTIMEs as FILETIME after clearing everything
// below minutes.
static bool check_equal_minutes(SYSTEMTIME aSystemTime1, SYSTEMTIME aSystemTime2)
{
aSystemTime1.wSecond = 0;
aSystemTime1.wMilliseconds = 0;
aSystemTime2.wSecond = 0;
aSystemTime2.wMilliseconds = 0;
FILETIME fileTime1;
FILETIME fileTime2;
if (!::SystemTimeToFileTime(&aSystemTime1, &fileTime1) || !::SystemTimeToFileTime(&aSystemTime2, &fileTime2))
return false;
return (fileTime1.dwLowDateTime == fileTime2.dwLowDateTime) && (fileTime1.dwHighDateTime == fileTime2.dwHighDateTime);
}
static std::wstring get_association_key_path(const wchar_t* aExt)
{
const wchar_t* keyPathFmt;
if (aExt[0] == L'.')
keyPathFmt = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\%s";
else
keyPathFmt = L"SOFTWARE\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\%s";
int keyPathLen = _scwprintf(keyPathFmt, aExt);
keyPathLen += 1; // _scwprintf does not include the terminator
std::wstring keyPath(keyPathLen, '\0');
_snwprintf_s(keyPath.data(), keyPathLen, _TRUNCATE, keyPathFmt, aExt);
return keyPath;
}
/*
* Set an association with a UserChoice key
*
* Removes the old key, creates a new one with ProgID and Hash set to
* enable a new asociation.
*
* @param aExt File type or protocol to associate
* @param aProgID ProgID to use for the asociation
*
* @return true if successful, false on error.
*/
static bool set_user_choice(const wchar_t* aExt, const wchar_t* aProgID) {
const std::wstring aSid = get_current_user_string_sid();
if (aSid.empty())
return false;
SYSTEMTIME hashTimestamp;
::GetSystemTime(&hashTimestamp);
std::wstring hash = generate_user_choice_hash(aExt, aSid.c_str(), aProgID, hashTimestamp);
if (hash.empty())
return false;
// The hash changes at the end of each minute, so check that the hash should
// be the same by the time we're done writing.
const ULONGLONG kWriteTimingThresholdMilliseconds = 100;
// Generating the hash could have taken some time, so start from now.
SYSTEMTIME writeEndTimestamp;
::GetSystemTime(&writeEndTimestamp);
if (!add_milliseconds_to_system_time(writeEndTimestamp, +kWriteTimingThresholdMilliseconds))
return false;
if (!check_equal_minutes(hashTimestamp, writeEndTimestamp)) {
::Sleep(kWriteTimingThresholdMilliseconds * 2);
// For consistency, use the current time.
::GetSystemTime(&hashTimestamp);
hash = generate_user_choice_hash(aExt, aSid.c_str(), aProgID, hashTimestamp);
if (hash.empty())
return false;
}
const std::wstring assocKeyPath = get_association_key_path(aExt);
if (assocKeyPath.empty())
return false;
LSTATUS ls;
HKEY rawAssocKey;
ls = ::RegOpenKeyExW(HKEY_CURRENT_USER, assocKeyPath.data(), 0, KEY_READ | KEY_WRITE, &rawAssocKey);
if (ls != ERROR_SUCCESS)
return false;
AutoRegKey assocKey(rawAssocKey);
HKEY currUserChoiceKey;
ls = ::RegOpenKeyExW(assocKey.get(), L"UserChoice", 0, KEY_READ, &currUserChoiceKey);
if (ls == ERROR_SUCCESS) {
::RegCloseKey(currUserChoiceKey);
// When Windows creates this key, it is read-only (Deny Set Value), so we need
// to delete it first.
// We don't set any similar special permissions.
ls = ::RegDeleteKeyW(assocKey.get(), L"UserChoice");
if (ls != ERROR_SUCCESS)
return false;
}
HKEY rawUserChoiceKey;
ls = ::RegCreateKeyExW(assocKey.get(), L"UserChoice", 0, nullptr,
0 /* options */, KEY_READ | KEY_WRITE,
0 /* security attributes */, &rawUserChoiceKey,
nullptr);
if (ls != ERROR_SUCCESS)
return false;
AutoRegKey userChoiceKey(rawUserChoiceKey);
DWORD progIdByteCount = (::lstrlenW(aProgID) + 1) * sizeof(wchar_t);
ls = ::RegSetValueExW(userChoiceKey.get(), L"ProgID", 0, REG_SZ, reinterpret_cast<const unsigned char*>(aProgID), progIdByteCount);
if (ls != ERROR_SUCCESS)
return false;
DWORD hashByteCount = (::lstrlenW(hash.data()) + 1) * sizeof(wchar_t);
ls = ::RegSetValueExW(userChoiceKey.get(), L"Hash", 0, REG_SZ, reinterpret_cast<const unsigned char*>(hash.data()), hashByteCount);
if (ls != ERROR_SUCCESS)
return false;
return true;
}
static bool set_as_default_per_file_type(const std::wstring& extension, const std::wstring& prog_id)
{
const std::wstring reg_extension = get_association_key_path(extension.c_str());
if (reg_extension.empty())
return false;
bool needs_update = true;
bool modified = false;
HKEY rawAssocKey = nullptr;
LSTATUS res = ::RegOpenKeyExW(HKEY_CURRENT_USER, reg_extension.c_str(), 0, KEY_READ, &rawAssocKey);
AutoRegKey assoc_key(rawAssocKey);
if (res == ERROR_SUCCESS) {
DWORD data_size_bytes = 0;
res = ::RegGetValueW(assoc_key.get(), L"UserChoice", L"ProgId", RRF_RT_REG_SZ, nullptr, nullptr, &data_size_bytes);
if (res == ERROR_SUCCESS) {
// +1 in case dataSizeBytes was odd, +1 to ensure termination
DWORD data_size_chars = (data_size_bytes / sizeof(wchar_t)) + 2;
std::wstring curr_prog_id(data_size_chars, L'\0');
res = ::RegGetValueW(assoc_key.get(), L"UserChoice", L"ProgId", RRF_RT_REG_SZ, nullptr, curr_prog_id.data(), &data_size_bytes);
if (res == ERROR_SUCCESS) {
const std::wstring::size_type pos = curr_prog_id.find_first_of(L'\0');
if (pos != std::wstring::npos)
curr_prog_id = curr_prog_id.substr(0, pos);
needs_update = !boost::algorithm::iequals(curr_prog_id, prog_id);
}
}
}
if (needs_update)
modified = set_user_choice(extension.c_str(), prog_id.c_str());
return modified;
}
void associate_file_type(const std::wstring& extension, const std::wstring& prog_id, const std::wstring& prog_desc, bool set_as_default)
{
assert(!extension.empty() && extension.front() == L'.');
const std::wstring reg_extension = L"SOFTWARE\\Classes\\" + extension;
const std::wstring reg_prog_id = L"SOFTWARE\\Classes\\" + prog_id;
const std::wstring reg_prog_id_command = L"SOFTWARE\\Classes\\" + prog_id + +L"\\Shell\\Open\\Command";
wchar_t app_path[1040];
::GetModuleFileNameW(nullptr, app_path, sizeof(app_path));
const std::wstring prog_command = L"\"" + std::wstring(app_path) + L"\"" + L" \"%1\"";
bool modified = false;
modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_extension.c_str(), prog_id.c_str());
modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id.c_str(), prog_desc.c_str());
modified |= set_into_win_registry(HKEY_CURRENT_USER, reg_prog_id_command.c_str(), prog_command.c_str());
if (set_as_default)
modified |= set_as_default_per_file_type(extension, prog_id);
// notify Windows only when any of the values gets changed
if (modified)
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
}
} // namespace Slic3r
#endif // _WIN32

View File

@ -0,0 +1,23 @@
#ifndef slic3r_Utils_WinRegistry_hpp_
#define slic3r_Utils_WinRegistry_hpp_
#ifdef _WIN32
#include <string>
namespace Slic3r {
// Creates a Windows registry key for the files with the given 'extension' and associates them to the application 'prog_id'.
// If 'set_as_default' is true, the application 'prog_id' is set ad default application for the file type 'extension'.
// The file type registration implementation is based on code taken from:
// https://stackoverflow.com/questions/20245262/c-program-needs-an-file-association
// The set as default implementation is based on code taken from:
// https://hg.mozilla.org/mozilla-central/rev/e928b3e95a6c3b7257d0ba475fc2303bfbad1874
// https://hg.mozilla.org/releases/mozilla-release/diff/7e775ce432b599c6daf7ac379aa42f1e9b3b33ed/browser/components/shell/WindowsUserChoice.cpp
void associate_file_type(const std::wstring& extension, const std::wstring& prog_id, const std::wstring& prog_desc, bool set_as_default);
} // namespace Slic3r
#endif // _WIN32
#endif // slic3r_Utils_WinRegistry_hpp_

View File

@ -4,10 +4,9 @@
#include <thread>
#include "slic3r/GUI/Jobs/BoostThreadWorker.hpp"
#include "slic3r/GUI/Jobs/UIThreadWorker.hpp"
#include "slic3r/GUI/Jobs/ProgressIndicator.hpp"
//#include <boost/thread/thread.hpp>
struct Progress: Slic3r::ProgressIndicator {
int range = 100;
int pr = 0;
@ -19,17 +18,30 @@ struct Progress: Slic3r::ProgressIndicator {
int get_range() const override { return range; }
};
TEST_CASE("nullptr job should be ignored", "[Jobs]") {
Slic3r::GUI::BoostThreadWorker worker{std::make_unique<Progress>()};
using TestClasses = std::tuple< Slic3r::GUI::UIThreadWorker, Slic3r::GUI::BoostThreadWorker >;
TEMPLATE_LIST_TEST_CASE("Empty worker should not do anything", "[Jobs]", TestClasses) {
TestType worker{std::make_unique<Progress>()};
REQUIRE(worker.is_idle());
worker.wait_for_current_job();
worker.process_events();
REQUIRE(worker.is_idle());
}
TEMPLATE_LIST_TEST_CASE("nullptr job should be ignored", "[Jobs]", TestClasses) {
TestType worker{std::make_unique<Progress>()};
worker.push(nullptr);
REQUIRE(worker.is_idle());
}
TEST_CASE("State should not be idle while running a job", "[Jobs]") {
TEMPLATE_LIST_TEST_CASE("State should not be idle while running a job", "[Jobs]", TestClasses) {
using namespace Slic3r;
using namespace Slic3r::GUI;
BoostThreadWorker worker{std::make_unique<Progress>(), "worker_thread"};
TestType worker{std::make_unique<Progress>(), "worker_thread"};
queue_job(worker, [&worker](Job::Ctl &ctl) {
ctl.call_on_main_thread([&worker] {
@ -42,11 +54,11 @@ TEST_CASE("State should not be idle while running a job", "[Jobs]") {
REQUIRE(worker.is_idle());
}
TEST_CASE("Status messages should be received by the main thread during job execution", "[Jobs]") {
TEMPLATE_LIST_TEST_CASE("Status messages should be received by the main thread during job execution", "[Jobs]", TestClasses) {
using namespace Slic3r;
using namespace Slic3r::GUI;
auto pri = std::make_shared<Progress>();
BoostThreadWorker worker{pri};
TestType worker{pri};
queue_job(worker, [](Job::Ctl &ctl){
for (int s = 0; s <= 100; ++s) {
@ -60,12 +72,12 @@ TEST_CASE("Status messages should be received by the main thread during job exec
REQUIRE(pri->statustxt == "Running");
}
TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]") {
TEMPLATE_LIST_TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]", TestClasses) {
using namespace Slic3r;
using namespace Slic3r::GUI;
auto pri = std::make_shared<Progress>();
BoostThreadWorker worker{pri};
TestType worker{pri};
queue_job(
worker,
@ -88,12 +100,12 @@ TEST_CASE("Cancellation should be recognized be the worker", "[Jobs]") {
REQUIRE(pri->pr != 100);
}
TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]") {
TEMPLATE_LIST_TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]", TestClasses) {
using namespace Slic3r;
using namespace Slic3r::GUI;
auto pri = std::make_shared<Progress>();
BoostThreadWorker worker{pri};
TestType worker{pri};
std::array<bool, 4> jobres = {false, false, false, false};
@ -128,12 +140,12 @@ TEST_CASE("cancel_all should remove all pending jobs", "[Jobs]") {
REQUIRE(jobres[3] == false);
}
TEST_CASE("Exception should be properly forwarded to finalize()", "[Jobs]") {
TEMPLATE_LIST_TEST_CASE("Exception should be properly forwarded to finalize()", "[Jobs]", TestClasses) {
using namespace Slic3r;
using namespace Slic3r::GUI;
auto pri = std::make_shared<Progress>();
BoostThreadWorker worker{pri};
TestType worker{pri};
queue_job(
worker, [](Job::Ctl &) { throw std::runtime_error("test"); },