/*
Copyright 2015-2018 Amebis
Copyright 2016 GÉANT
This file is part of wxExtend.
wxExtend is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
wxExtend is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with wxExtend. If not, see .
*/
#pragma once
#include
#include
#ifndef USER_DEFAULT_SCREEN_DPI
#define USER_DEFAULT_SCREEN_DPI 96
#endif
/// \addtogroup wxExtend
/// @{
///
/// `wxPersistentDialog` kind for persistent storage
///
#define wxPERSIST_TLW_MONITOR_X "xmon"
#define wxPERSIST_TLW_MONITOR_Y "ymon"
#define wxPERSIST_TLW_MONITOR_W "wmon"
#define wxPERSIST_TLW_MONITOR_H "hmon"
#define wxPERSIST_TLW_DPI_HORZ "xdpi"
#define wxPERSIST_TLW_DPI_VERT "ydpi"
class wxTLWGeometryEx : public wxTLWGeometryBase
{
public:
wxTLWGeometryEx()
{
wxZeroMemory(m_placement);
m_placement.length = sizeof(m_placement);
wxZeroMemory(m_mntinfo);
m_mntinfo.cbSize = sizeof(m_mntinfo);
m_dpiHorz = USER_DEFAULT_SCREEN_DPI;
m_dpiVert = USER_DEFAULT_SCREEN_DPI;
}
virtual bool Save(const Serializer& ser) const wxOVERRIDE
{
// For compatibility with the existing saved positions/sizes, use the
// same keys as the generic version (which was previously used under
// MSW too).
// Normal position and size.
const RECT& rc = m_placement.rcNormalPosition;
if (!ser.SaveField(wxPERSIST_TLW_X, rc.left) ||
!ser.SaveField(wxPERSIST_TLW_Y, rc.top) ||
!ser.SaveField(wxPERSIST_TLW_W, rc.right - rc.left) ||
!ser.SaveField(wxPERSIST_TLW_H, rc.bottom - rc.top))
return false;
// Maximized/minimized state.
UINT show = m_placement.showCmd;
if (!ser.SaveField(wxPERSIST_TLW_MAXIMIZED, show == SW_SHOWMAXIMIZED))
return false;
if (!ser.SaveField(wxPERSIST_TLW_ICONIZED, show == SW_SHOWMINIMIZED))
return false;
// Maximized window position.
const POINT pt = m_placement.ptMaxPosition;
if (!ser.SaveField(wxPERSIST_TLW_MAX_X, pt.x) ||
!ser.SaveField(wxPERSIST_TLW_MAX_Y, pt.y))
return false;
// We don't currently save the minimized window position, it doesn't
// seem useful for anything and is probably just a left over from
// Windows 3.1 days, when icons were positioned on the desktop instead
// of being located in the taskbar.
// Monitor position and size.
const RECT& rcMon = m_mntinfo.rcWork;
if (!ser.SaveField(wxPERSIST_TLW_MONITOR_X, rcMon.left) ||
!ser.SaveField(wxPERSIST_TLW_MONITOR_Y, rcMon.top) ||
!ser.SaveField(wxPERSIST_TLW_MONITOR_W, rcMon.right - rcMon.left) ||
!ser.SaveField(wxPERSIST_TLW_MONITOR_H, rcMon.bottom - rcMon.top))
return false;
// DPI.
if (!ser.SaveField(wxPERSIST_TLW_DPI_HORZ, m_dpiHorz) ||
!ser.SaveField(wxPERSIST_TLW_DPI_VERT, m_dpiVert))
return false;
return true;
}
virtual bool Restore(Serializer& ser) wxOVERRIDE
{
// Normal position and size.
wxRect r;
if (!ser.RestoreField(wxPERSIST_TLW_X, &r.x) ||
!ser.RestoreField(wxPERSIST_TLW_Y, &r.y) ||
!ser.RestoreField(wxPERSIST_TLW_W, &r.width) ||
!ser.RestoreField(wxPERSIST_TLW_H, &r.height))
return false;
wxCopyRectToRECT(r, m_placement.rcNormalPosition);
// Maximized/minimized state.
//
// Note the special case of SW_MINIMIZE: while GetWindowPlacement()
// returns SW_SHOWMINIMIZED when the window is iconized, we restore it
// as SW_MINIMIZE as this is what the code in wxTLW checks to determine
// whether the window is supposed to be iconized or not.
//
// Just to confuse matters further, note that SW_MAXIMIZE is exactly
// the same thing as SW_SHOWMAXIMIZED.
int tmp;
UINT& show = m_placement.showCmd;
if (ser.RestoreField(wxPERSIST_TLW_MAXIMIZED, &tmp) && tmp)
show = SW_MAXIMIZE;
else if (ser.RestoreField(wxPERSIST_TLW_ICONIZED, &tmp) && tmp)
show = SW_MINIMIZE;
else
show = SW_SHOWNORMAL;
// Maximized window position.
if (ser.RestoreField(wxPERSIST_TLW_MAX_X, &r.x) &&
ser.RestoreField(wxPERSIST_TLW_MAX_Y, &r.y))
{
m_placement.ptMaxPosition.x = r.x;
m_placement.ptMaxPosition.y = r.y;
} else {
m_placement.ptMaxPosition.x = -1;
m_placement.ptMaxPosition.y = -1;
}
m_placement.ptMinPosition.x = -1;
m_placement.ptMinPosition.y = -1;
// Monitor position and size.
wxRect rmon;
if (!ser.RestoreField(wxPERSIST_TLW_MONITOR_X, &rmon.x) ||
!ser.RestoreField(wxPERSIST_TLW_MONITOR_Y, &rmon.y) ||
!ser.RestoreField(wxPERSIST_TLW_MONITOR_W, &rmon.width) ||
!ser.RestoreField(wxPERSIST_TLW_MONITOR_H, &rmon.height))
return false;
wxCopyRectToRECT(rmon, m_mntinfo.rcWork);
// DPI.
if (!ser.RestoreField(wxPERSIST_TLW_DPI_HORZ, &r.x) ||
!ser.RestoreField(wxPERSIST_TLW_DPI_VERT, &r.y))
return false;
m_dpiHorz = r.x;
m_dpiVert = r.y;
return true;
}
virtual bool GetFrom(const wxTopLevelWindow* tlw) wxOVERRIDE
{
WXHWND hWnd = GetHwndOf(tlw);
if (!::GetWindowPlacement(hWnd, &m_placement))
{
wxLogLastError(wxS("GetWindowPlacement"));
return false;
}
HMONITOR hMonitor = ::MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
wxASSERT_MSG(hMonitor, wxT("error locating monitor"));
if (!::GetMonitorInfo(hMonitor, &m_mntinfo))
{
wxLogLastError(wxS("GetMonitorInfo"));
return false;
}
GetDPI(hWnd, &m_dpiHorz, &m_dpiVert) || GetDPI(hMonitor, &m_dpiHorz, &m_dpiVert);
return true;
}
virtual bool ApplyTo(wxTopLevelWindow* tlw) wxOVERRIDE
{
// There is a subtlety here: if the window is currently hidden,
// restoring its geometry shouldn't show it, so we must use SW_HIDE as
// show command, but showing it later should restore it to the correct
// state, so we need to remember it in wxTLW itself. And even if it's
// currently shown, we still need to update its show command, so that
// it matches the real window state after SetWindowPlacement() call.
tlw->MSWSetShowCommand(m_placement.showCmd);
if (!tlw->IsShown())
{
m_placement.showCmd = SW_HIDE;
}
// Get monitor to restore window to.
HMONITOR hMonitor = ::MonitorFromRect(&m_mntinfo.rcWork, MONITOR_DEFAULTTONEAREST);
wxASSERT_MSG(hMonitor, wxT("error locating monitor"));
MONITORINFO mntinfo;
mntinfo.cbSize = sizeof(mntinfo);
if (!::GetMonitorInfo(hMonitor, &mntinfo))
{
wxLogLastError(wxS("GetMonitorInfo"));
return false;
}
UINT dpiHorz, dpiVert;
GetDPI(hMonitor, &dpiHorz, &dpiVert);
SIZE
sizeWorkPrev = {
m_mntinfo.rcWork.right - m_mntinfo.rcWork.left,
m_mntinfo.rcWork.bottom - m_mntinfo.rcWork.top
},
sizeWork = {
mntinfo.rcWork.right - mntinfo.rcWork.left,
mntinfo.rcWork.bottom - mntinfo.rcWork.top
};
//
// Project the coordinates:
// - Position relative to monitor working area center.
// - Scale according to DPI.
//
if (m_placement.ptMaxPosition.x != -1 && m_placement.ptMaxPosition.y != -1) {
m_placement.ptMaxPosition.x = wxMulDivInt32(m_placement.ptMaxPosition.x - m_mntinfo.rcWork.left, sizeWork.cx, sizeWorkPrev.cx) + mntinfo.rcWork.left;
m_placement.ptMaxPosition.y = wxMulDivInt32(m_placement.ptMaxPosition.y - m_mntinfo.rcWork.top, sizeWork.cy, sizeWorkPrev.cy) + mntinfo.rcWork.top;
}
SIZE sizeWndPrev, sizeWnd;
HWND hWnd = GetHwndOf(tlw);
if (tlw->GetWindowStyle() & wxRESIZE_BORDER) {
sizeWndPrev.cx = m_placement.rcNormalPosition.right - m_placement.rcNormalPosition.left;
sizeWndPrev.cy = m_placement.rcNormalPosition.bottom - m_placement.rcNormalPosition.top;
sizeWnd.cx = wxMulDivInt32(sizeWndPrev.cx, dpiHorz, m_dpiHorz);
sizeWnd.cy = wxMulDivInt32(sizeWndPrev.cy, dpiVert, m_dpiVert);
} else {
// The window is not resizable. Do not change its size.
WINDOWPLACEMENT placement = { sizeof(placement) };
if (!::GetWindowPlacement(hWnd, &placement))
{
wxLogLastError(wxS("GetWindowPlacement"));
return false;
}
SIZE size = {
placement.rcNormalPosition.right - placement.rcNormalPosition.left,
placement.rcNormalPosition.bottom - placement.rcNormalPosition.top
};
UINT dpiWndHorz, dpiWndVert;
GetDPI(hWnd, &dpiWndHorz, &dpiWndVert) || GetDPI(::MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST), &dpiWndHorz, &dpiWndVert);
sizeWndPrev.cx = wxMulDivInt32(size.cx, m_dpiHorz, dpiWndHorz);
sizeWndPrev.cy = wxMulDivInt32(size.cy, m_dpiVert, dpiWndVert);
sizeWnd.cx = wxMulDivInt32(size.cx, dpiHorz, dpiWndHorz);
sizeWnd.cy = wxMulDivInt32(size.cy, dpiVert, dpiWndVert);
}
m_placement.rcNormalPosition.left = wxMulDivInt32(m_placement.rcNormalPosition.left + sizeWndPrev.cx / 2 - m_mntinfo.rcWork.left, sizeWork.cx, sizeWorkPrev.cx) + mntinfo.rcWork.left - sizeWnd.cx / 2;
m_placement.rcNormalPosition.top = wxMulDivInt32(m_placement.rcNormalPosition.top + sizeWndPrev.cy / 2 - m_mntinfo.rcWork.top, sizeWork.cy, sizeWorkPrev.cy) + mntinfo.rcWork.top - sizeWnd.cy / 2;
m_placement.rcNormalPosition.right = m_placement.rcNormalPosition.left + sizeWnd.cx;
m_placement.rcNormalPosition.bottom = m_placement.rcNormalPosition.top + sizeWnd.cy;
if (!::SetWindowPlacement(hWnd, &m_placement))
{
wxLogLastError(wxS("SetWindowPlacement"));
return false;
}
return true;
}
private:
static bool GetDPI(HWND hWnd, UINT *dpiHorz, UINT *dpiVert)
{
wxASSERT(dpiHorz);
wxASSERT(dpiVert);
#if wxUSE_DYNLIB_CLASS
typedef UINT(WINAPI *GetDpiForWindow_t)(HWND);
static bool s_checkedGetDpiForWindow = false;
static GetDpiForWindow_t s_pfnGetDpiForWindow = NULL;
if (!s_checkedGetDpiForWindow && s_dllUser32.IsLoaded()) {
s_pfnGetDpiForWindow = (GetDpiForWindow_t)s_dllUser32.RawGetSymbol(wxT("GetDpiForWindow"));
s_checkedGetDpiForWindow = true;
}
if (s_pfnGetDpiForWindow) {
*dpiHorz = *dpiVert = s_pfnGetDpiForWindow(hWnd);
return true;
}
#endif
*dpiHorz = *dpiVert = USER_DEFAULT_SCREEN_DPI;
return false;
}
static bool GetDPI(HMONITOR hMonitor, UINT *dpiHorz, UINT *dpiVert)
{
wxASSERT(dpiHorz);
wxASSERT(dpiVert);
#if wxUSE_DYNLIB_CLASS
enum MONITOR_DPI_TYPE {
MDT_EFFECTIVE_DPI = 0,
MDT_ANGULAR_DPI = 1,
MDT_RAW_DPI = 2,
MDT_DEFAULT = MDT_EFFECTIVE_DPI
};
typedef HRESULT(WINAPI *GetDpiForMonitor_t)(HMONITOR, MONITOR_DPI_TYPE, UINT *, UINT *);
static GetDpiForMonitor_t s_pfnGetDpiForMonitor = NULL;
if (!s_pfnGetDpiForMonitor) {
if (s_dllShCore.IsLoaded())
s_pfnGetDpiForMonitor = (GetDpiForMonitor_t)s_dllShCore.GetSymbol(wxT("GetDpiForMonitor"));
}
if (s_pfnGetDpiForMonitor) {
s_pfnGetDpiForMonitor(hMonitor, MDT_DEFAULT, dpiHorz, dpiVert);
return true;
}
#endif
*dpiHorz = *dpiVert = USER_DEFAULT_SCREEN_DPI;
return false;
}
private:
WINDOWPLACEMENT m_placement;
MONITORINFO m_mntinfo;
UINT m_dpiHorz;
UINT m_dpiVert;
#if wxUSE_DYNLIB_CLASS
static wxDynamicLibrary s_dllUser32;
static wxDynamicLibrary s_dllShCore;
#endif
};