mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-05 01:02:08 +00:00
Merge branch 'main' into feature/bambu_printer_housekeeping
# Conflicts: # tests/slic3rutils/CMakeLists.txt
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#include "ExportPresetBundleDialog.hpp"
|
||||
#include "OrcaCloudServiceAgent.hpp"
|
||||
#include "libslic3r/Technologies.hpp"
|
||||
#include "libslic3r/Platform.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI_Init.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
@@ -13,6 +14,7 @@
|
||||
#include <boost/log/detail/native_typeof.hpp>
|
||||
#include <libslic3r/Config.hpp>
|
||||
#include <mutex>
|
||||
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
||||
#include <wx/event.h>
|
||||
|
||||
// Localization headers: include libslic3r version first so everything in this file
|
||||
@@ -76,6 +78,9 @@
|
||||
#include "libslic3r/miniz_extension.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/Color.hpp"
|
||||
#include "slic3r/plugin/PluginManager.hpp"
|
||||
#include "slic3r/plugin/host/PluginHostUi.hpp"
|
||||
#include "slic3r/plugin/PythonInterpreter.hpp"
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_Utils.hpp"
|
||||
@@ -135,6 +140,10 @@
|
||||
#include "slic3r/Utils/BBLNetworkPlugin.hpp"
|
||||
#include "slic3r/Utils/bambu_networking.hpp"
|
||||
|
||||
#include "PluginsDialog.hpp"
|
||||
#include "SpeedDialDialog.hpp"
|
||||
#include "TerminalDialog.hpp"
|
||||
|
||||
//#ifdef WIN32
|
||||
//#include "BaseException.h"
|
||||
//#endif
|
||||
@@ -1113,6 +1122,12 @@ void GUI_App::shutdown()
|
||||
if (m_is_recreating_gui) return;
|
||||
stop_http_server();
|
||||
set_closing(true);
|
||||
Slic3r::PluginManager::instance().set_shutting_down();
|
||||
|
||||
if (m_agent)
|
||||
m_agent->set_printer_agent(nullptr);
|
||||
NetworkAgentFactory::clear_printer_agent_cache();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "GUI_App::shutdown exit";
|
||||
}
|
||||
|
||||
@@ -2272,6 +2287,14 @@ void GUI_App::init_networking_callbacks()
|
||||
GUI_App::~GUI_App()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": enter");
|
||||
|
||||
if (m_agent)
|
||||
m_agent->set_printer_agent(nullptr);
|
||||
NetworkAgentFactory::clear_printer_agent_cache();
|
||||
|
||||
Slic3r::PluginManager::instance().shutdown();
|
||||
Slic3r::PythonInterpreter::instance().shutdown();
|
||||
|
||||
if (app_config != nullptr) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": destroy app_config");
|
||||
delete app_config;
|
||||
@@ -2709,6 +2732,55 @@ std::string get_system_info()
|
||||
return out.str();
|
||||
}
|
||||
|
||||
// wx/app-level plugin wiring, kept in one place: subscriptions to plugin
|
||||
// loader events that drive GUI policy (plugins dialog refresh, network-agent
|
||||
// registration, plate revalidation). The libslic3r dispatch hooks are NOT
|
||||
// wired here -- PluginManager::initialize() installs those via
|
||||
// plugin_hooks::install().
|
||||
void GUI_App::init_plugin_gui_wiring()
|
||||
{
|
||||
PluginManager& plugin_mgr = PluginManager::instance();
|
||||
|
||||
auto refresh_plugins_dialog = [] {
|
||||
if (!wxTheApp)
|
||||
return;
|
||||
|
||||
GUI_App* app = &GUI::wxGetApp();
|
||||
if (app->is_closing())
|
||||
return;
|
||||
|
||||
app->CallAfter([app] {
|
||||
if (!app->is_closing() && app->m_plugins_dlg)
|
||||
app->m_plugins_dlg->update_plugin_dialog_ui();
|
||||
});
|
||||
};
|
||||
|
||||
plugin_mgr.subscribe_on_unload_callback(PluginHostUi::close_windows_for_plugin);
|
||||
plugin_mgr.subscribe_on_load_callback([refresh_plugins_dialog](const std::string&) { refresh_plugins_dialog(); });
|
||||
plugin_mgr.subscribe_on_unload_callback([refresh_plugins_dialog](const std::string&) { refresh_plugins_dialog(); });
|
||||
plugin_mgr.subscribe_on_load_callback(NetworkAgentFactory::register_python_plugin);
|
||||
plugin_mgr.subscribe_on_unload_callback(NetworkAgentFactory::deregister_python_plugin);
|
||||
plugin_mgr.subscribe_on_capability_load_callback(
|
||||
[refresh_plugins_dialog](const PluginCapabilityId& capability) {
|
||||
if (capability.type == PluginCapabilityType::PrinterConnection)
|
||||
NetworkAgentFactory::register_python_printer_agent(capability.plugin_key, capability.name);
|
||||
refresh_plugins_dialog();
|
||||
// A newly loaded capability may satisfy a missing-plugin notification; re-validate the
|
||||
// current plate (on the UI thread) so the notification clears once its plugin is available.
|
||||
if (wxTheApp && !wxGetApp().is_closing())
|
||||
wxGetApp().CallAfter([]() {
|
||||
if (Plater* plater = wxGetApp().plater())
|
||||
plater->revalidate_current_plate_if_plugins_missing();
|
||||
});
|
||||
});
|
||||
plugin_mgr.subscribe_on_capability_unload_callback(
|
||||
[refresh_plugins_dialog](const PluginCapabilityId& capability) {
|
||||
if (capability.type == PluginCapabilityType::PrinterConnection)
|
||||
NetworkAgentFactory::deregister_python_printer_agent(capability.plugin_key, capability.name);
|
||||
refresh_plugins_dialog();
|
||||
});
|
||||
}
|
||||
|
||||
bool GUI_App::on_init_inner()
|
||||
{
|
||||
wxLog::SetActiveTarget(new wxBoostLog());
|
||||
@@ -3102,13 +3174,70 @@ bool GUI_App::on_init_inner()
|
||||
wxMessageBox("Force using legacy bambu networking plugin because debugger is attached! If the app terminates itself immediately, please delete installed plugin and try again!");
|
||||
}
|
||||
} */
|
||||
|
||||
copy_network_if_available();
|
||||
|
||||
if (scrn) {
|
||||
scrn->SetText(_L("Loading Plugins") + dots, 20);
|
||||
wxYield();
|
||||
}
|
||||
|
||||
on_init_network();
|
||||
|
||||
// Initialize plugins after network then register on_load callbacks so once the plugin loads finish, it gets registered automatically.
|
||||
// initialize() also installs the libslic3r hooks (capability resolver,
|
||||
// slicing-pipeline dispatcher) via plugin_hooks::install() -- no
|
||||
// per-capability wiring belongs here.
|
||||
PluginManager& plugin_mgr = PluginManager::instance();
|
||||
plugin_mgr.initialize();
|
||||
|
||||
// Set cloud plugin directory from previous session so cloud-installed
|
||||
// plugins are discovered even before the network agent is ready.
|
||||
const std::string preset_folder = app_config->get("preset_folder");
|
||||
if (!preset_folder.empty()) {
|
||||
plugin_mgr.set_cloud_user(preset_folder);
|
||||
}
|
||||
|
||||
plugin_mgr.discover_plugins(false, true);
|
||||
|
||||
init_plugin_gui_wiring();
|
||||
|
||||
// Subscribe to the plugin loader and enumerate current actions (UI thread, once).
|
||||
m_action_registry.init();
|
||||
|
||||
for (const std::string& plugin_key : plugin_mgr.get_enabled_plugin_keys()) {
|
||||
if (!plugin_mgr.is_plugin_loaded(plugin_key)) {
|
||||
plugin_mgr.load_plugin(plugin_key, false);
|
||||
BOOST_LOG_TRIVIAL(info) << "Auto-loading plugin on startup: " << plugin_key;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_agent)
|
||||
plugin_mgr.set_cloud_agent(std::dynamic_pointer_cast<OrcaCloudServiceAgent>(m_agent->get_cloud_agent()));
|
||||
|
||||
if (m_agent && m_agent->is_user_login()) {
|
||||
enable_user_preset_folder(true);
|
||||
plugin_mgr.set_cloud_user(m_agent->get_user_id());
|
||||
// If there is a user logged in we do an immediate sync.
|
||||
std::vector<std::string> not_found, unauthorized;
|
||||
plugin_mgr.fetch_plugins_from_cloud(¬_found, &unauthorized);
|
||||
if (plater()) {
|
||||
for (const auto& uuid : not_found) {
|
||||
plater()->get_notification_manager()->push_notification(
|
||||
NotificationType::CustomNotification,
|
||||
NotificationManager::NotificationLevel::RegularNotificationLevel,
|
||||
format(_L("Plugin %s is no longer available."), uuid));
|
||||
}
|
||||
for (const auto& uuid : unauthorized) {
|
||||
plater()->get_notification_manager()->push_notification(
|
||||
NotificationType::CustomNotification,
|
||||
NotificationManager::NotificationLevel::RegularNotificationLevel,
|
||||
format(_L("Plugin %s access is unauthorized."), uuid));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
enable_user_preset_folder(false);
|
||||
plugin_mgr.set_cloud_user("");
|
||||
}
|
||||
|
||||
// BBS if load user preset failed
|
||||
@@ -3680,69 +3809,69 @@ void GUI_App::switch_printer_agent()
|
||||
|
||||
// Read printer_agent from config, falling back to default
|
||||
std::string effective_agent_id = ORCA_PRINTER_AGENT_ID;
|
||||
std::string cloud_agent_id = ORCA_CLOUD_PROVIDER;
|
||||
if (preset_bundle->is_bbl_vendor()) {
|
||||
if (preset_bundle->is_bbl_vendor())
|
||||
effective_agent_id = BBL_PRINTER_AGENT_ID;
|
||||
cloud_agent_id = BBL_CLOUD_PROVIDER;
|
||||
} else {
|
||||
const DynamicPrintConfig& config = preset_bundle->printers.get_edited_preset().config;
|
||||
if (config.has("printer_agent")) {
|
||||
const std::string& value = config.option<ConfigOptionString>("printer_agent")->value;
|
||||
if (!value.empty())
|
||||
effective_agent_id = value;
|
||||
}
|
||||
|
||||
const DynamicPrintConfig& config = preset_bundle->printers.get_edited_preset().config;
|
||||
if (config.has("printer_agent")) {
|
||||
const std::string& value = config.option<ConfigOptionString>("printer_agent")->value;
|
||||
if (!value.empty())
|
||||
effective_agent_id = value;
|
||||
}
|
||||
|
||||
// Check if agent is registered
|
||||
if (!NetworkAgentFactory::is_printer_agent_registered(effective_agent_id)) {
|
||||
const PrinterAgentInfo* agent_info_ptr = NetworkAgentFactory::get_printer_agent_info(effective_agent_id);
|
||||
if (!agent_info_ptr) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": unregistered agent ID '" << effective_agent_id
|
||||
<< "', keeping current agent";
|
||||
// Keep current agent, don't switch
|
||||
return;
|
||||
}
|
||||
const PrinterAgentInfo agent_info = *agent_info_ptr;
|
||||
|
||||
std::string current_agent_id;
|
||||
if (m_agent->get_printer_agent())
|
||||
current_agent_id = m_agent->get_printer_agent()->get_agent_info().id;
|
||||
std::string log_dir = data_dir();
|
||||
std::string cloud_agent_id = agent_info.id == BBL_PRINTER_AGENT_ID ? BBL_CLOUD_PROVIDER : ORCA_CLOUD_PROVIDER;
|
||||
std::shared_ptr<ICloudServiceAgent> cloud_agent = m_agent->get_cloud_agent(cloud_agent_id);
|
||||
|
||||
if (current_agent_id != effective_agent_id) {
|
||||
std::string log_dir = data_dir();
|
||||
std::shared_ptr<ICloudServiceAgent> cloud_agent = m_agent->get_cloud_agent(cloud_agent_id);
|
||||
// Create new printer agent via registry
|
||||
std::shared_ptr<IPrinterAgent> new_printer_agent =
|
||||
NetworkAgentFactory::create_printer_agent_by_id(effective_agent_id, cloud_agent, log_dir);
|
||||
|
||||
// Create new printer agent via registry
|
||||
std::shared_ptr<IPrinterAgent> new_printer_agent =
|
||||
NetworkAgentFactory::create_printer_agent_by_id(effective_agent_id, cloud_agent, log_dir);
|
||||
if (!new_printer_agent) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": failed to create agent '" << effective_agent_id << "', keeping current agent";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!new_printer_agent) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": failed to create agent '" << effective_agent_id << "', keeping current agent";
|
||||
return;
|
||||
}
|
||||
|
||||
// Swap the agent
|
||||
m_agent->set_printer_agent(new_printer_agent);
|
||||
sidebar().update_all_preset_comboboxes();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": printer agent switched to " << effective_agent_id;
|
||||
|
||||
// Auto-switch MachineObject (new agent has empty device_info, so always re-select)
|
||||
select_machine(effective_agent_id);
|
||||
} else if (effective_agent_id != BBL_PRINTER_AGENT_ID) {
|
||||
// The factory caches agents per ID, so an identical pointer means the agent type is unchanged.
|
||||
if (m_agent->get_printer_agent() == new_printer_agent) {
|
||||
// Orca: the agent type is unchanged (e.g. switching between two Moonraker/Klipper
|
||||
// printer presets), so the selected machine and the agent's cached device_info still
|
||||
// point at the previously active printer preset. Re-select the machine when the new
|
||||
// preset targets a different host, otherwise filament sync keeps hitting the old
|
||||
// printer. (#12506)
|
||||
if (m_device_manager && preset_bundle) {
|
||||
const DynamicPrintConfig& cfg = preset_bundle->printers.get_edited_preset().config;
|
||||
const std::string print_host = cfg.opt_string("print_host");
|
||||
if (effective_agent_id != BBL_PRINTER_AGENT_ID && m_device_manager && preset_bundle) {
|
||||
const std::string print_host = config.opt_string("print_host");
|
||||
if (!print_host.empty()) {
|
||||
const std::string dev_id = MachineObject::dev_id_from_address(print_host, cfg.opt_string("printhost_port"));
|
||||
MachineObject* sel = m_device_manager->get_selected_machine();
|
||||
const std::string dev_id = MachineObject::dev_id_from_address(print_host, config.opt_string("printhost_port"));
|
||||
MachineObject* sel = m_device_manager->get_selected_machine();
|
||||
if (!sel || sel->get_dev_id() != dev_id)
|
||||
select_machine(effective_agent_id);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Swap the agent
|
||||
m_agent->set_printer_agent(new_printer_agent);
|
||||
sidebar().update_all_preset_comboboxes();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": printer agent switched to " << effective_agent_id;
|
||||
|
||||
// Start discovery so Python agents can populate the device list via SSDP callback
|
||||
m_agent->start_discovery(true, false);
|
||||
|
||||
// Auto-switch MachineObject (new agent has empty device_info, so always re-select)
|
||||
select_machine(effective_agent_id);
|
||||
}
|
||||
|
||||
void GUI_App::select_machine(const std::string& agent_id)
|
||||
@@ -4740,9 +4869,16 @@ void GUI_App::request_user_logout(const std::string& provider/* = ORCA_CLOUD_PRO
|
||||
|
||||
remove_user_presets();
|
||||
enable_user_preset_folder(false);
|
||||
Slic3r::PluginManager::instance().unload_cloud_plugins();
|
||||
Slic3r::PluginManager::instance().clear_cloud_plugin_metadata();
|
||||
Slic3r::PluginManager::instance().set_cloud_user("");
|
||||
preset_bundle->load_user_presets(DEFAULT_USER_FOLDER_NAME, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
mainframe->update_side_preset_ui();
|
||||
|
||||
// keep this here. refresh_from_catalog is meant to update the dialog UI.
|
||||
if (m_plugins_dlg)
|
||||
m_plugins_dlg->update_plugin_dialog_ui();
|
||||
|
||||
GUI::wxGetApp().stop_sync_user_preset();
|
||||
}
|
||||
|
||||
@@ -5281,10 +5417,12 @@ void GUI_App::enable_user_preset_folder(bool enable)
|
||||
std::string user_id = m_agent->get_user_id();
|
||||
app_config->set("preset_folder", user_id);
|
||||
GUI::wxGetApp().preset_bundle->update_user_presets_directory(user_id);
|
||||
PluginManager::instance().set_cloud_user(user_id);
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << "preset_folder: set to empty";
|
||||
app_config->set("preset_folder", "");
|
||||
GUI::wxGetApp().preset_bundle->update_user_presets_directory(DEFAULT_USER_FOLDER_NAME);
|
||||
PluginManager::instance().set_cloud_user("");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5322,12 +5460,27 @@ void GUI_App::on_user_login_handle(wxCommandEvent &evt)
|
||||
});
|
||||
|
||||
if (online_login && provider == ORCA_CLOUD_PROVIDER) {
|
||||
// The steps below run synchronously on the UI thread (cloud plugin fetch and
|
||||
// user-preset load both block on network/disk). Show an indeterminate progress
|
||||
// dialog so the window isn't frozen without feedback. Percentages are cosmetic
|
||||
// milestones, not measured progress.
|
||||
ProgressDialog dlg(_L("Loading"), _L("Syncing your account…"), 100, mainframe, wxPD_AUTO_HIDE | wxPD_APP_MODAL);
|
||||
|
||||
dlg.Update(10, _L("Migrating presets…"));
|
||||
maybe_migrate_user_presets_on_login();
|
||||
remove_user_presets();
|
||||
enable_user_preset_folder(true);
|
||||
|
||||
dlg.Update(40, _L("Fetching plugins…"));
|
||||
PluginManager::instance().fetch_plugins_from_cloud();
|
||||
if (m_plugins_dlg)
|
||||
m_plugins_dlg->update_plugin_dialog_ui();
|
||||
|
||||
dlg.Update(70, _L("Loading user presets…"));
|
||||
preset_bundle->load_user_presets(m_agent->get_user_id(provider), ForwardCompatibilitySubstitutionRule::Enable);
|
||||
mainframe->update_side_preset_ui();
|
||||
|
||||
dlg.Update(100);
|
||||
GUI::wxGetApp().mainframe->show_sync_dialog();
|
||||
}
|
||||
|
||||
@@ -5489,40 +5642,6 @@ struct UpdaterQuery
|
||||
std::string os_info;
|
||||
};
|
||||
|
||||
std::string detect_updater_os()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return "win";
|
||||
#elif defined(__APPLE__)
|
||||
return "macos";
|
||||
#elif defined(__linux__) || defined(__LINUX__)
|
||||
return "linux";
|
||||
#else
|
||||
return "unknown";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string detect_updater_arch()
|
||||
{
|
||||
#if defined(__aarch64__) || defined(_M_ARM64)
|
||||
return "arm64";
|
||||
#elif defined(__x86_64__) || defined(_M_X64)
|
||||
return "x86_64";
|
||||
#elif defined(__i386__) || defined(_M_IX86)
|
||||
return "i386";
|
||||
#else
|
||||
std::string arch = wxPlatformInfo::Get().GetArchName().ToStdString();
|
||||
boost::algorithm::to_lower(arch);
|
||||
if (arch.find("aarch64") != std::string::npos || arch.find("arm64") != std::string::npos)
|
||||
return "arm64";
|
||||
if (arch.find("x86_64") != std::string::npos || arch.find("amd64") != std::string::npos)
|
||||
return "x86_64";
|
||||
if (arch.find("i686") != std::string::npos || arch.find("i386") != std::string::npos || arch.find("x86") != std::string::npos)
|
||||
return "i386";
|
||||
return "unknown";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string detect_updater_os_info()
|
||||
{
|
||||
wxString description = wxPlatformInfo::Get().GetOperatingSystemDescription();
|
||||
@@ -5751,8 +5870,8 @@ void GUI_App::check_new_version_sf(bool show_tips, int by_user)
|
||||
UpdaterQuery query{
|
||||
detect_updater_iid(app_config),
|
||||
detect_updater_version(),
|
||||
detect_updater_os(),
|
||||
detect_updater_arch(),
|
||||
platform_os_type(),
|
||||
platform_architecture(),
|
||||
detect_updater_os_info()
|
||||
};
|
||||
|
||||
@@ -8191,6 +8310,89 @@ void GUI_App::open_presetbundledialog(size_t open_on_tab, const std::string& hig
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_App::open_plugins_dialog(size_t open_on_tab, const std::string& highlight_option)
|
||||
{
|
||||
if (m_plugins_dlg) {
|
||||
m_plugins_dlg->Show();
|
||||
m_plugins_dlg->Raise();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
m_plugins_dlg = new PluginsDialog(mainframe, wxID_ANY, _L("Plugins"));
|
||||
m_plugins_dlg->set_open_terminal_dlg_fn();
|
||||
m_plugins_dlg->Bind(wxEVT_DESTROY, [this](wxWindowDestroyEvent& event) {
|
||||
if (event.GetEventObject() == m_plugins_dlg)
|
||||
m_plugins_dlg = nullptr;
|
||||
event.Skip();
|
||||
});
|
||||
|
||||
m_plugins_dlg->Show();
|
||||
m_plugins_dlg->Raise();
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << "open_plugins_dialog failed: " << e.what();
|
||||
if (m_plugins_dlg) {
|
||||
m_plugins_dlg->Destroy();
|
||||
m_plugins_dlg = nullptr;
|
||||
}
|
||||
wxMessageBox(wxString::Format(_L("Failed to open the Plugins dialog:\n%s"), from_u8(e.what())), _L("Plugins"),
|
||||
wxOK | wxICON_ERROR, mainframe);
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "open_plugins_dialog failed with a non-standard exception";
|
||||
if (m_plugins_dlg) {
|
||||
m_plugins_dlg->Destroy();
|
||||
m_plugins_dlg = nullptr;
|
||||
}
|
||||
wxMessageBox(_L("Failed to open the Plugins dialog (unknown error)."), _L("Plugins"), wxOK | wxICON_ERROR, mainframe);
|
||||
}
|
||||
}
|
||||
|
||||
void GUI_App::open_terminal_dialog()
|
||||
{
|
||||
// Reached from the plugins dialog's webview ("open_terminal" command), i.e. from
|
||||
// inside the webview script-message callback, which GTK/macOS deliver synchronously
|
||||
// (see ui_create_window in PluginHostUi.cpp). TerminalDialog hosts a webview of its
|
||||
// own, so creating or presenting it on that stack is the same class as the Linux
|
||||
// gtk_window_present crash — defer all window work to a clean main-loop iteration.
|
||||
CallAfter([this]() {
|
||||
if (m_terminal_dlg) {
|
||||
// Re-front the existing window; guard Show() per #13657 (GTK re-enters
|
||||
// layout when showing an already-visible window).
|
||||
if (!m_terminal_dlg->IsShown())
|
||||
m_terminal_dlg->Show();
|
||||
m_terminal_dlg->Raise();
|
||||
return;
|
||||
}
|
||||
|
||||
m_terminal_dlg = new TerminalDialog(mainframe, wxID_ANY, _L("Plugin Terminal"),
|
||||
wxDefaultPosition, wxSize(820, 600));
|
||||
m_terminal_dlg->Bind(wxEVT_DESTROY, [this](wxWindowDestroyEvent& event) {
|
||||
if (event.GetEventObject() == m_terminal_dlg)
|
||||
m_terminal_dlg = nullptr;
|
||||
event.Skip();
|
||||
});
|
||||
|
||||
// Show() alone activates and fronts a freshly created window on every platform.
|
||||
m_terminal_dlg->Show();
|
||||
});
|
||||
}
|
||||
|
||||
void GUI_App::open_speed_dial()
|
||||
{
|
||||
if (!mainframe)
|
||||
return;
|
||||
if (!m_speed_dial_dialog) {
|
||||
m_speed_dial_dialog = new SpeedDialWebDialog(mainframe);
|
||||
m_speed_dial_dialog->Bind(wxEVT_DESTROY, [this](wxWindowDestroyEvent& event) {
|
||||
if (event.GetEventObject() == m_speed_dial_dialog)
|
||||
m_speed_dial_dialog = nullptr;
|
||||
event.Skip();
|
||||
});
|
||||
}
|
||||
m_speed_dial_dialog->request_show();
|
||||
}
|
||||
|
||||
void GUI_App::open_exportpresetbundledialog(size_t open_on_tab, const std::string& highlight_option)
|
||||
{
|
||||
bool app_layout_changed = false;
|
||||
|
||||
Reference in New Issue
Block a user