1 // This file is part of Notepad++ project
2 // Copyright (C)2020 Don HO <don.h@free.fr>
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either
7 // version 2 of the License, or (at your option) any later version.
8 //
9 // Note that the GPL places important restrictions on "derived works", yet
10 // it does not provide a detailed definition of that term.  To avoid
11 // misunderstandings, we consider an application to constitute a
12 // "derivative work" for the purpose of this license if it does any of the
13 // following:
14 // 1. Integrates source code from Notepad++.
15 // 2. Integrates/includes/aggregates Notepad++ into a proprietary executable
16 //    installer, such as those produced by InstallShield.
17 // 3. Links to a library or executes a program that does any of the above.
18 //
19 // This program is distributed in the hope that it will be useful,
20 // but WITHOUT ANY WARRANTY; without even the implied warranty of
21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 // GNU General Public License for more details.
23 //
24 // You should have received a copy of the GNU General Public License
25 // along with this program; if not, write to the Free Software
26 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 /**
28  *
29  *
30  * License: GPL-2.0 or later
31  */
32 module npp_api.PowerEditor.WinControls.StaticDialog.StaticDialog;
33 
34 
35 version (Windows):
36 version (Not_betterC):
37 
38 //pragma(lib, "kernel32");
39 //pragma(lib, "user32");
40 
41 private static import core.stdc.config;
42 private static import core.stdc.string;
43 private static import core.sys.windows.basetsd;
44 private static import core.sys.windows.winbase;
45 private static import core.sys.windows.windef;
46 private static import core.sys.windows.winuser;
47 private static import npp_api.PowerEditor.MISC.PluginsManager.Notepad_plus_msgs;
48 private static import npp_api.PowerEditor.WinControls.Window;
49 private static import npp_api.pluginfunc.npp_msgs;
50 private static import std.format;
51 private static import std.utf;
52 
53 enum PosAlign
54 {
55 	left,
56 	right,
57 	top,
58 	bottom,
59 }
60 
61 /**
62  * The structure has more fields but are variable length
63  */
64 extern (C)
65 struct DLGTEMPLATEEX
66 {
67 	core.sys.windows.windef.WORD dlgVer;
68 	core.sys.windows.windef.WORD signature;
69 	core.sys.windows.windef.DWORD helpID;
70 	core.sys.windows.windef.DWORD exStyle;
71 	core.sys.windows.windef.DWORD style;
72 	core.sys.windows.windef.WORD cDlgItems;
73 	short x;
74 	short y;
75 	short cx;
76 	short cy;
77 }
78 
79 abstract class StaticDialog : npp_api.PowerEditor.WinControls.Window.Window
80 {
81 public:
82 	nothrow @nogc
83 	~this()
84 
85 		do
86 		{
87 			if (this.isCreated()) {
88 				// Prevent run_dlgProc from doing anything, since its virtual
89 				core.sys.windows.winuser.SetWindowLongPtrW(this._hSelf, core.sys.windows.winuser.GWLP_USERDATA, 0);
90 
91 				this.destroy();
92 			}
93 		}
94 
95 	void create(void* dialog_p, int dialogID, bool isRTL = false, bool msgDestParent = true)
96 
97 		in
98 		{
99 			static assert(core.sys.windows.windef.LPARAM.sizeof >= dialog_p.sizeof);
100 			assert(dialog_p != null);
101 		}
102 
103 		do
104 		{
105 			if (isRTL) {
106 				core.sys.windows.winuser.DLGTEMPLATE* pMyDlgTemplate = core.sys.windows.windef.NULL;
107 				core.sys.windows.windef.HGLOBAL hMyDlgTemplate = this.makeRTLResource(dialogID, &pMyDlgTemplate);
108 				this._hSelf = core.sys.windows.winuser.CreateDialogIndirectParamW(this._hInst, cast(core.sys.windows.winuser.LPCDLGTEMPLATEW)(pMyDlgTemplate), this._hParent, &this.dlgProc, cast(core.sys.windows.windef.LPARAM)(dialog_p));
109 				core.sys.windows.winbase.GlobalFree(hMyDlgTemplate);
110 			} else {
111 				this._hSelf = core.sys.windows.winuser.CreateDialogParamW(this._hInst, core.sys.windows.winuser.MAKEINTRESOURCEW(dialogID), this._hParent, &this.dlgProc, cast(core.sys.windows.windef.LPARAM)(dialog_p));
112 			}
113 
114 			if (this._hSelf == core.sys.windows.windef.NULL) {
115 				throw new Exception(std.utf.toUTF8(std.format.format!("CreateDialogParamW() return NULL.\rGetLastError(): %d")(core.sys.windows.winbase.GetLastError())));
116 			}
117 
118 			// if the destination of message NPPM_MODELESSDIALOG is not its parent, then it's the grand-parent
119 			core.sys.windows.winuser.SendMessageW(((msgDestParent) ? (this._hParent) : (core.sys.windows.winuser.GetParent(this._hParent))), npp_api.PowerEditor.MISC.PluginsManager.Notepad_plus_msgs.NPPM_MODELESSDIALOG, npp_api.PowerEditor.MISC.PluginsManager.Notepad_plus_msgs.MODELESSDIALOGADD, cast(core.sys.windows.windef.WPARAM)(this._hSelf));
120 		}
121 
122 	pure nothrow @safe @nogc
123 	bool isCreated() const
124 
125 		do
126 		{
127 			return (this._hSelf != core.sys.windows.windef.NULL);
128 		}
129 
130 	nothrow @nogc
131 	void goToCenter()
132 
133 		do
134 		{
135 			core.sys.windows.windef.RECT rc;
136 			core.sys.windows.winuser.GetClientRect(this._hParent, &rc);
137 			core.sys.windows.windef.POINT center;
138 			center.x = rc.left + (rc.right - rc.left) / 2;
139 			center.y = rc.top + (rc.bottom - rc.top) / 2;
140 			core.sys.windows.winuser.ClientToScreen(this._hParent, &center);
141 
142 			int x = center.x - (this._rc.right - this._rc.left) / 2;
143 			int y = center.y - (this._rc.bottom - this._rc.top) / 2;
144 
145 			core.sys.windows.winuser.SetWindowPos(this._hSelf, core.sys.windows.winuser.HWND_TOP, x, y, this._rc.right - this._rc.left, this._rc.bottom - this._rc.top, core.sys.windows.winuser.SWP_SHOWWINDOW);
146 		}
147 
148 	nothrow @nogc
149 	override void destroy()
150 
151 		do
152 		{
153 			npp_api.pluginfunc.npp_msgs.send_NPPM_MODELESSDIALOG(this._hParent, npp_api.PowerEditor.MISC.PluginsManager.Notepad_plus_msgs.MODELESSDIALOGREMOVE, this._hSelf);
154 			core.sys.windows.winuser.DestroyWindow(this._hSelf);
155 		}
156 
157 	nothrow @nogc
158 	override void display(bool toShow, bool enhancedPositioningCheckWhenShowing = false)
159 
160 		do
161 		{
162 			if (toShow) {
163 				if (enhancedPositioningCheckWhenShowing) {
164 					core.sys.windows.windef.RECT testPositionRc;
165 					super.getWindowRect(testPositionRc);
166 
167 					core.sys.windows.windef.RECT candidateRc = this.getViewablePositionRect(testPositionRc);
168 
169 					if ((testPositionRc.left != candidateRc.left) || (testPositionRc.top != candidateRc.top)) {
170 						core.sys.windows.winuser.MoveWindow(_hSelf, candidateRc.left, candidateRc.top, candidateRc.right - candidateRc.left, candidateRc.bottom - candidateRc.top, core.sys.windows.windef.TRUE);
171 					}
172 				} else {
173 					// If the user has switched from a dual monitor to a single monitor since we last
174 					// displayed the dialog, then ensure that it's still visible on the single monitor.
175 					core.sys.windows.windef.RECT workAreaRect;
176 					core.sys.windows.windef.RECT rc;
177 					core.sys.windows.winuser.SystemParametersInfoW(core.sys.windows.winuser.SPI_GETWORKAREA, 0, &workAreaRect, 0);
178 					core.sys.windows.winuser.GetWindowRect(_hSelf, &rc);
179 					int newLeft = rc.left;
180 					int newTop = rc.top;
181 					int margin = core.sys.windows.winuser.GetSystemMetrics(core.sys.windows.winuser.SM_CYSMCAPTION);
182 
183 					if (newLeft > core.sys.windows.winuser.GetSystemMetrics(core.sys.windows.winuser.SM_CXVIRTUALSCREEN) - margin) {
184 						newLeft -= rc.right - workAreaRect.right;
185 					}
186 
187 					if ((newLeft + (rc.right - rc.left)) < (core.sys.windows.winuser.GetSystemMetrics(core.sys.windows.winuser.SM_XVIRTUALSCREEN) + margin)) {
188 						newLeft = workAreaRect.left;
189 					}
190 
191 					if (newTop > (core.sys.windows.winuser.GetSystemMetrics(core.sys.windows.winuser.SM_CYVIRTUALSCREEN) - margin)) {
192 						newTop -= rc.bottom - workAreaRect.bottom;
193 					}
194 
195 					if ((newTop + (rc.bottom - rc.top)) < (core.sys.windows.winuser.GetSystemMetrics(core.sys.windows.winuser.SM_YVIRTUALSCREEN) + margin)) {
196 						newTop = workAreaRect.top;
197 					}
198 
199 					if ((newLeft != rc.left) || (newTop != rc.top)) {
200 						// then the virtual screen size has shrunk
201 						// Remember that core.sys.windows.winuser.MoveWindow wants width/height.
202 						core.sys.windows.winuser.MoveWindow(_hSelf, newLeft, newTop, rc.right - rc.left, rc.bottom - rc.top, core.sys.windows.windef.TRUE);
203 					}
204 				}
205 			}
206 
207 			super.display(toShow);
208 		}
209 
210 	nothrow @nogc
211 	core.sys.windows.windef.RECT getViewablePositionRect(core.sys.windows.windef.RECT testPositionRc) const
212 
213 		do
214 		{
215 			core.sys.windows.windef.HMONITOR hMon = core.sys.windows.winuser.MonitorFromRect(&testPositionRc, core.sys.windows.winuser.MONITOR_DEFAULTTONULL);
216 
217 			core.sys.windows.winuser.MONITORINFO mi =
218 			{
219 				cbSize: core.sys.windows.winuser.MONITORINFO.sizeof,
220 			};
221 
222 			bool rectPosViewableWithoutChange = false;
223 
224 			if (hMon != core.sys.windows.windef.NULL) {
225 				// rect would be at least partially visible on a monitor
226 
227 				core.sys.windows.winuser.GetMonitorInfoW(hMon, &mi);
228 
229 				int margin = core.sys.windows.winuser.GetSystemMetrics(core.sys.windows.winuser.SM_CYBORDER) + core.sys.windows.winuser.GetSystemMetrics(core.sys.windows.winuser.SM_CYSIZEFRAME) + core.sys.windows.winuser.GetSystemMetrics(core.sys.windows.winuser.SM_CYCAPTION);
230 
231 				// require that the title bar of the window be in a viewable place so the user can see it to grab it with the mouse
232 				if ((testPositionRc.top >= mi.rcWork.top) && ((testPositionRc.top + margin) <= mi.rcWork.bottom) &&
233 						// require that some reasonable amount of width of the title bar be in the viewable area:
234 						((testPositionRc.right - (margin * 2)) > mi.rcWork.left) && ((testPositionRc.left + (margin * 2)) < mi.rcWork.right)) {
235 					rectPosViewableWithoutChange = true;
236 				}
237 			} else {
238 				// rect would not have been visible on a monitor; get info about the nearest monitor to it
239 
240 				hMon = core.sys.windows.winuser.MonitorFromRect(&testPositionRc, core.sys.windows.winuser.MONITOR_DEFAULTTONEAREST);
241 
242 				core.sys.windows.winuser.GetMonitorInfoW(hMon, &mi);
243 			}
244 
245 			core.sys.windows.windef.RECT returnRc = testPositionRc;
246 
247 			if (!rectPosViewableWithoutChange) {
248 				// reposition rect so that it would be viewable on current/nearest monitor, centering if reasonable
249 
250 				core.sys.windows.windef.LONG testRectWidth = testPositionRc.right - testPositionRc.left;
251 				core.sys.windows.windef.LONG testRectHeight = testPositionRc.bottom - testPositionRc.top;
252 				core.sys.windows.windef.LONG monWidth = mi.rcWork.right - mi.rcWork.left;
253 				core.sys.windows.windef.LONG monHeight = mi.rcWork.bottom - mi.rcWork.top;
254 
255 				returnRc.left = mi.rcWork.left;
256 
257 				if (testRectWidth < monWidth) {
258 					returnRc.left += (monWidth - testRectWidth) / 2;
259 				}
260 
261 				returnRc.right = returnRc.left + testRectWidth;
262 
263 				returnRc.top = mi.rcWork.top;
264 
265 				if (testRectHeight < monHeight) {
266 					returnRc.top += (monHeight - testRectHeight) / 2;
267 				}
268 
269 				returnRc.bottom = returnRc.top + testRectHeight;
270 			}
271 
272 			return returnRc;
273 		}
274 
275 	nothrow @nogc
276 	core.sys.windows.windef.POINT getTopPoint(core.sys.windows.windef.HWND hwnd, bool isLeft)
277 
278 		do
279 		{
280 			core.sys.windows.windef.RECT rc;
281 			core.sys.windows.winuser.GetWindowRect(hwnd, &rc);
282 
283 			core.sys.windows.windef.POINT p;
284 
285 			if (isLeft) {
286 				p.x = rc.left;
287 			} else {
288 				p.x = rc.right;
289 			}
290 
291 			p.y = rc.top;
292 			core.sys.windows.winuser.ScreenToClient(this._hSelf, &p);
293 
294 			return p;
295 		}
296 
297 	nothrow @nogc
298 	void setChecked(int checkControlID, bool checkOrNot = true)
299 
300 		do
301 		{
302 			core.sys.windows.winuser.SendDlgItemMessageW(this._hSelf, checkControlID, core.sys.windows.winuser.BM_SETCHECK, (checkOrNot) ? (core.sys.windows.winuser.BST_CHECKED) : (core.sys.windows.winuser.BST_UNCHECKED), 0);
303 		}
304 
305 protected:
306 	core.sys.windows.windef.RECT _rc;
307 
308 	extern (Windows)
309 	nothrow @nogc
310 	static core.sys.windows.basetsd.INT_PTR dlgProc(core.sys.windows.windef.HWND hwnd, core.sys.windows.windef.UINT message, core.sys.windows.windef.WPARAM wParam, core.sys.windows.windef.LPARAM lParam)
311 
312 		in
313 		{
314 		}
315 
316 		do
317 		{
318 			switch (message) {
319 				case core.sys.windows.winuser.WM_INITDIALOG:
320 					StaticDialog* pStaticDlg = cast(StaticDialog*)(lParam);
321 
322 					(*pStaticDlg)._hSelf = hwnd;
323 					core.sys.windows.winuser.SetWindowLongPtrW(hwnd, core.sys.windows.winuser.GWLP_USERDATA, cast(core.sys.windows.basetsd.LONG_PTR)(lParam));
324 					core.sys.windows.winuser.GetWindowRect(hwnd, &((*pStaticDlg)._rc));
325 					(*pStaticDlg).run_dlgProc(message, wParam, lParam);
326 
327 					return core.sys.windows.windef.TRUE;
328 
329 				default:
330 					StaticDialog* pStaticDlg = cast(StaticDialog*)(core.sys.windows.winuser.GetWindowLongPtrW(hwnd, core.sys.windows.winuser.GWLP_USERDATA));
331 
332 					if (pStaticDlg == core.sys.windows.windef.NULL) {
333 						return core.sys.windows.windef.FALSE;
334 					}
335 
336 					return (*pStaticDlg).run_dlgProc(message, wParam, lParam);
337 			}
338 		}
339 
340 	extern (Windows)
341 	nothrow @nogc
342 	core.sys.windows.basetsd.INT_PTR run_dlgProc(core.sys.windows.windef.UINT message, core.sys.windows.windef.WPARAM wParam, core.sys.windows.windef.LPARAM lParam);
343 
344 	nothrow @nogc
345 	void alignWith(core.sys.windows.windef.HWND handle, core.sys.windows.windef.HWND handle2Align, .PosAlign pos, ref core.sys.windows.windef.POINT point)
346 
347 		in
348 		{
349 		}
350 
351 		do
352 		{
353 			core.sys.windows.windef.RECT rc, rc2;
354 			core.sys.windows.winuser.GetWindowRect(handle, &rc);
355 
356 			point.x = rc.left;
357 			point.y = rc.top;
358 
359 			switch (pos) {
360 				case .PosAlign.left:
361 					core.sys.windows.winuser.GetWindowRect(handle2Align, &rc2);
362 					point.x -= rc2.right - rc2.left;
363 
364 					break;
365 
366 				case .PosAlign.right:
367 					core.sys.windows.winuser.GetWindowRect(handle, &rc2);
368 					point.x += rc2.right - rc2.left;
369 
370 					break;
371 
372 				case .PosAlign.top:
373 					core.sys.windows.winuser.GetWindowRect(handle2Align, &rc2);
374 					point.y -= rc2.bottom - rc2.top;
375 
376 					break;
377 
378 				case .PosAlign.bottom:
379 					core.sys.windows.winuser.GetWindowRect(handle, &rc2);
380 					point.y += rc2.bottom - rc2.top;
381 
382 					break;
383 
384 				default:
385 					break;
386 			}
387 
388 			core.sys.windows.winuser.ScreenToClient(this._hSelf, &point);
389 		}
390 
391 	nothrow @nogc
392 	core.sys.windows.windef.HGLOBAL makeRTLResource(int dialogID, core.sys.windows.winuser.DLGTEMPLATE** ppMyDlgTemplate)
393 
394 		in
395 		{
396 			assert(ppMyDlgTemplate != null);
397 		}
398 
399 		do
400 		{
401 			// Get Dlg Template resource
402 			core.sys.windows.windef.HRSRC hDialogRC = core.sys.windows.winbase.FindResourceW(this._hInst, core.sys.windows.winuser.MAKEINTRESOURCEW(dialogID), core.sys.windows.winuser.RT_DIALOG);
403 
404 			if (hDialogRC == core.sys.windows.windef.NULL) {
405 				return core.sys.windows.windef.NULL;
406 			}
407 
408 			core.sys.windows.windef.HGLOBAL hDlgTemplate = core.sys.windows.winbase.LoadResource(this._hInst, hDialogRC);
409 
410 			if (hDlgTemplate == core.sys.windows.windef.NULL) {
411 				return core.sys.windows.windef.NULL;
412 			}
413 
414 			core.sys.windows.winuser.DLGTEMPLATE* pDlgTemplate = cast(core.sys.windows.winuser.DLGTEMPLATE*)(core.sys.windows.winbase.LockResource(hDlgTemplate));
415 
416 			if (pDlgTemplate == core.sys.windows.windef.NULL) {
417 				return core.sys.windows.windef.NULL;
418 			}
419 
420 			// Duplicate Dlg Template resource
421 			core.stdc.config.c_ulong sizeDlg = core.sys.windows.winbase.SizeofResource(this._hInst, hDialogRC);
422 			core.sys.windows.windef.HGLOBAL hMyDlgTemplate = core.sys.windows.winbase.GlobalAlloc(core.sys.windows.winbase.GPTR, sizeDlg);
423 			*ppMyDlgTemplate = cast(core.sys.windows.winuser.DLGTEMPLATE*)(core.sys.windows.winbase.GlobalLock(hMyDlgTemplate));
424 
425 			core.stdc..string.memcpy(*ppMyDlgTemplate, pDlgTemplate, sizeDlg);
426 
427 			.DLGTEMPLATEEX* pMyDlgTemplateEx = cast(.DLGTEMPLATEEX*)(*ppMyDlgTemplate);
428 
429 			if ((*pMyDlgTemplateEx).signature == 0xFFFF) {
430 				(*pMyDlgTemplateEx).exStyle |= core.sys.windows.winuser.WS_EX_LAYOUTRTL;
431 			} else {
432 				(*ppMyDlgTemplate).dwExtendedStyle |= core.sys.windows.winuser.WS_EX_LAYOUTRTL;
433 			}
434 
435 			return hMyDlgTemplate;
436 		}
437 }