1// Windows/Control/Dialog.cpp
2
3#include "StdAfx.h"
4
5// #include "../../Windows/DLL.h"
6
7#ifndef _UNICODE
8#include "../../Common/StringConvert.h"
9#endif
10
11#include "Dialog.h"
12
13extern HINSTANCE g_hInstance;
14#ifndef _UNICODE
15extern bool g_IsNT;
16#endif
17
18namespace NWindows {
19namespace NControl {
20
21static
22#ifdef Z7_OLD_WIN_SDK
23  BOOL
24#else
25  INT_PTR
26#endif
27APIENTRY
28DialogProcedure(HWND dialogHWND, UINT message, WPARAM wParam, LPARAM lParam)
29{
30  CWindow tempDialog(dialogHWND);
31  if (message == WM_INITDIALOG)
32    tempDialog.SetUserDataLongPtr(lParam);
33  CDialog *dialog = (CDialog *)(tempDialog.GetUserDataLongPtr());
34  if (dialog == NULL)
35    return FALSE;
36  if (message == WM_INITDIALOG)
37    dialog->Attach(dialogHWND);
38
39  /* MSDN: The dialog box procedure should return
40       TRUE  - if it processed the message
41       FALSE - if it did not process the message
42     If the dialog box procedure returns FALSE,
43     the dialog manager performs the default dialog operation in response to the message.
44  */
45
46  try { return BoolToBOOL(dialog->OnMessage(message, wParam, lParam)); }
47  catch(...) { return TRUE; }
48}
49
50bool CDialog::OnMessage(UINT message, WPARAM wParam, LPARAM lParam)
51{
52  switch (message)
53  {
54    case WM_INITDIALOG: return OnInit();
55    case WM_COMMAND: return OnCommand(HIWORD(wParam), LOWORD(wParam), lParam);
56    case WM_NOTIFY: return OnNotify((UINT)wParam, (LPNMHDR) lParam);
57    case WM_TIMER: return OnTimer(wParam, lParam);
58    case WM_SIZE: return OnSize(wParam, LOWORD(lParam), HIWORD(lParam));
59    case WM_DESTROY: return OnDestroy();
60    case WM_HELP: OnHelp(); return true;
61    /*
62        OnHelp(
63          #ifdef UNDER_CE
64          (void *)
65          #else
66          (LPHELPINFO)
67          #endif
68          lParam);
69        return true;
70    */
71    default: return false;
72  }
73}
74
75/*
76bool CDialog::OnCommand2(WPARAM wParam, LPARAM lParam)
77{
78  return OnCommand(HIWORD(wParam), LOWORD(wParam), lParam);
79}
80*/
81
82bool CDialog::OnCommand(unsigned code, unsigned itemID, LPARAM lParam)
83{
84  if (code == BN_CLICKED)
85    return OnButtonClicked(itemID, (HWND)lParam);
86  return false;
87}
88
89bool CDialog::OnButtonClicked(unsigned buttonID, HWND /* buttonHWND */)
90{
91  switch (buttonID)
92  {
93    case IDOK: OnOK(); break;
94    case IDCANCEL: OnCancel(); break;
95    case IDCLOSE: OnClose(); break;
96    case IDHELP: OnHelp(); break;
97    default: return false;
98  }
99  return true;
100}
101
102#ifndef UNDER_CE
103/* in win2000/win98 : monitor functions are supported.
104   We need dynamic linking, if we want nt4/win95 support in program.
105   Even if we compile the code with low (WINVER) value, we still
106   want to use monitor functions. So we declare missing functions here */
107// #if (WINVER < 0x0500)
108#ifndef MONITOR_DEFAULTTOPRIMARY
109extern "C" {
110DECLARE_HANDLE(HMONITOR);
111#define MONITOR_DEFAULTTOPRIMARY    0x00000001
112typedef struct tagMONITORINFO
113{
114    DWORD   cbSize;
115    RECT    rcMonitor;
116    RECT    rcWork;
117    DWORD   dwFlags;
118} MONITORINFO, *LPMONITORINFO;
119WINUSERAPI HMONITOR WINAPI MonitorFromWindow(HWND hwnd, DWORD dwFlags);
120WINUSERAPI BOOL WINAPI GetMonitorInfoA(HMONITOR hMonitor, LPMONITORINFO lpmi);
121}
122#endif
123#endif
124
125static bool GetWorkAreaRect(RECT *rect, HWND hwnd)
126{
127  if (hwnd)
128  {
129    #ifndef UNDER_CE
130    /* MonitorFromWindow() is supported in Win2000+
131       MonitorFromWindow() : retrieves a handle to the display monitor that has the
132         largest area of intersection with the bounding rectangle of a specified window.
133       dwFlags: Determines the function's return value if the window does not intersect any display monitor.
134         MONITOR_DEFAULTTONEAREST : Returns display that is nearest to the window.
135         MONITOR_DEFAULTTONULL    : Returns NULL.
136         MONITOR_DEFAULTTOPRIMARY : Returns the primary display monitor.
137    */
138    const HMONITOR hmon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
139    if (hmon)
140    {
141      MONITORINFO mi;
142      memset(&mi, 0, sizeof(mi));
143      mi.cbSize = sizeof(mi);
144      if (GetMonitorInfoA(hmon, &mi))
145      {
146        *rect = mi.rcWork;
147        return true;
148      }
149    }
150    #endif
151  }
152
153  /* Retrieves the size of the work area on the primary display monitor.
154     The work area is the portion of the screen not obscured
155     by the system taskbar or by application desktop toolbars.
156     Any DPI virtualization mode of the caller has no effect on this output. */
157
158  return BOOLToBool(::SystemParametersInfo(SPI_GETWORKAREA, 0, rect, 0));
159}
160
161
162bool IsDialogSizeOK(int xSize, int ySize, HWND hwnd)
163{
164  // it returns for system font. Real font uses another values
165  const LONG v = GetDialogBaseUnits();
166  const int x = LOWORD(v);
167  const int y = HIWORD(v);
168
169  RECT rect;
170  GetWorkAreaRect(&rect, hwnd);
171  const int wx = RECT_SIZE_X(rect);
172  const int wy = RECT_SIZE_Y(rect);
173  return
174    xSize / 4 * x <= wx &&
175    ySize / 8 * y <= wy;
176}
177
178bool CDialog::GetMargins(int margin, int &x, int &y)
179{
180  x = margin;
181  y = margin;
182  RECT rect;
183  rect.left = 0;
184  rect.top = 0;
185  rect.right = margin;
186  rect.bottom = margin;
187  if (!MapRect(&rect))
188    return false;
189  x = rect.right - rect.left;
190  y = rect.bottom - rect.top;
191  return true;
192}
193
194int CDialog::Units_To_Pixels_X(int units)
195{
196  RECT rect;
197  rect.left = 0;
198  rect.top = 0;
199  rect.right = units;
200  rect.bottom = units;
201  if (!MapRect(&rect))
202    return units * 3 / 2;
203  return rect.right - rect.left;
204}
205
206bool CDialog::GetItemSizes(unsigned id, int &x, int &y)
207{
208  RECT rect;
209  if (!::GetWindowRect(GetItem(id), &rect))
210    return false;
211  x = RECT_SIZE_X(rect);
212  y = RECT_SIZE_Y(rect);
213  return true;
214}
215
216void CDialog::GetClientRectOfItem(unsigned id, RECT &rect)
217{
218  ::GetWindowRect(GetItem(id), &rect);
219  ScreenToClient(&rect);
220}
221
222bool CDialog::MoveItem(unsigned id, int x, int y, int width, int height, bool repaint)
223{
224  return BOOLToBool(::MoveWindow(GetItem(id), x, y, width, height, BoolToBOOL(repaint)));
225}
226
227
228/*
229typedef BOOL (WINAPI * Func_DwmGetWindowAttribute)(
230    HWND hwnd, DWORD dwAttribute, PVOID pvAttribute, DWORD cbAttribute);
231
232static bool GetWindowsRect_DWM(HWND hwnd, RECT *rect)
233{
234  // dll load and free is too slow : 300 calls in second.
235  NDLL::CLibrary dll;
236  if (!dll.Load(FTEXT("dwmapi.dll")))
237    return false;
238  Func_DwmGetWindowAttribute f = (Func_DwmGetWindowAttribute)dll.GetProc("DwmGetWindowAttribute" );
239  if (f)
240  {
241    #define MY__DWMWA_EXTENDED_FRAME_BOUNDS 9
242    // 30000 per second
243    RECT r;
244    if (f(hwnd, MY__DWMWA_EXTENDED_FRAME_BOUNDS, &r, sizeof(RECT)) == S_OK)
245    {
246      *rect = r;
247      return true;
248    }
249  }
250  return false;
251}
252*/
253
254
255static bool IsRect_Small_Inside_Big(const RECT &sm, const RECT &big)
256{
257  return sm.left   >= big.left
258      && sm.right  <= big.right
259      && sm.top    >= big.top
260      && sm.bottom <= big.bottom;
261}
262
263
264static bool AreRectsOverlapped(const RECT &r1, const RECT &r2)
265{
266  return r1.left   < r2.right
267      && r1.right  > r2.left
268      && r1.top    < r2.bottom
269      && r1.bottom > r2.top;
270}
271
272
273static bool AreRectsEqual(const RECT &r1, const RECT &r2)
274{
275  return r1.left   == r2.left
276      && r1.right  == r2.right
277      && r1.top    == r2.top
278      && r1.bottom == r2.bottom;
279}
280
281
282void CDialog::NormalizeSize(bool fullNormalize)
283{
284  RECT workRect;
285  if (!GetWorkAreaRect(&workRect, *this))
286    return;
287  RECT rect;
288  if (!GetWindowRect(&rect))
289    return;
290  int xs = RECT_SIZE_X(rect);
291  int ys = RECT_SIZE_Y(rect);
292
293  // we don't want to change size using workRect, if window is outside of WorkArea
294  if (!AreRectsOverlapped(rect, workRect))
295    return;
296
297  /* here rect and workRect are overlapped, but it can be false
298     overlapping of small shadow when window in another display. */
299
300  const int xsW = RECT_SIZE_X(workRect);
301  const int ysW = RECT_SIZE_Y(workRect);
302  if (xs <= xsW && ys <= ysW)
303    return; // size of window is OK
304  if (fullNormalize)
305  {
306    Show(SW_SHOWMAXIMIZED);
307    return;
308  }
309  int x = workRect.left;
310  int y = workRect.top;
311  if (xs < xsW)  x += (xsW - xs) / 2;  else xs = xsW;
312  if (ys < ysW)  y += (ysW - ys) / 2;  else ys = ysW;
313  Move(x, y, xs, ys, true);
314}
315
316
317void CDialog::NormalizePosition()
318{
319  RECT workRect;
320  if (!GetWorkAreaRect(&workRect, *this))
321    return;
322
323  RECT rect2 = workRect;
324  bool useWorkArea = true;
325  const HWND parentHWND = GetParent();
326
327  if (parentHWND)
328  {
329    RECT workRectParent;
330    if (!GetWorkAreaRect(&workRectParent, parentHWND))
331      return;
332
333    // if windows are in different monitors, we use only workArea of current window
334
335    if (AreRectsEqual(workRectParent, workRect))
336    {
337      // RECT rect3; if (GetWindowsRect_DWM(parentHWND, &rect3)) {}
338      CWindow wnd(parentHWND);
339      if (wnd.GetWindowRect(&rect2))
340      {
341        // it's same monitor. So we try to use parentHWND rect.
342        /* we don't want to change position, if parent window is not inside work area.
343           In Win10 : parent window rect is 8 pixels larger for each corner than window size for shadow.
344           In maximize mode : window is outside of workRect.
345           if parent window is inside workRect, we will use parent window instead of workRect */
346        if (IsRect_Small_Inside_Big(rect2, workRect))
347          useWorkArea = false;
348      }
349    }
350  }
351
352  RECT rect;
353  if (!GetWindowRect(&rect))
354    return;
355
356  if (useWorkArea)
357  {
358    // we don't want to move window, if it's already inside.
359    if (IsRect_Small_Inside_Big(rect, workRect))
360      return;
361    // we don't want to move window, if it's outside of workArea
362    if (!AreRectsOverlapped(rect, workRect))
363      return;
364    rect2 = workRect;
365  }
366
367  {
368    const int xs = RECT_SIZE_X(rect);
369    const int ys = RECT_SIZE_Y(rect);
370    const int xs2 = RECT_SIZE_X(rect2);
371    const int ys2 = RECT_SIZE_Y(rect2);
372    // we don't want to change position if parent is smaller.
373    if (xs <= xs2 && ys <= ys2)
374    {
375      const int x = rect2.left + (xs2 - xs) / 2;
376      const int y = rect2.top  + (ys2 - ys) / 2;
377
378      if (x != rect.left || y != rect.top)
379        Move(x, y, xs, ys, true);
380      // SetWindowPos(*this, HWND_TOP, x, y, 0, 0, SWP_NOSIZE);
381      return;
382    }
383  }
384}
385
386
387
388bool CModelessDialog::Create(LPCTSTR templateName, HWND parentWindow)
389{
390  const HWND aHWND = CreateDialogParam(g_hInstance, templateName, parentWindow, DialogProcedure, (LPARAM)this);
391  if (!aHWND)
392    return false;
393  Attach(aHWND);
394  return true;
395}
396
397INT_PTR CModalDialog::Create(LPCTSTR templateName, HWND parentWindow)
398{
399  return DialogBoxParam(g_hInstance, templateName, parentWindow, DialogProcedure, (LPARAM)this);
400}
401
402#ifndef _UNICODE
403
404bool CModelessDialog::Create(LPCWSTR templateName, HWND parentWindow)
405{
406  HWND aHWND;
407  if (g_IsNT)
408    aHWND = CreateDialogParamW(g_hInstance, templateName, parentWindow, DialogProcedure, (LPARAM)this);
409  else
410  {
411    AString name;
412    LPCSTR templateNameA;
413    if (IS_INTRESOURCE(templateName))
414      templateNameA = (LPCSTR)templateName;
415    else
416    {
417      name = GetSystemString(templateName);
418      templateNameA = name;
419    }
420    aHWND = CreateDialogParamA(g_hInstance, templateNameA, parentWindow, DialogProcedure, (LPARAM)this);
421  }
422  if (aHWND == 0)
423    return false;
424  Attach(aHWND);
425  return true;
426}
427
428INT_PTR CModalDialog::Create(LPCWSTR templateName, HWND parentWindow)
429{
430  if (g_IsNT)
431    return DialogBoxParamW(g_hInstance, templateName, parentWindow, DialogProcedure, (LPARAM)this);
432  AString name;
433  LPCSTR templateNameA;
434  if (IS_INTRESOURCE(templateName))
435    templateNameA = (LPCSTR)templateName;
436  else
437  {
438    name = GetSystemString(templateName);
439    templateNameA = name;
440  }
441  return DialogBoxParamA(g_hInstance, templateNameA, parentWindow, DialogProcedure, (LPARAM)this);
442}
443#endif
444
445}}
446