2018-10-04 09:12:55 +00:00
|
|
|
#include "GUI_Utils.hpp"
|
|
|
|
|
2018-10-17 12:01:10 +00:00
|
|
|
#include <algorithm>
|
|
|
|
#include <boost/lexical_cast.hpp>
|
|
|
|
#include <boost/format.hpp>
|
|
|
|
|
2019-04-02 10:00:50 +00:00
|
|
|
#ifdef _WIN32
|
|
|
|
#include <Windows.h>
|
|
|
|
#endif
|
|
|
|
|
2018-10-17 12:01:10 +00:00
|
|
|
#include <wx/toplevel.h>
|
2018-10-04 09:12:55 +00:00
|
|
|
#include <wx/sizer.h>
|
|
|
|
#include <wx/checkbox.h>
|
2019-04-02 10:00:50 +00:00
|
|
|
#include <wx/dcclient.h>
|
2019-05-17 11:15:32 +00:00
|
|
|
#include <wx/font.h>
|
|
|
|
#include <wx/fontutil.h>
|
2018-10-04 09:12:55 +00:00
|
|
|
|
2018-10-17 12:01:10 +00:00
|
|
|
#include "libslic3r/Config.hpp"
|
|
|
|
|
2018-10-04 09:12:55 +00:00
|
|
|
|
|
|
|
namespace Slic3r {
|
|
|
|
namespace GUI {
|
|
|
|
|
|
|
|
|
2018-10-18 13:13:38 +00:00
|
|
|
wxTopLevelWindow* find_toplevel_parent(wxWindow *window)
|
|
|
|
{
|
|
|
|
for (; window != nullptr; window = window->GetParent()) {
|
|
|
|
if (window->IsTopLevel()) {
|
|
|
|
return dynamic_cast<wxTopLevelWindow*>(window);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2019-02-07 14:55:47 +00:00
|
|
|
void on_window_geometry(wxTopLevelWindow *tlw, std::function<void()> callback)
|
|
|
|
{
|
2019-02-08 11:05:12 +00:00
|
|
|
#ifdef _WIN32
|
|
|
|
// On windows, the wxEVT_SHOW is not received if the window is created maximized
|
|
|
|
// cf. https://groups.google.com/forum/#!topic/wx-users/c7ntMt6piRI
|
|
|
|
// OTOH the geometry is available very soon, so we can call the callback right away
|
|
|
|
callback();
|
|
|
|
#elif defined __linux__
|
|
|
|
tlw->Bind(wxEVT_SHOW, [=](wxShowEvent &evt) {
|
|
|
|
// On Linux, the geometry is only available after wxEVT_SHOW + CallAfter
|
2019-02-07 14:55:47 +00:00
|
|
|
// cf. https://groups.google.com/forum/?pli=1#!topic/wx-users/fERSXdpVwAI
|
2019-02-08 11:05:12 +00:00
|
|
|
tlw->CallAfter([=]() { callback(); });
|
|
|
|
evt.Skip();
|
2019-02-07 14:55:47 +00:00
|
|
|
});
|
2019-02-08 11:05:12 +00:00
|
|
|
#elif defined __APPLE__
|
|
|
|
tlw->Bind(wxEVT_SHOW, [=](wxShowEvent &evt) {
|
|
|
|
callback();
|
|
|
|
evt.Skip();
|
|
|
|
});
|
|
|
|
#endif
|
2019-02-07 14:55:47 +00:00
|
|
|
}
|
|
|
|
|
2019-04-02 10:00:50 +00:00
|
|
|
wxDEFINE_EVENT(EVT_DPI_CHANGED, DpiChangedEvent);
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
template<class F> typename F::FN winapi_get_function(const wchar_t *dll, const char *fn_name) {
|
|
|
|
static HINSTANCE dll_handle = LoadLibraryExW(dll, nullptr, 0);
|
|
|
|
|
|
|
|
if (dll_handle == nullptr) { return nullptr; }
|
|
|
|
return (F::FN)GetProcAddress(dll_handle, fn_name);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-05-06 16:28:23 +00:00
|
|
|
// If called with nullptr, a DPI for the primary monitor is returned.
|
2019-04-02 10:00:50 +00:00
|
|
|
int get_dpi_for_window(wxWindow *window)
|
|
|
|
{
|
|
|
|
#ifdef _WIN32
|
|
|
|
enum MONITOR_DPI_TYPE_ {
|
|
|
|
// This enum is inlined here to avoid build-time dependency
|
|
|
|
MDT_EFFECTIVE_DPI_ = 0,
|
|
|
|
MDT_ANGULAR_DPI_ = 1,
|
|
|
|
MDT_RAW_DPI_ = 2,
|
|
|
|
MDT_DEFAULT_ = MDT_EFFECTIVE_DPI_,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Need strong types for winapi_get_function() to work
|
|
|
|
struct GetDpiForWindow_t { typedef HRESULT (WINAPI *FN)(HWND hwnd); };
|
|
|
|
struct GetDpiForMonitor_t { typedef HRESULT (WINAPI *FN)(HMONITOR hmonitor, MONITOR_DPI_TYPE_ dpiType, UINT *dpiX, UINT *dpiY); };
|
|
|
|
|
|
|
|
static auto GetDpiForWindow_fn = winapi_get_function<GetDpiForWindow_t>(L"User32.dll", "GetDpiForWindow");
|
|
|
|
static auto GetDpiForMonitor_fn = winapi_get_function<GetDpiForMonitor_t>(L"Shcore.dll", "GetDpiForMonitor");
|
|
|
|
|
2019-05-06 16:28:23 +00:00
|
|
|
// Desktop Window is the window of the primary monitor.
|
|
|
|
const HWND hwnd = (window == nullptr) ? ::GetDesktopWindow() : window->GetHandle();
|
2019-04-02 10:00:50 +00:00
|
|
|
|
|
|
|
if (GetDpiForWindow_fn != nullptr) {
|
|
|
|
// We're on Windows 10, we have per-screen DPI settings
|
|
|
|
return GetDpiForWindow_fn(hwnd);
|
|
|
|
} else if (GetDpiForMonitor_fn != nullptr) {
|
|
|
|
// We're on Windows 8.1, we have per-system DPI
|
|
|
|
// Note: MonitorFromWindow() is available on all Windows.
|
|
|
|
|
|
|
|
const HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
|
|
|
UINT dpiX;
|
|
|
|
UINT dpiY;
|
|
|
|
return GetDpiForMonitor_fn(monitor, MDT_EFFECTIVE_DPI_, &dpiX, &dpiY) == S_OK ? dpiX : DPI_DEFAULT;
|
|
|
|
} else {
|
|
|
|
// We're on Windows earlier than 8.1, use DC
|
|
|
|
|
|
|
|
const HDC hdc = GetDC(hwnd);
|
|
|
|
if (hdc == NULL) { return DPI_DEFAULT; }
|
|
|
|
return GetDeviceCaps(hdc, LOGPIXELSX);
|
|
|
|
}
|
|
|
|
#elif defined __linux__
|
|
|
|
// TODO
|
|
|
|
return DPI_DEFAULT;
|
|
|
|
#elif defined __APPLE__
|
|
|
|
// TODO
|
|
|
|
return DPI_DEFAULT;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2019-05-17 11:15:32 +00:00
|
|
|
wxFont get_default_font_for_dpi(int dpi)
|
|
|
|
{
|
|
|
|
#ifdef _WIN32
|
|
|
|
// First try to load the font with the Windows 10 specific way.
|
|
|
|
struct SystemParametersInfoForDpi_t { typedef BOOL (WINAPI *FN)(UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWinIni, UINT dpi); };
|
|
|
|
static auto SystemParametersInfoForDpi_fn = winapi_get_function<SystemParametersInfoForDpi_t>(L"User32.dll", "SystemParametersInfoForDpi");
|
|
|
|
if (SystemParametersInfoForDpi_fn != nullptr) {
|
|
|
|
NONCLIENTMETRICS nm;
|
|
|
|
memset(&nm, 0, sizeof(NONCLIENTMETRICS));
|
|
|
|
nm.cbSize = sizeof(NONCLIENTMETRICS);
|
2019-05-17 11:41:19 +00:00
|
|
|
if (SystemParametersInfoForDpi_fn(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &nm, 0, dpi)) {
|
2019-05-17 11:15:32 +00:00
|
|
|
wxNativeFontInfo info;
|
|
|
|
info.lf = nm.lfMessageFont;
|
|
|
|
return wxFont(info);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Then try to guesstimate the font DPI scaling on Windows 8.
|
|
|
|
// Let's hope that the font returned by the SystemParametersInfo(), which is used by wxWidgets internally, makes sense.
|
|
|
|
int dpi_primary = get_dpi_for_window(nullptr);
|
|
|
|
if (dpi_primary != dpi) {
|
|
|
|
// Rescale the font.
|
|
|
|
return wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Scaled(float(dpi) / float(dpi_primary));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
|
|
|
}
|
2018-10-18 13:13:38 +00:00
|
|
|
|
2018-10-19 13:12:38 +00:00
|
|
|
CheckboxFileDialog::ExtraPanel::ExtraPanel(wxWindow *parent)
|
|
|
|
: wxPanel(parent, wxID_ANY)
|
|
|
|
{
|
|
|
|
// WARN: wxMSW does some extra shenanigans to calc the extra control size.
|
|
|
|
// It first calls the create function with a dummy empty wxDialog parent and saves its size.
|
|
|
|
// Afterwards, the create function is called again with the real parent.
|
|
|
|
// Additionally there's no way to pass any extra data to the create function (no closure),
|
|
|
|
// which is why we have to this stuff here. Grrr!
|
|
|
|
auto *dlg = dynamic_cast<CheckboxFileDialog*>(parent);
|
2018-10-19 13:35:39 +00:00
|
|
|
const wxString checkbox_label(dlg != nullptr ? dlg->checkbox_label : wxString("String long enough to contain dlg->checkbox_label"));
|
2018-10-19 13:12:38 +00:00
|
|
|
|
|
|
|
auto* sizer = new wxBoxSizer(wxHORIZONTAL);
|
|
|
|
cbox = new wxCheckBox(this, wxID_ANY, checkbox_label);
|
2018-10-22 09:45:03 +00:00
|
|
|
cbox->SetValue(true);
|
2018-10-19 13:12:38 +00:00
|
|
|
sizer->AddSpacer(5);
|
|
|
|
sizer->Add(this->cbox, 0, wxEXPAND | wxALL, 5);
|
|
|
|
SetSizer(sizer);
|
|
|
|
sizer->SetSizeHints(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
wxWindow* CheckboxFileDialog::ExtraPanel::ctor(wxWindow *parent) {
|
|
|
|
return new ExtraPanel(parent);
|
|
|
|
}
|
|
|
|
|
2018-10-04 09:12:55 +00:00
|
|
|
CheckboxFileDialog::CheckboxFileDialog(wxWindow *parent,
|
2018-10-17 12:01:10 +00:00
|
|
|
const wxString &checkbox_label,
|
2018-10-04 09:12:55 +00:00
|
|
|
bool checkbox_value,
|
|
|
|
const wxString &message,
|
|
|
|
const wxString &default_dir,
|
|
|
|
const wxString &default_file,
|
|
|
|
const wxString &wildcard,
|
|
|
|
long style,
|
|
|
|
const wxPoint &pos,
|
|
|
|
const wxSize &size,
|
|
|
|
const wxString &name
|
|
|
|
)
|
|
|
|
: wxFileDialog(parent, message, default_dir, default_file, wildcard, style, pos, size, name)
|
2018-10-19 13:12:38 +00:00
|
|
|
, checkbox_label(checkbox_label)
|
2018-10-04 09:12:55 +00:00
|
|
|
{
|
2018-10-17 12:01:10 +00:00
|
|
|
if (checkbox_label.IsEmpty()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-10-19 13:12:38 +00:00
|
|
|
SetExtraControlCreator(ExtraPanel::ctor);
|
2018-10-04 09:12:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool CheckboxFileDialog::get_checkbox_value() const
|
|
|
|
{
|
2018-10-19 13:12:38 +00:00
|
|
|
auto *extra_panel = dynamic_cast<ExtraPanel*>(GetExtraControl());
|
|
|
|
return extra_panel != nullptr ? extra_panel->cbox->GetValue() : false;
|
2018-10-19 11:38:35 +00:00
|
|
|
}
|
2018-10-17 12:01:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
WindowMetrics WindowMetrics::from_window(wxTopLevelWindow *window)
|
|
|
|
{
|
|
|
|
WindowMetrics res;
|
|
|
|
res.rect = window->GetScreenRect();
|
|
|
|
res.maximized = window->IsMaximized();
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
boost::optional<WindowMetrics> WindowMetrics::deserialize(const std::string &str)
|
|
|
|
{
|
|
|
|
std::vector<std::string> metrics_str;
|
|
|
|
metrics_str.reserve(5);
|
|
|
|
|
|
|
|
if (!unescape_strings_cstyle(str, metrics_str) || metrics_str.size() != 5) {
|
|
|
|
return boost::none;
|
|
|
|
}
|
|
|
|
|
|
|
|
int metrics[5];
|
|
|
|
try {
|
|
|
|
for (size_t i = 0; i < 5; i++) {
|
|
|
|
metrics[i] = boost::lexical_cast<int>(metrics_str[i]);
|
|
|
|
}
|
|
|
|
} catch(const boost::bad_lexical_cast &) {
|
|
|
|
return boost::none;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((metrics[4] & ~1) != 0) { // Checks if the maximized flag is 1 or 0
|
|
|
|
metrics[4] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
WindowMetrics res;
|
|
|
|
res.rect = wxRect(metrics[0], metrics[1], metrics[2], metrics[3]);
|
2018-11-08 19:18:40 +00:00
|
|
|
res.maximized = metrics[4] != 0;
|
2018-10-17 12:01:10 +00:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
void WindowMetrics::sanitize_for_display(const wxRect &screen_rect)
|
|
|
|
{
|
|
|
|
rect = rect.Intersect(screen_rect);
|
2019-01-11 17:09:21 +00:00
|
|
|
|
|
|
|
// Prevent the window from going too far towards the right and/or bottom edge
|
|
|
|
// It's hardcoded here that the threshold is 80% of the screen size
|
|
|
|
rect.x = std::min(rect.x, screen_rect.x + 4*screen_rect.width/5);
|
|
|
|
rect.y = std::min(rect.y, screen_rect.y + 4*screen_rect.height/5);
|
2018-10-17 12:01:10 +00:00
|
|
|
}
|
|
|
|
|
2019-01-11 17:09:21 +00:00
|
|
|
std::string WindowMetrics::serialize() const
|
2018-10-17 12:01:10 +00:00
|
|
|
{
|
|
|
|
return (boost::format("%1%; %2%; %3%; %4%; %5%")
|
2019-01-11 17:09:21 +00:00
|
|
|
% rect.x
|
|
|
|
% rect.y
|
|
|
|
% rect.width
|
|
|
|
% rect.height
|
2018-10-17 12:01:10 +00:00
|
|
|
% static_cast<int>(maximized)
|
|
|
|
).str();
|
2018-10-04 09:12:55 +00:00
|
|
|
}
|
|
|
|
|
2019-01-11 17:09:21 +00:00
|
|
|
std::ostream& operator<<(std::ostream &os, const WindowMetrics& metrics)
|
|
|
|
{
|
|
|
|
return os << '(' << metrics.serialize() << ')';
|
|
|
|
}
|
2018-10-04 09:12:55 +00:00
|
|
|
|
2018-10-17 12:01:10 +00:00
|
|
|
|
2018-10-04 09:12:55 +00:00
|
|
|
}
|
|
|
|
}
|