BambuStudio/slic3r/GUI/BonjourDialog.cpp

273 lines
8.2 KiB
C++
Raw Normal View History

2024-12-20 06:44:50 +00:00
#include "slic3r/Utils/Bonjour.hpp" // On Windows, boost needs to be included before wxWidgets headers
#include "BonjourDialog.hpp"
#include <set>
#include <mutex>
#include <wx/sizer.h>
#include <wx/button.h>
#include <wx/listctrl.h>
#include <wx/stattext.h>
#include <wx/timer.h>
#include <wx/wupdlock.h>
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/Utils/Bonjour.hpp"
#include "Widgets/Button.hpp"
namespace Slic3r {
class BonjourReplyEvent : public wxEvent
{
public:
BonjourReply reply;
BonjourReplyEvent(wxEventType eventType, int winid, BonjourReply &&reply) :
wxEvent(winid, eventType),
reply(std::move(reply))
{}
virtual wxEvent *Clone() const
{
return new BonjourReplyEvent(*this);
}
};
wxDEFINE_EVENT(EVT_BONJOUR_REPLY, BonjourReplyEvent);
wxDECLARE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent);
wxDEFINE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent);
class ReplySet: public std::set<BonjourReply> {};
struct LifetimeGuard
{
std::mutex mutex;
BonjourDialog *dialog;
LifetimeGuard(BonjourDialog *dialog) : dialog(dialog) {}
};
BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech)
: wxDialog(parent, wxID_ANY, _(L("Network lookup")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
, list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxSIMPLE_BORDER))
, replies(new ReplySet)
, label(new wxStaticText(this, wxID_ANY, ""))
, timer(new wxTimer())
, timer_state(0)
, tech(tech)
{
SetBackgroundColour(*wxWHITE);
const int em = GUI::wxGetApp().em_unit();
list->SetMinSize(wxSize(80 * em, 30 * em));
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, em);
list->SetSingleStyle(wxLC_SINGLE_SEL);
list->SetSingleStyle(wxLC_SORT_DESCENDING);
list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 5 * em);
list->AppendColumn(_(L("Hostname")), wxLIST_FORMAT_LEFT, 10 * em);
list->AppendColumn(_(L("Service name")), wxLIST_FORMAT_LEFT, 20 * em);
if (tech == ptFFF) {
list->AppendColumn(_(L("OctoPrint version")), wxLIST_FORMAT_LEFT, 5 * em);
}
vsizer->Add(list, 1, wxEXPAND | wxALL, em);
auto button_sizer = new wxBoxSizer(wxHORIZONTAL);
StateColor btn_bg_green(std::pair<wxColour, int>(wxColour(27, 136, 68), StateColor::Pressed), std::pair<wxColour, int>(wxColour(61, 203, 115), StateColor::Hovered),
//std::pair<wxColour, int>(wxColour(0, 174, 66), StateColor::Normal));
std::pair<wxColour, int>(wxColour(0x9A5F21), StateColor::Normal));
2024-12-20 06:44:50 +00:00
StateColor btn_bg_white(std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed), std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(*wxWHITE, StateColor::Normal));
auto m_button_ok = new Button(this, _L("OK"));
m_button_ok->SetBackgroundColor(btn_bg_green);
m_button_ok->SetBorderColor(*wxWHITE);
m_button_ok->SetTextColor(*wxWHITE);
m_button_ok->SetFont(Label::Body_12);
m_button_ok->SetSize(wxSize(FromDIP(58), FromDIP(24)));
m_button_ok->SetMinSize(wxSize(FromDIP(58), FromDIP(24)));
m_button_ok->SetCornerRadius(FromDIP(12));
m_button_ok->Bind(wxEVT_LEFT_DOWN, [this](auto &e) { this->EndModal(wxID_OK); });
auto m_button_cancel = new Button(this, _L("Cancel"));
m_button_cancel->SetBackgroundColor(btn_bg_white);
m_button_cancel->SetBorderColor(wxColour(38, 46, 48));
m_button_cancel->SetFont(Label::Body_12);
m_button_cancel->SetSize(wxSize(FromDIP(58), FromDIP(24)));
m_button_cancel->SetMinSize(wxSize(FromDIP(58), FromDIP(24)));
m_button_cancel->SetCornerRadius(FromDIP(12));
m_button_cancel->Bind(wxEVT_LEFT_DOWN, [this](auto &e) { this->EndModal(wxID_CANCEL); });
button_sizer->AddStretchSpacer();
button_sizer->Add(m_button_ok, 0, wxALL, FromDIP(5));
button_sizer->Add(m_button_cancel, 0, wxALL, FromDIP(5));
vsizer->Add(button_sizer, 0, wxALIGN_CENTER);
SetSizerAndFit(vsizer);
Bind(EVT_BONJOUR_REPLY, &BonjourDialog::on_reply, this);
Bind(EVT_BONJOUR_COMPLETE, [this](wxCommandEvent &) {
this->timer_state = 0;
});
Bind(wxEVT_TIMER, &BonjourDialog::on_timer, this);
GUI::wxGetApp().UpdateDlgDarkUI(this);
}
BonjourDialog::~BonjourDialog()
{
// Needed bacuse of forward defs
}
bool BonjourDialog::show_and_lookup()
{
Show(); // Because we need GetId() to work before ShowModal()
timer->Stop();
timer->SetOwner(this);
timer_state = 1;
timer->Start(1000);
on_timer_process();
// The background thread needs to queue messages for this dialog
// and for that it needs a valid pointer to it (mandated by the wxWidgets API).
// Here we put the pointer under a shared_ptr and protect it by a mutex,
// so that both threads can access it safely.
auto dguard = std::make_shared<LifetimeGuard>(this);
// Note: More can be done here when we support discovery of hosts other than Octoprint and SL1
Bonjour::TxtKeys txt_keys { "version", "model" };
bonjour = Bonjour("octoprint")
.set_txt_keys(std::move(txt_keys))
.set_retries(3)
.set_timeout(4)
.on_reply([dguard](BonjourReply &&reply) {
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
auto dialog = dguard->dialog;
if (dialog != nullptr) {
auto evt = new BonjourReplyEvent(EVT_BONJOUR_REPLY, dialog->GetId(), std::move(reply));
wxQueueEvent(dialog, evt);
}
})
.on_complete([dguard]() {
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
auto dialog = dguard->dialog;
if (dialog != nullptr) {
auto evt = new wxCommandEvent(EVT_BONJOUR_COMPLETE, dialog->GetId());
wxQueueEvent(dialog, evt);
}
})
.lookup();
bool res = ShowModal() == wxID_OK && list->GetFirstSelected() >= 0;
{
// Tell the background thread the dialog is going away...
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
dguard->dialog = nullptr;
}
return res;
}
wxString BonjourDialog::get_selected() const
{
auto sel = list->GetFirstSelected();
return sel >= 0 ? list->GetItemText(sel) : wxString();
}
// Private
void BonjourDialog::on_reply(BonjourReplyEvent &e)
{
if (replies->find(e.reply) != replies->end()) {
// We already have this reply
return;
}
// Filter replies based on selected technology
const auto model = e.reply.txt_data.find("model");
const bool sl1 = model != e.reply.txt_data.end() && model->second == "SL1";
if ((tech == ptFFF && sl1) || (tech == ptSLA && !sl1)) {
return;
}
replies->insert(std::move(e.reply));
auto selected = get_selected();
wxWindowUpdateLocker freeze_guard(this);
(void)freeze_guard;
list->DeleteAllItems();
// The whole list is recreated so that we benefit from it already being sorted in the set.
// (And also because wxListView's sorting API is bananas.)
for (const auto &reply : *replies) {
auto item = list->InsertItem(0, reply.full_address);
list->SetItem(item, 1, reply.hostname);
list->SetItem(item, 2, reply.service_name);
if (tech == ptFFF) {
const auto it = reply.txt_data.find("version");
if (it != reply.txt_data.end()) {
list->SetItem(item, 3, GUI::from_u8(it->second));
}
}
}
const int em = GUI::wxGetApp().em_unit();
for (int i = 0; i < list->GetColumnCount(); i++) {
list->SetColumnWidth(i, wxLIST_AUTOSIZE);
if (list->GetColumnWidth(i) < 10 * em) { list->SetColumnWidth(i, 10 * em); }
}
if (!selected.IsEmpty()) {
// Attempt to preserve selection
auto hit = list->FindItem(-1, selected);
if (hit >= 0) { list->SetItemState(hit, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); }
}
}
void BonjourDialog::on_timer(wxTimerEvent &)
{
on_timer_process();
}
// This is here so the function can be bound to wxEVT_TIMER and also called
// explicitly (wxTimerEvent should not be created by user code).
void BonjourDialog::on_timer_process()
{
const auto search_str = _utf8(L("Searching for devices"));
if (timer_state > 0) {
const std::string dots(timer_state, '.');
label->SetLabel(GUI::from_u8((boost::format("%1% %2%") % search_str % dots).str()));
timer_state = (timer_state) % 3 + 1;
} else {
label->SetLabel(GUI::from_u8((boost::format("%1%: %2%") % search_str % (_utf8(L("Finished"))+".")).str()));
timer->Stop();
}
}
}