mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-26 18:31:11 +00:00
Code cleanup, dedup, update unit tests
This commit is contained in:
@@ -178,7 +178,7 @@ struct SettingAction : AppAction
|
||||
{
|
||||
std::string opt_key;
|
||||
Preset::Type type;
|
||||
std::wstring category; // localized category, forwarded to jump_to_option
|
||||
std::wstring category; // English category, forwarded to jump_to_option (it localizes)
|
||||
|
||||
static std::string id_for(const std::string& opt_key, Preset::Type type)
|
||||
{ return std::string(kSettingPrefix) + ":" + opt_key + ":" + std::to_string(int(type)); }
|
||||
@@ -556,7 +556,7 @@ void ActionRegistry::materialize_setting_actions()
|
||||
// title = the option leaf name (last label segment); group stays empty so the source path
|
||||
// (above) is the single display/search breadcrumb rather than being duplicated.
|
||||
auto action = std::make_unique<SettingAction>(opt.opt_key(), opt.type, boost::nowide::narrow(label_w), std::string(),
|
||||
opt.category_local, boost::nowide::narrow(path), opt.mode);
|
||||
opt.category, boost::nowide::narrow(path), opt.mode);
|
||||
|
||||
// Tile pictogram = the icon of the setting's own group header (e.g. Advanced -> param_advanced),
|
||||
// the one shown next to it in the page. Fall back to the page/category icon for groups
|
||||
|
||||
@@ -737,7 +737,9 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
||||
evt.Skip(); // let the focused control keep Space
|
||||
return;
|
||||
}
|
||||
wxGetApp().open_speed_dial();
|
||||
// Defer out of the native key-event stack: open_speed_dial() may create a WebView and
|
||||
// run script, the same window work the codebase avoids doing on native callbacks.
|
||||
this->CallAfter([this] { wxGetApp().open_speed_dial(); });
|
||||
return;
|
||||
}
|
||||
if (evt.CmdDown() && evt.GetKeyCode() == 'R') { if (m_slice_enable) { wxGetApp().plater()->update(true, true); wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE)); this->m_tabpanel->SelectPageByName(TAB_ID_PREVIEW); } return; }
|
||||
|
||||
@@ -528,68 +528,37 @@ bool install_local_plugin_package(const boost::filesystem::path& package_file, w
|
||||
{
|
||||
struct Result
|
||||
{
|
||||
std::mutex mutex;
|
||||
bool ok = false;
|
||||
std::mutex mutex;
|
||||
bool ok = false;
|
||||
std::string error;
|
||||
};
|
||||
auto state = std::make_shared<Result>();
|
||||
|
||||
wxProgressDialog* progress = new wxProgressDialog(_L("Installing plugin"), _L("Installing plugin") + ": " + package_name,
|
||||
100, parent, wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME);
|
||||
wxTimer* timer = new wxTimer();
|
||||
timer->Bind(wxEVT_TIMER, [progress](wxTimerEvent&) {
|
||||
if (progress)
|
||||
progress->Pulse();
|
||||
});
|
||||
timer->Start(100);
|
||||
|
||||
// finished/loop live on the heap: the worker's completion callback is posted to the UI loop
|
||||
// and can still fire after this stack frame is gone, so it must not reference locals.
|
||||
struct WaitState
|
||||
{
|
||||
bool finished = false;
|
||||
wxEventLoop loop;
|
||||
};
|
||||
auto wait = std::make_shared<WaitState>();
|
||||
|
||||
std::thread([state, package_file, wait]() mutable {
|
||||
std::string error;
|
||||
bool ok = false;
|
||||
try {
|
||||
ok = PluginManager::instance().install_plugin(package_file, error);
|
||||
} catch (const std::exception& ex) {
|
||||
error = ex.what();
|
||||
} catch (...) {
|
||||
error = "Unknown error";
|
||||
}
|
||||
if (ok) {
|
||||
// Reflect the new package in discovery/cloud metadata without blocking the caller.
|
||||
try { refresh_plugin_metadata_blocking(kUseCurrentCloudMeta); } catch (...) {}
|
||||
}
|
||||
{
|
||||
detail::run_wait_with_progress(
|
||||
[state, package_file]() {
|
||||
std::string error;
|
||||
bool ok = false;
|
||||
try {
|
||||
ok = PluginManager::instance().install_plugin(package_file, error);
|
||||
} catch (const std::exception& ex) {
|
||||
error = ex.what();
|
||||
} catch (...) {
|
||||
error = "Unknown error";
|
||||
}
|
||||
if (ok) {
|
||||
// Reflect the new package in discovery/cloud metadata without blocking the caller.
|
||||
try { refresh_plugin_metadata_blocking(kUseCurrentCloudMeta); } catch (...) {}
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(state->mutex);
|
||||
state->ok = ok;
|
||||
state->error = std::move(error);
|
||||
}
|
||||
if (!wxTheApp)
|
||||
return;
|
||||
wxTheApp->CallAfter([wait]() {
|
||||
wait->finished = true;
|
||||
if (wait->loop.IsRunning())
|
||||
wait->loop.Exit();
|
||||
});
|
||||
}).detach();
|
||||
|
||||
if (!wait->finished)
|
||||
wait->loop.Run();
|
||||
|
||||
timer->Stop();
|
||||
delete timer;
|
||||
progress->Destroy();
|
||||
},
|
||||
parent, _L("Installing plugin"), _L("Installing plugin") + ": " + package_name, 100,
|
||||
wxPD_APP_MODAL | wxPD_AUTO_HIDE | wxPD_ELAPSED_TIME, /*alive=*/nullptr, /*restore=*/{});
|
||||
|
||||
std::lock_guard<std::mutex> lock(state->mutex);
|
||||
installed = state->ok;
|
||||
error = std::move(state->error);
|
||||
installed = state->ok;
|
||||
error = std::move(state->error);
|
||||
}
|
||||
|
||||
if (!installed) {
|
||||
@@ -980,6 +949,9 @@ bool PluginsDialog::install_plugin_package(const std::string& package_path)
|
||||
const boost::filesystem::path package_file(package_path);
|
||||
wxString message;
|
||||
const bool installed = install_local_plugin_package(package_file, this, message);
|
||||
// The helper's overwrite prompt and progress dialog can push this webview behind; re-raise it
|
||||
// once, after both have closed (the speed-dial path parents to the mainframe instead).
|
||||
restore_z_order();
|
||||
|
||||
// The shared helper reports a user-cancelled overwrite with an empty message: stay silent.
|
||||
if (message.IsEmpty()) {
|
||||
|
||||
+165
-124
@@ -52,6 +52,168 @@ void open_plugin_hub();
|
||||
// confirmation; on a user-cancelled overwrite it is empty; on failure it carries the reason.
|
||||
bool install_local_plugin_package(const boost::filesystem::path& package_file, wxWindow* parent, wxString& message);
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Shared worker + modal-progress machinery: pulse a progress dialog while `run` executes on a
|
||||
// detached worker, then run `on_finish` back on the UI thread. `alive`, when non-null, gates both
|
||||
// the pulse and `on_finish` so a worker outliving its dialog can't touch freed windows; pass null
|
||||
// for a dialog-independent caller. `restore` runs after the progress dialog is destroyed and before
|
||||
// `on_finish`, so a webview host can re-raise itself. `finish_after_dialog_destroyed` still calls
|
||||
// `on_finish` (without touching the dialog) when the host died, so a waiting loop can exit.
|
||||
template<typename Run, typename OnFinish>
|
||||
void run_off_thread_with_progress(Run&& run,
|
||||
OnFinish&& on_finish,
|
||||
wxWindow* parent,
|
||||
const wxString& title,
|
||||
const wxString& message,
|
||||
int maximum,
|
||||
int style,
|
||||
std::shared_ptr<std::atomic<bool>> alive,
|
||||
bool finish_after_dialog_destroyed,
|
||||
std::function<void()> restore)
|
||||
{
|
||||
wxProgressDialog* progress = new wxProgressDialog(title, message, maximum, parent, style);
|
||||
wxTimer* timer = new wxTimer();
|
||||
|
||||
timer->Bind(wxEVT_TIMER, [alive, progress, message](wxTimerEvent&) {
|
||||
if ((!alive || alive->load(std::memory_order_acquire)) && progress)
|
||||
progress->Pulse(message);
|
||||
});
|
||||
|
||||
timer->Start(100);
|
||||
|
||||
std::thread([alive,
|
||||
progress,
|
||||
timer,
|
||||
run = std::forward<Run>(run),
|
||||
on_finish = std::forward<OnFinish>(on_finish),
|
||||
finish_after_dialog_destroyed,
|
||||
restore = std::move(restore)]() mutable {
|
||||
try {
|
||||
run();
|
||||
} catch (const std::exception& ex) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Plugin dialog worker failed: " << ex.what();
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Plugin dialog worker failed with an unknown exception";
|
||||
}
|
||||
|
||||
if (wxTheApp == nullptr)
|
||||
return;
|
||||
|
||||
wxTheApp->CallAfter([alive,
|
||||
progress,
|
||||
timer,
|
||||
on_finish = std::move(on_finish),
|
||||
finish_after_dialog_destroyed,
|
||||
restore = std::move(restore)]() mutable {
|
||||
timer->Stop();
|
||||
delete timer;
|
||||
|
||||
if (!alive || alive->load(std::memory_order_acquire)) {
|
||||
progress->Destroy();
|
||||
if (restore)
|
||||
restore();
|
||||
on_finish();
|
||||
} else if (finish_after_dialog_destroyed) {
|
||||
on_finish();
|
||||
}
|
||||
});
|
||||
}).detach();
|
||||
}
|
||||
|
||||
// Wait for a worker behind a progress dialog, returning its result (or rethrowing). The waiting
|
||||
// loop stays responsive because it pumps the event loop the worker posts its completion into.
|
||||
template<typename Run>
|
||||
std::invoke_result_t<std::decay_t<Run>&> run_wait_with_progress(Run&& run,
|
||||
wxWindow* parent,
|
||||
const wxString& title,
|
||||
const wxString& message,
|
||||
int maximum,
|
||||
int style,
|
||||
std::shared_ptr<std::atomic<bool>> alive,
|
||||
std::function<void()> restore)
|
||||
{
|
||||
using Result = std::invoke_result_t<std::decay_t<Run>&>;
|
||||
|
||||
bool finished = false;
|
||||
wxEventLoop loop;
|
||||
auto on_finish = [&finished, &loop]() {
|
||||
finished = true;
|
||||
if (loop.IsRunning())
|
||||
loop.Exit();
|
||||
};
|
||||
|
||||
if constexpr (std::is_void_v<Result>) {
|
||||
struct WaitState
|
||||
{
|
||||
std::mutex mutex;
|
||||
std::exception_ptr exception;
|
||||
};
|
||||
|
||||
auto state = std::make_shared<WaitState>();
|
||||
run_off_thread_with_progress(
|
||||
[run = std::forward<Run>(run), state]() mutable {
|
||||
try {
|
||||
run();
|
||||
} catch (...) {
|
||||
std::lock_guard<std::mutex> lock(state->mutex);
|
||||
state->exception = std::current_exception();
|
||||
}
|
||||
},
|
||||
on_finish, parent, title, message, maximum, style, std::move(alive), /*finish_after_dialog_destroyed=*/true, std::move(restore));
|
||||
|
||||
if (!finished)
|
||||
loop.Run();
|
||||
|
||||
std::exception_ptr exception;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state->mutex);
|
||||
exception = state->exception;
|
||||
}
|
||||
if (exception)
|
||||
std::rethrow_exception(exception);
|
||||
} else {
|
||||
using StoredResult = std::decay_t<Result>;
|
||||
struct WaitState
|
||||
{
|
||||
std::mutex mutex;
|
||||
std::optional<StoredResult> result;
|
||||
std::exception_ptr exception;
|
||||
};
|
||||
|
||||
auto state = std::make_shared<WaitState>();
|
||||
run_off_thread_with_progress(
|
||||
[run = std::forward<Run>(run), state]() mutable {
|
||||
try {
|
||||
StoredResult result = run();
|
||||
std::lock_guard<std::mutex> lock(state->mutex);
|
||||
state->result.emplace(std::move(result));
|
||||
} catch (...) {
|
||||
std::lock_guard<std::mutex> lock(state->mutex);
|
||||
state->exception = std::current_exception();
|
||||
}
|
||||
},
|
||||
on_finish, parent, title, message, maximum, style, std::move(alive), /*finish_after_dialog_destroyed=*/true, std::move(restore));
|
||||
|
||||
if (!finished)
|
||||
loop.Run();
|
||||
|
||||
std::optional<StoredResult> result;
|
||||
std::exception_ptr exception;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state->mutex);
|
||||
if (state->result)
|
||||
result.emplace(std::move(*state->result));
|
||||
exception = state->exception;
|
||||
}
|
||||
if (exception)
|
||||
std::rethrow_exception(exception);
|
||||
return std::move(*result);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
class PluginsDialog : public Slic3r::GUI::WebViewHostDialog
|
||||
{
|
||||
public:
|
||||
@@ -128,53 +290,8 @@ private:
|
||||
int style = wxPD_APP_MODAL | wxPD_AUTO_HIDE,
|
||||
bool finish_after_dialog_destroyed = false)
|
||||
{
|
||||
const auto alive = m_alive;
|
||||
wxProgressDialog* progress = new wxProgressDialog(title, message, maximum, this, style);
|
||||
wxTimer* timer = new wxTimer();
|
||||
|
||||
timer->Bind(wxEVT_TIMER, [alive, progress, message](wxTimerEvent&) {
|
||||
if (alive->load(std::memory_order_acquire) && progress)
|
||||
progress->Pulse(message);
|
||||
});
|
||||
|
||||
timer->Start(100);
|
||||
|
||||
std::thread([this,
|
||||
alive,
|
||||
progress,
|
||||
timer,
|
||||
run = std::forward<Run>(run),
|
||||
on_finish = std::forward<OnFinish>(on_finish),
|
||||
finish_after_dialog_destroyed]() mutable {
|
||||
try {
|
||||
run();
|
||||
} catch (const std::exception& ex) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Plugin dialog worker failed: " << ex.what();
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Plugin dialog worker failed with an unknown exception";
|
||||
}
|
||||
|
||||
if (wxTheApp == nullptr)
|
||||
return;
|
||||
|
||||
wxTheApp->CallAfter([this,
|
||||
alive,
|
||||
progress,
|
||||
timer,
|
||||
on_finish = std::move(on_finish),
|
||||
finish_after_dialog_destroyed]() mutable {
|
||||
timer->Stop();
|
||||
delete timer;
|
||||
|
||||
if (alive->load(std::memory_order_acquire)) {
|
||||
progress->Destroy();
|
||||
restore_z_order();
|
||||
on_finish();
|
||||
} else if (finish_after_dialog_destroyed) {
|
||||
on_finish();
|
||||
}
|
||||
});
|
||||
}).detach();
|
||||
detail::run_off_thread_with_progress(std::forward<Run>(run), std::forward<OnFinish>(on_finish), this, title, message, maximum, style,
|
||||
m_alive, finish_after_dialog_destroyed, [this] { restore_z_order(); });
|
||||
}
|
||||
|
||||
template<typename Run>
|
||||
@@ -184,83 +301,7 @@ private:
|
||||
int maximum = 100,
|
||||
int style = wxPD_APP_MODAL | wxPD_AUTO_HIDE)
|
||||
{
|
||||
using Result = std::invoke_result_t<std::decay_t<Run>&>;
|
||||
|
||||
bool finished = false;
|
||||
wxEventLoop loop;
|
||||
auto on_finish = [&finished, &loop]() {
|
||||
finished = true;
|
||||
if (loop.IsRunning())
|
||||
loop.Exit();
|
||||
};
|
||||
|
||||
if constexpr (std::is_void_v<Result>) {
|
||||
struct WaitState
|
||||
{
|
||||
std::mutex mutex;
|
||||
std::exception_ptr exception;
|
||||
};
|
||||
|
||||
auto state = std::make_shared<WaitState>();
|
||||
run_with_dialog(
|
||||
[run = std::forward<Run>(run), state]() mutable {
|
||||
try {
|
||||
run();
|
||||
} catch (...) {
|
||||
std::lock_guard<std::mutex> lock(state->mutex);
|
||||
state->exception = std::current_exception();
|
||||
}
|
||||
},
|
||||
on_finish, title, message, maximum, style, true);
|
||||
|
||||
if (!finished)
|
||||
loop.Run();
|
||||
|
||||
std::exception_ptr exception;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state->mutex);
|
||||
exception = state->exception;
|
||||
}
|
||||
if (exception)
|
||||
std::rethrow_exception(exception);
|
||||
} else {
|
||||
using StoredResult = std::decay_t<Result>;
|
||||
struct WaitState
|
||||
{
|
||||
std::mutex mutex;
|
||||
std::optional<StoredResult> result;
|
||||
std::exception_ptr exception;
|
||||
};
|
||||
|
||||
auto state = std::make_shared<WaitState>();
|
||||
run_with_dialog(
|
||||
[run = std::forward<Run>(run), state]() mutable {
|
||||
try {
|
||||
StoredResult result = run();
|
||||
std::lock_guard<std::mutex> lock(state->mutex);
|
||||
state->result.emplace(std::move(result));
|
||||
} catch (...) {
|
||||
std::lock_guard<std::mutex> lock(state->mutex);
|
||||
state->exception = std::current_exception();
|
||||
}
|
||||
},
|
||||
on_finish, title, message, maximum, style, true);
|
||||
|
||||
if (!finished)
|
||||
loop.Run();
|
||||
|
||||
std::optional<StoredResult> result;
|
||||
std::exception_ptr exception;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state->mutex);
|
||||
if (state->result)
|
||||
result.emplace(std::move(*state->result));
|
||||
exception = state->exception;
|
||||
}
|
||||
if (exception)
|
||||
std::rethrow_exception(exception);
|
||||
return std::move(*result);
|
||||
}
|
||||
return detail::run_wait_with_progress(std::forward<Run>(run), this, title, message, maximum, style, m_alive, [this] { restore_z_order(); });
|
||||
}
|
||||
|
||||
std::function<void()> m_open_terminal_dlg_fn;
|
||||
|
||||
@@ -143,17 +143,24 @@ void OptionsSearcher::append_options(DynamicPrintConfig *config, Preset::Type ty
|
||||
}
|
||||
}
|
||||
|
||||
inline void OptionsSearcher::sort_options()
|
||||
void OptionsSearcher::sort_options()
|
||||
{
|
||||
std::sort(options.begin(), options.end(), [](const Option &o1, const Option &o2) { return o1.label < o2.label; });
|
||||
Option * last = nullptr;
|
||||
for (auto& opt : options) {
|
||||
if (last && last->label == opt.label && last->group == opt.group && last->type == opt.type && last->category != opt.category) {
|
||||
last->multi_category = true;
|
||||
opt.multi_category = true;
|
||||
// Both views are label-sorted and multi_category-marked. They are separate consumers (the sidebar
|
||||
// search and the Speed Dial); keeping them in sync here prevents the all-modes view from silently
|
||||
// diverging in order or flags.
|
||||
auto sort_and_mark = [](std::vector<Option> &v) {
|
||||
std::sort(v.begin(), v.end(), [](const Option &o1, const Option &o2) { return o1.label < o2.label; });
|
||||
Option *last = nullptr;
|
||||
for (auto &opt : v) {
|
||||
if (last && last->label == opt.label && last->group == opt.group && last->type == opt.type && last->category != opt.category) {
|
||||
last->multi_category = true;
|
||||
opt.multi_category = true;
|
||||
}
|
||||
last = &opt;
|
||||
}
|
||||
last = &opt;
|
||||
}
|
||||
};
|
||||
sort_and_mark(options);
|
||||
sort_and_mark(options_all_modes);
|
||||
}
|
||||
|
||||
// Mark a string using ColorMarkerStart and ColorMarkerEnd symbols
|
||||
|
||||
Reference in New Issue
Block a user