mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-30 14:22:07 +00:00
Merge remote-tracking branch 'upstream/main' into belt/baseChanges
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
#include <boost/chrono/duration.hpp>
|
||||
#include <boost/log/detail/native_typeof.hpp>
|
||||
#include <libslic3r/Config.hpp>
|
||||
#include <mutex>
|
||||
#include <wx/event.h>
|
||||
|
||||
// Localization headers: include libslic3r version first so everything in this file
|
||||
@@ -4508,21 +4509,46 @@ std::string GUI_App::handle_web_request(std::string cmd)
|
||||
boost::optional<std::string> command = root.get_optional<std::string>("command");
|
||||
if (command.has_value()) {
|
||||
std::string command_str = command.value();
|
||||
static const std::unordered_set<std::string> stealth_blocked_commands = {
|
||||
static const std::unordered_set<std::string> stealth_blocked_info_commands = {
|
||||
"get_login_info",
|
||||
"get_orca_login_info",
|
||||
"get_bambu_login_info",
|
||||
};
|
||||
static const std::unordered_set<std::string> stealth_blocked_login_commands = {
|
||||
"homepage_login_or_register",
|
||||
"homepage_orca_login_or_register",
|
||||
"homepage_bambu_login_or_register",
|
||||
};
|
||||
if (app_config->get_stealth_mode() && stealth_blocked_commands.count(command_str)) {
|
||||
if (app_config->get_stealth_mode() && stealth_blocked_info_commands.count(command_str)) {
|
||||
CallAfter([this] {
|
||||
if (mainframe && mainframe->m_webview)
|
||||
mainframe->m_webview->SendCloudProvidersInfo();
|
||||
});
|
||||
return "";
|
||||
}
|
||||
if (app_config->get_stealth_mode() && stealth_blocked_login_commands.count(command_str)) {
|
||||
CallAfter([this, command_str] {
|
||||
MessageDialog dlg(mainframe,
|
||||
_L("You are currently in Stealth Mode. To log into the Cloud, you need to disable Stealth Mode first."),
|
||||
_L("Stealth Mode"),
|
||||
wxOK | wxCANCEL | wxCENTRE);
|
||||
dlg.SetButtonLabel(wxID_OK, _L("Quit Stealth Mode"));
|
||||
if (dlg.ShowModal() == wxID_OK) {
|
||||
app_config->set_bool("stealth_mode", false);
|
||||
app_config->save();
|
||||
if (mainframe && mainframe->m_webview)
|
||||
mainframe->m_webview->SendCloudProvidersInfo();
|
||||
// Continue with login
|
||||
if (command_str == "homepage_login_or_register")
|
||||
this->request_login(true);
|
||||
else if (command_str == "homepage_orca_login_or_register")
|
||||
this->request_login(true, ORCA_CLOUD_PROVIDER);
|
||||
else if (command_str == "homepage_bambu_login_or_register")
|
||||
this->request_login(true, BBL_CLOUD_PROVIDER);
|
||||
}
|
||||
});
|
||||
return "";
|
||||
}
|
||||
if (command_str.compare("request_project_download") == 0) {
|
||||
if (root.get_child_optional("data") != boost::none) {
|
||||
pt::ptree data_node = root.get_child("data");
|
||||
@@ -4812,6 +4838,8 @@ void GUI_App::handle_http_error(unsigned int status, std::string body, const std
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
|
||||
static std::mutex conflict_ids_mutex;
|
||||
|
||||
void GUI_App::on_http_error(wxCommandEvent &evt)
|
||||
{
|
||||
int status = evt.GetInt();
|
||||
@@ -4887,32 +4915,62 @@ void GUI_App::on_http_error(wxCommandEvent &evt)
|
||||
return;
|
||||
}
|
||||
|
||||
static bool m_is_error_shown = false;
|
||||
if (status == 409 && provider == ORCA_CLOUD_PROVIDER) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Http error 409.";
|
||||
// Parse the conflict body to extract the error code and server profile id
|
||||
int conflict_code = 0;
|
||||
std::string conflict_setting_id;
|
||||
try {
|
||||
json conflict_body = json::parse(body_str);
|
||||
if (conflict_body.contains("code"))
|
||||
conflict_code = conflict_body["code"].get<int>();
|
||||
if (conflict_body.contains("server_profile") && conflict_body["server_profile"].contains("id")
|
||||
&& conflict_body["server_profile"]["id"].is_string())
|
||||
conflict_setting_id = conflict_body["server_profile"]["id"].get<std::string>();
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Failed to parse 409 conflict body.";
|
||||
}
|
||||
auto* plater = wxGetApp().plater();
|
||||
if (plater != nullptr && wxGetApp().imgui()->display_initialized()) {
|
||||
std::string text;
|
||||
if (conflict_code == -1) {
|
||||
text = _u8L("Cloud sync conflict: this preset has a newer version in OrcaCloud.\n"
|
||||
"Pull downloads the cloud copy. Force push overwrites it with your local preset.");
|
||||
} else {
|
||||
text = _u8L("Cloud sync conflict: a preset with this name already exists in OrcaCloud.\n"
|
||||
"Pull downloads the cloud copy. Force push overwrites it with your local preset.");
|
||||
}
|
||||
plater->get_notification_manager()->push_orca_sync_conflict_notification(
|
||||
text,
|
||||
[this](wxEvtHandler*) {
|
||||
// Runs on the GUI thread (on_http_error is a queued wx event); restart_sync_user_preset()
|
||||
// already joins the old sync thread off the UI thread, so no extra thread is needed here.
|
||||
if (is_closing() || !m_agent || !preset_bundle)
|
||||
return false;
|
||||
BOOST_LOG_TRIVIAL(info) << "Pulling Orca Cloud settings to resolve sync conflict.";
|
||||
restart_sync_user_preset();
|
||||
return true;
|
||||
},
|
||||
[this, conflict_setting_id](wxEvtHandler*) {
|
||||
if (mainframe == nullptr)
|
||||
return false;
|
||||
MessageDialog
|
||||
dlg(mainframe,
|
||||
_L("Force push will overwrite the cloud copy with your local preset changes.\nDo you want to continue?"),
|
||||
_L("Resolve cloud sync conflict"), wxCENTER | wxYES_NO | wxNO_DEFAULT | wxICON_WARNING);
|
||||
if (dlg.ShowModal() != wxID_YES)
|
||||
return false;
|
||||
|
||||
force_push_conflicting_preset(conflict_setting_id);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Show general error notification for Orca Cloud API failures (not Bambu)
|
||||
if (provider == ORCA_CLOUD_PROVIDER && status >= 400 && code != HttpErrorVersionLimited) {
|
||||
wxString msg;
|
||||
if (!error.empty()) {
|
||||
msg = wxString::Format(_L("Failed to connect to OrcaCloud.\nPlease check your network connectivity\n(HTTP %u): %s"), status, wxString::FromUTF8(error));
|
||||
} else {
|
||||
msg = wxString::Format(_L("Failed to connect to OrcaCloud.\nPlease check your network connectivity\n(HTTP %u)"), status);
|
||||
}
|
||||
|
||||
if (app_config->get_bool("developer_mode")) {
|
||||
// Use notification manager if ImGui is ready; fall back to wxMessageBox on Linux
|
||||
// where ImGui may not be initialized until the user switches to the Prepare tab.
|
||||
if (wxGetApp().plater() != nullptr && wxGetApp().imgui()->display_initialized()) {
|
||||
wxGetApp()
|
||||
.plater()
|
||||
->get_notification_manager()
|
||||
->push_notification(NotificationType::PlaterError, NotificationManager::NotificationLevel::WarningNotificationLevel,
|
||||
msg.ToUTF8().data());
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_is_error_shown) {
|
||||
m_is_error_shown = true;
|
||||
wxMessageBox(msg, _L("Cloud Error"), wxOK | wxICON_ERROR, wxGetApp().mainframe);
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(warning) << "API call to OrcaCloud failed with status=" << status;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6181,13 +6239,14 @@ void GUI_App::load_pending_vendors()
|
||||
need_add_filaments.clear();
|
||||
}
|
||||
|
||||
void GUI_App::sync_preset(Preset* preset)
|
||||
void GUI_App::sync_preset(Preset* preset, bool force)
|
||||
{
|
||||
int result = -1;
|
||||
unsigned int http_code = 200;
|
||||
std::string updated_info;
|
||||
long long update_time = 0;
|
||||
// only sync user's preset
|
||||
if (!m_agent) return;
|
||||
if (!preset->is_user()) return;
|
||||
|
||||
auto setting_id = preset->setting_id;
|
||||
@@ -6259,9 +6318,9 @@ void GUI_App::sync_preset(Preset* preset)
|
||||
result = 0;
|
||||
}
|
||||
else {
|
||||
result = m_agent->put_setting(setting_id, preset->name, &values_map, &http_code);
|
||||
result = m_agent->put_setting(setting_id, preset->name, &values_map, &http_code, ORCA_CLOUD_PROVIDER, force);
|
||||
if (http_code >= 400) {
|
||||
result = 0;
|
||||
result = 0;
|
||||
updated_info = "hold";
|
||||
BOOST_LOG_TRIVIAL(error) << "[sync_preset] put setting_id = " << setting_id << " failed, http_code = " << http_code;
|
||||
} else {
|
||||
@@ -6722,7 +6781,8 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
|
||||
// Sync once immediately, then every 60 seconds.
|
||||
while (!t.expired()) {
|
||||
++tick_tock;
|
||||
if (tick_tock % 120 == 0) {
|
||||
// Sync once immediately, then every 60s, or right away when a force-push asked for it.
|
||||
if (tick_tock % 120 == 0 || m_sync_user_presets_now.exchange(false, std::memory_order_acq_rel)) {
|
||||
tick_tock = 0;
|
||||
if (m_agent) {
|
||||
if (!m_agent->is_user_login()) {
|
||||
@@ -6733,9 +6793,24 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
|
||||
|
||||
int total_count = 0;
|
||||
sync_count = preset_bundle->prints.get_user_presets(preset_bundle, presets_to_sync);
|
||||
|
||||
auto sync_with_lock = [this](Preset& preset) {
|
||||
bool force = false;
|
||||
{
|
||||
std::scoped_lock lock(conflict_ids_mutex);
|
||||
auto it = std::find_if(m_pending_conflict_setting_ids.begin(), m_pending_conflict_setting_ids.end(),
|
||||
[&preset](const std::string& id) { return id == preset.setting_id; });
|
||||
if (it != m_pending_conflict_setting_ids.end()) {
|
||||
force = true;
|
||||
m_pending_conflict_setting_ids.erase(it);
|
||||
}
|
||||
}
|
||||
sync_preset(&preset, force);
|
||||
};
|
||||
|
||||
if (sync_count > 0) {
|
||||
for (Preset& preset : presets_to_sync) {
|
||||
sync_preset(&preset);
|
||||
sync_with_lock(preset);
|
||||
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
|
||||
}
|
||||
}
|
||||
@@ -6744,7 +6819,7 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
|
||||
sync_count = preset_bundle->filaments.get_user_presets(preset_bundle, presets_to_sync);
|
||||
if (sync_count > 0) {
|
||||
for (Preset& preset : presets_to_sync) {
|
||||
sync_preset(&preset);
|
||||
sync_with_lock(preset);
|
||||
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
|
||||
}
|
||||
}
|
||||
@@ -6753,7 +6828,7 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg)
|
||||
sync_count = preset_bundle->printers.get_user_presets(preset_bundle, presets_to_sync);
|
||||
if (sync_count > 0) {
|
||||
for (Preset& preset : presets_to_sync) {
|
||||
sync_preset(&preset);
|
||||
sync_with_lock(preset);
|
||||
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
|
||||
}
|
||||
}
|
||||
@@ -6930,6 +7005,35 @@ void GUI_App::restart_sync_user_preset()
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void GUI_App::force_push_conflicting_preset(const std::string& setting_id)
|
||||
{
|
||||
if (setting_id.empty() || !preset_bundle)
|
||||
return;
|
||||
|
||||
// Queue the id so the next push-sync re-uploads this preset with force=true.
|
||||
{
|
||||
std::scoped_lock lock(conflict_ids_mutex);
|
||||
m_pending_conflict_setting_ids.push_back(setting_id);
|
||||
}
|
||||
|
||||
// The 409 left this preset on "hold", which get_user_presets() skips. Restore it to
|
||||
// "update" so the next push-sync re-includes it and consumes the queued force flag.
|
||||
// (We must NOT pull from the cloud here as the Pull path does — that would overwrite
|
||||
// the local changes the user is trying to force-push.)
|
||||
PresetCollection* collections[] = {&preset_bundle->prints, &preset_bundle->filaments, &preset_bundle->printers};
|
||||
for (PresetCollection* coll : collections) {
|
||||
for (const Preset& preset : coll->get_presets()) {
|
||||
if (preset.setting_id == setting_id && preset.sync_info == "hold") {
|
||||
coll->set_sync_info_and_save(preset.name, preset.setting_id, "update", 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nudge the sync loop to push on its next tick instead of waiting for the 60s cadence.
|
||||
m_sync_user_presets_now.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
void GUI_App::on_stealth_mode_enter()
|
||||
{
|
||||
stop_sync_user_preset();
|
||||
|
||||
@@ -297,6 +297,7 @@ private:
|
||||
NetworkAgent* m_agent { nullptr };
|
||||
std::map<std::string, std::string> need_delete_presets; // store setting ids of preset
|
||||
std::vector<bool> m_create_preset_blocked { false, false, false, false, false, false }; // excceed limit
|
||||
std::vector<std::string> m_pending_conflict_setting_ids; // setting_id from the most recent 409 conflict
|
||||
bool m_networking_compatible { false };
|
||||
bool m_networking_need_update { false };
|
||||
bool m_networking_cancel_update { false };
|
||||
@@ -322,6 +323,7 @@ private:
|
||||
boost::thread m_sync_update_thread;
|
||||
std::shared_ptr<int> m_user_sync_token;
|
||||
std::atomic<bool> m_restart_sync_pending {false};
|
||||
std::atomic<bool> m_sync_user_presets_now {false}; // request the sync loop to push user presets on its next tick
|
||||
bool m_is_dark_mode{ false };
|
||||
bool m_adding_script_handler { false };
|
||||
bool m_side_popup_status{false};
|
||||
@@ -529,10 +531,13 @@ public:
|
||||
void add_pending_vendor_preset(const std::pair<std::string, std::map<std::string, std::string>>& preset_data);
|
||||
void load_pending_vendors();
|
||||
|
||||
void sync_preset(Preset* preset);
|
||||
void sync_preset(Preset* preset, bool force = false);
|
||||
void start_sync_user_preset(bool with_progress_dlg = false);
|
||||
void stop_sync_user_preset();
|
||||
void restart_sync_user_preset();
|
||||
// Resolve a cloud sync 409 by force-pushing the conflicting preset: clears the "hold"
|
||||
// state the conflict left behind and queues it to be re-uploaded with force=true.
|
||||
void force_push_conflicting_preset(const std::string& setting_id);
|
||||
void on_stealth_mode_enter();
|
||||
|
||||
// Bundle subscription sync
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "DeviceManager.hpp"
|
||||
#include "DeviceCore/DevManager.h"
|
||||
#include "DeviceCore/DevUtil.h"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
@@ -13,6 +14,14 @@ static const char* HMS_LOCAL_IMG_PATH = "hms/local_image";
|
||||
// the local HMS info
|
||||
static unordered_set<string> package_dev_id_types {"094", "239", "093", "22E"};
|
||||
|
||||
// HMS should be disabled when stealth mode is on or networking is not installed
|
||||
static bool should_disable_hms()
|
||||
{
|
||||
Slic3r::AppConfig* config = Slic3r::GUI::wxGetApp().app_config;
|
||||
if (!config) return true;
|
||||
return config->get_stealth_mode() || !config->get_bool("installed_networking");
|
||||
}
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
@@ -21,7 +30,7 @@ int get_hms_info_version(std::string& version)
|
||||
AppConfig* config = wxGetApp().app_config;
|
||||
if (!config)
|
||||
return -1;
|
||||
if (config->get_stealth_mode())
|
||||
if (should_disable_hms())
|
||||
return -1;
|
||||
std::string hms_host = config->get_hms_host();
|
||||
if(hms_host.empty()) {
|
||||
@@ -61,7 +70,7 @@ int HMSQuery::download_hms_related(const std::string& hms_type, const std::strin
|
||||
|
||||
AppConfig* config = wxGetApp().app_config;
|
||||
if (!config) return -1;
|
||||
if (config->get_stealth_mode()) return -1;
|
||||
if (should_disable_hms()) return -1;
|
||||
|
||||
std::string hms_host = wxGetApp().app_config->get_hms_host();
|
||||
std::string lang;
|
||||
@@ -546,7 +555,7 @@ wxString HMSQuery::query_print_image_action(const MachineObject* obj, int print_
|
||||
::sprintf(buf, "%08X", print_error);
|
||||
//The first three digits of SN number
|
||||
const auto result = _query_error_image_action(get_dev_id_type(obj),std::string(buf), button_action);
|
||||
if (wxGetApp().app_config->get_stealth_mode() && result.Contains("http")) {
|
||||
if (should_disable_hms() && result.Contains("http")) {
|
||||
return wxEmptyString;
|
||||
}
|
||||
return result;
|
||||
@@ -637,7 +646,7 @@ std::string get_hms_wiki_url(std::string error_code)
|
||||
{
|
||||
AppConfig* config = wxGetApp().app_config;
|
||||
if (!config) return "";
|
||||
if (config->get_stealth_mode()) return "";
|
||||
if (should_disable_hms()) return "";
|
||||
|
||||
std::string hms_host = wxGetApp().app_config->get_hms_host();
|
||||
std::string lang_code = HMSQuery::hms_language_code();
|
||||
@@ -663,7 +672,7 @@ std::string get_hms_wiki_url(std::string error_code)
|
||||
|
||||
std::string get_error_message(int error_code)
|
||||
{
|
||||
if (wxGetApp().app_config->get_stealth_mode()) return "";
|
||||
if (should_disable_hms()) return "";
|
||||
|
||||
char buf[64];
|
||||
std::string result_str = "";
|
||||
|
||||
@@ -791,6 +791,42 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui,
|
||||
|
||||
}
|
||||
|
||||
void NotificationManager::PopNotification::render_hyperlink_action(ImGuiWrapper& imgui, float text_x, float text_y,
|
||||
const std::string& text, const char* button_id, const std::function<void()>& on_click)
|
||||
{
|
||||
// Invisible button over the label
|
||||
ImVec2 part_size = ImGui::CalcTextSize(text.c_str());
|
||||
ImGui::SetCursorPosX(text_x - 4);
|
||||
ImGui::SetCursorPosY(text_y - 5);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||
if (imgui.button(button_id, part_size.x + 6, part_size.y + 10) && on_click)
|
||||
on_click();
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
// Hover color
|
||||
ImVec4 color = m_HyperTextColor;
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly))
|
||||
color = m_HyperTextColorHover;
|
||||
|
||||
// Text
|
||||
push_style_color(ImGuiCol_Text, color, m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
ImGui::SetCursorPosX(text_x);
|
||||
ImGui::SetCursorPosY(text_y);
|
||||
imgui.text(text.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
// Underline
|
||||
ImVec2 lineEnd = ImGui::GetItemRectMax();
|
||||
lineEnd.y -= 2;
|
||||
ImVec2 lineStart = lineEnd;
|
||||
lineStart.x = ImGui::GetItemRectMin().x;
|
||||
ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd,
|
||||
IM_COL32((int)(color.x * 255), (int)(color.y * 255), (int)(color.z * 255),
|
||||
(int)(color.w * 255.f * (m_state == EState::FadingOut ? m_current_fade_opacity : 1.f))));
|
||||
}
|
||||
|
||||
void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
ensure_ui_inited();
|
||||
@@ -2346,40 +2382,49 @@ bool NotificationManager::SharedProfilesNotification::on_text_click()
|
||||
void NotificationManager::SharedProfilesNotification::render_hypertext(ImGuiWrapper& imgui,
|
||||
const float text_x, const float text_y, const std::string text, bool more)
|
||||
{
|
||||
// Invisible button
|
||||
ImVec2 part_size = ImGui::CalcTextSize(text.c_str());
|
||||
ImGui::SetCursorPosX(text_x - 4);
|
||||
ImGui::SetCursorPosY(text_y - 5);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||
if (imgui.button("##browse_btn", part_size.x + 6, part_size.y + 10)) {
|
||||
if (on_text_click()) {
|
||||
close();
|
||||
render_hyperlink_action(imgui, text_x, text_y, text, "##browse_btn",
|
||||
[this] { if (on_text_click()) close(); });
|
||||
}
|
||||
|
||||
void NotificationManager::OrcaSyncConflictNotification::init()
|
||||
{
|
||||
PopNotification::init();
|
||||
// Reserve a dedicated action row for the two conflict-resolution links.
|
||||
m_lines_count = m_lines_count + 1;
|
||||
}
|
||||
|
||||
void NotificationManager::OrcaSyncConflictNotification::render_text(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
float x_offset = m_left_indentation;
|
||||
float shift_y = m_line_height;
|
||||
float starting_y = m_line_height / 2;
|
||||
|
||||
int last_end = 0;
|
||||
std::string line;
|
||||
for (size_t i = 0; i < m_endlines.size(); i++) {
|
||||
if (m_text1.size() >= m_endlines[i]) {
|
||||
line = m_text1.substr(last_end, m_endlines[i] - last_end);
|
||||
last_end = m_endlines[i];
|
||||
if (m_text1.size() > m_endlines[i])
|
||||
last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0);
|
||||
ImGui::SetCursorPosX(x_offset);
|
||||
ImGui::SetCursorPosY(starting_y + i * shift_y);
|
||||
imgui.text(line.c_str());
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
// Hover color
|
||||
ImVec4 HyperColor = m_HyperTextColor;
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly))
|
||||
HyperColor = m_HyperTextColorHover;
|
||||
|
||||
// Text
|
||||
push_style_color(ImGuiCol_Text, HyperColor, m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
ImGui::SetCursorPosX(text_x);
|
||||
ImGui::SetCursorPosY(text_y);
|
||||
imgui.text(text.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
// Underline
|
||||
ImVec2 lineEnd = ImGui::GetItemRectMax();
|
||||
lineEnd.y -= 2;
|
||||
ImVec2 lineStart = lineEnd;
|
||||
lineStart.x = ImGui::GetItemRectMin().x;
|
||||
ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd,
|
||||
IM_COL32((int)(HyperColor.x * 255), (int)(HyperColor.y * 255), (int)(HyperColor.z * 255),
|
||||
(int)(HyperColor.w * 255.f * (m_state == EState::FadingOut ? m_current_fade_opacity : 1.f))));
|
||||
const float action_y = starting_y + m_endlines.size() * shift_y;
|
||||
const std::string pull_text = _u8L("Pull");
|
||||
render_hyperlink_action(imgui, x_offset, action_y, pull_text, "##orca_sync_pull",
|
||||
[this] { if (m_pull_callback && m_pull_callback(m_evt_handler)) close(); });
|
||||
if (m_force_push_callback) {
|
||||
const std::string force_push_text = _u8L("Force push");
|
||||
const float force_x = x_offset + ImGui::CalcTextSize((pull_text + " ").c_str()).x;
|
||||
render_hyperlink_action(imgui, force_x, action_y, force_push_text, "##orca_sync_force_push",
|
||||
[this] { if (m_force_push_callback && m_force_push_callback(m_evt_handler)) close(); });
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationManager::push_shared_profiles_notification(const std::string& explore_url)
|
||||
@@ -2391,6 +2436,16 @@ void NotificationManager::push_shared_profiles_notification(const std::string& e
|
||||
push_notification_data(std::make_unique<NotificationManager::SharedProfilesNotification>(data, m_id_provider, m_evt_handler, explore_url), 0);
|
||||
}
|
||||
|
||||
void NotificationManager::push_orca_sync_conflict_notification(const std::string& text,
|
||||
std::function<bool(wxEvtHandler*)> pull_callback,
|
||||
std::function<bool(wxEvtHandler*)> force_push_callback)
|
||||
{
|
||||
close_notification_of_type(NotificationType::OrcaSyncConflict);
|
||||
NotificationData data{ NotificationType::OrcaSyncConflict, NotificationLevel::WarningNotificationLevel, 0, text };
|
||||
push_notification_data(std::make_unique<NotificationManager::OrcaSyncConflictNotification>(
|
||||
data, m_id_provider, m_evt_handler, std::move(pull_callback), std::move(force_push_callback)), 0);
|
||||
}
|
||||
|
||||
void NotificationManager::push_download_URL_progress_notification(size_t id, const std::string& text, std::function<bool(DownloaderUserAction, int)> user_action_callback)
|
||||
{
|
||||
// If already exists
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <wx/time.h>
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <unordered_set>
|
||||
@@ -162,6 +164,8 @@ enum class NotificationType
|
||||
BBLMixUsePLAAndPETG,
|
||||
BBLNozzleFilamentIncompatible,
|
||||
OrcaSharedProfilesAvailable,
|
||||
OrcaCloudAPIError,
|
||||
OrcaSyncConflict,
|
||||
NotificationTypeCount
|
||||
|
||||
};
|
||||
@@ -274,6 +278,9 @@ public:
|
||||
|
||||
// Shared profiles available for selected printer
|
||||
void push_shared_profiles_notification(const std::string& explore_url);
|
||||
void push_orca_sync_conflict_notification(const std::string& text,
|
||||
std::function<bool(wxEvtHandler*)> pull_callback,
|
||||
std::function<bool(wxEvtHandler*)> force_push_callback);
|
||||
|
||||
// Download URL progress notif
|
||||
void push_download_URL_progress_notification(size_t id, const std::string& text, std::function<bool(DownloaderUserAction, int)> user_action_callback);
|
||||
@@ -491,6 +498,11 @@ private:
|
||||
const float text_x, const float text_y,
|
||||
const std::string text,
|
||||
bool more = false);
|
||||
// Renders an underlined, hyperlink-style clickable label backed by an invisible button.
|
||||
// on_click runs when pressed; the callback itself decides whether to close().
|
||||
void render_hyperlink_action(ImGuiWrapper& imgui, float text_x, float text_y,
|
||||
const std::string& text, const char* button_id,
|
||||
const std::function<void()>& on_click);
|
||||
virtual void bbl_render_block_notif_text(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x, const float win_pos_y);
|
||||
@@ -887,6 +899,28 @@ private:
|
||||
std::string m_explore_url;
|
||||
bool m_dont_show_clicked{ false };
|
||||
};
|
||||
|
||||
class OrcaSyncConflictNotification : public PopNotification
|
||||
{
|
||||
public:
|
||||
OrcaSyncConflictNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler,
|
||||
std::function<bool(wxEvtHandler*)> pull_callback,
|
||||
std::function<bool(wxEvtHandler*)> force_push_callback)
|
||||
: PopNotification(n, id_provider, evt_handler)
|
||||
, m_pull_callback(std::move(pull_callback))
|
||||
, m_force_push_callback(std::move(force_push_callback))
|
||||
{
|
||||
m_multiline = true;
|
||||
}
|
||||
protected:
|
||||
void init() override;
|
||||
void render_text(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x, const float win_pos_y) override;
|
||||
|
||||
std::function<bool(wxEvtHandler*)> m_pull_callback;
|
||||
std::function<bool(wxEvtHandler*)> m_force_push_callback;
|
||||
};
|
||||
class SlicingProgressNotification;
|
||||
|
||||
// in HintNotification.hpp
|
||||
|
||||
@@ -968,6 +968,11 @@ wxBoxSizer *PreferencesDialog::create_item_checkbox(wxString title, wxString too
|
||||
if (m_sync_user_preset_checkbox) m_sync_user_preset_checkbox->Enable(!enabled);
|
||||
if (m_bambu_cloud_checkbox) m_bambu_cloud_checkbox->Enable(!enabled);
|
||||
}
|
||||
else if (param == "hide_login_side_panel") {
|
||||
if (wxGetApp().mainframe && wxGetApp().mainframe->m_webview) {
|
||||
wxGetApp().mainframe->m_webview->SendCloudProvidersInfo();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __WXMSW__
|
||||
if (param == "associate_3mf") {
|
||||
@@ -1605,9 +1610,12 @@ void PreferencesDialog::create_items()
|
||||
auto item_region = create_item_region_combobox(_L("Login region"), "");
|
||||
g_sizer->Add(item_region);
|
||||
|
||||
auto item_stealth_mode = create_item_checkbox(_L("Stealth mode"), _L("This disables all cloud services e.g. Orca Cloud and Bambu Cloud. This stops the transmission of data to Bambu's cloud services too. Users who don't use BBL machines or use LAN mode only can safely turn on this function."), "stealth_mode");
|
||||
auto item_stealth_mode = create_item_checkbox(_L("Stealth mode"), _L("This disables all cloud features, including Orca Cloud profile syncing. Users who prefer to work entirely offline can enable this option.\nNote: When Stealth Mode is enabled, your user profiles will not be backed up to Orca Cloud."), "stealth_mode");
|
||||
g_sizer->Add(item_stealth_mode);
|
||||
|
||||
auto item_hide_login_side_panel = create_item_checkbox(_L("Hide login side panel"), _L("Hide the login side panel on the home page."), "hide_login_side_panel");
|
||||
g_sizer->Add(item_hide_login_side_panel);
|
||||
|
||||
auto item_network_test = create_item_button(_L("Network test"), _L("Test") + " " + dots, "", _L("Open Network Test"), []() {
|
||||
NetworkTestDialog dlg(wxGetApp().mainframe);
|
||||
dlg.ShowModal();
|
||||
|
||||
@@ -177,11 +177,7 @@ ZUserLogin::ZUserLogin(std::shared_ptr<ICloudServiceAgent> cloud_agent)
|
||||
wxSize pSize = FromDIP(wxSize(650, 840));
|
||||
SetSize(pSize);
|
||||
|
||||
int screenheight = wxSystemSettings::GetMetric(wxSYS_SCREEN_Y, NULL);
|
||||
int screenwidth = wxSystemSettings::GetMetric(wxSYS_SCREEN_X, NULL);
|
||||
int MaxY = (screenheight - pSize.y) > 0 ? (screenheight - pSize.y) / 2 : 0;
|
||||
wxPoint tmpPT((screenwidth - pSize.x) / 2, MaxY);
|
||||
Move(tmpPT);
|
||||
CentreOnParent();
|
||||
}
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
}
|
||||
|
||||
@@ -526,7 +526,7 @@ void WebViewPanel::SendCloudProvidersInfo()
|
||||
json data;
|
||||
json provider_array = json::array();
|
||||
|
||||
if (!app_config->get_stealth_mode()) {
|
||||
if (!app_config->get_hide_login_side_panel()) {
|
||||
auto providers = app_config->get_cloud_providers();
|
||||
for (const auto& p : providers) {
|
||||
provider_array.push_back(p);
|
||||
|
||||
Reference in New Issue
Block a user