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.sys.windows.basetsd;
42 private static import core.sys.windows.winbase;
43 private static import core.sys.windows.windef;
44 private static import core.sys.windows.winuser;
45 private static import core.stdc.config;
46 private static import core.stdc.string;
47 private static import std.utf;
48 private static import std.format;
49 private static import npp_api.powereditor.misc.pluginsmanager.notepad_plus_msgs;
50 private static import npp_api.powereditor.wincontrols.window;
51 private static import npp_api.pluginfunc.npp_msgs;
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 	static import core.sys.windows.windef;
68 
69 	core.sys.windows.windef.WORD dlgVer;
70 	core.sys.windows.windef.WORD signature;
71 	core.sys.windows.windef.DWORD helpID;
72 	core.sys.windows.windef.DWORD exStyle;
73 	core.sys.windows.windef.DWORD style;
74 	core.sys.windows.windef.WORD cDlgItems;
75 	short x;
76 	short y;
77 	short cx;
78 	short cy;
79 }
80 
81 abstract class StaticDialog : npp_api.powereditor.wincontrols.window.Window
82 {
83 	static import core.sys.windows.basetsd;
84 	static import core.sys.windows.winbase;
85 	static import core.sys.windows.windef;
86 	static import core.sys.windows.winuser;
87 	static import core.stdc.config;
88 	static import core.stdc.string;
89 	static import std.utf;
90 	static import std.format;
91 	static import npp_api.powereditor.misc.pluginsmanager.notepad_plus_msgs;
92 	static import npp_api.pluginfunc.npp_msgs;
93 
94 public:
95 	nothrow @nogc
96 	~this()
97 
98 		do
99 		{
100 			if (this.isCreated()) {
101 				// Prevent run_dlgProc from doing anything, since its virtual
102 				core.sys.windows.winuser.SetWindowLongPtrW(this._hSelf, core.sys.windows.winuser.GWLP_USERDATA, 0);
103 
104 				this.destroy();
105 			}
106 		}
107 
108 	void create(void* dialog_p, int dialogID, bool isRTL = false, bool msgDestParent = true)
109 
110 		in
111 		{
112 			static assert(core.sys.windows.windef.LPARAM.sizeof >= dialog_p.sizeof);
113 			assert(dialog_p != null);
114 		}
115 
116 		do
117 		{
118 			if (isRTL) {
119 				core.sys.windows.winuser.DLGTEMPLATE* pMyDlgTemplate = core.sys.windows.windef.NULL;
120 				core.sys.windows.windef.HGLOBAL hMyDlgTemplate = this.makeRTLResource(dialogID, &pMyDlgTemplate);
121 				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));
122 				core.sys.windows.winbase.GlobalFree(hMyDlgTemplate);
123 			} else {
124 				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));
125 			}
126 
127 			if (this._hSelf == core.sys.windows.windef.NULL) {
128 				throw new Exception(std.utf.toUTF8(std.format.format!("CreateDialogParamW() return NULL.\rGetLastError(): %d")(core.sys.windows.winbase.GetLastError())));
129 			}
130 
131 			// if the destination of message NPPM_MODELESSDIALOG is not its parent, then it's the grand-parent
132 			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));
133 		}
134 
135 	pure nothrow @safe @nogc
136 	bool isCreated() const
137 
138 		do
139 		{
140 			return (this._hSelf != core.sys.windows.windef.NULL);
141 		}
142 
143 	nothrow @nogc
144 	void goToCenter()
145 
146 		do
147 		{
148 			core.sys.windows.windef.RECT rc;
149 			core.sys.windows.winuser.GetClientRect(this._hParent, &rc);
150 			core.sys.windows.windef.POINT center;
151 			center.x = rc.left + (rc.right - rc.left) / 2;
152 			center.y = rc.top + (rc.bottom - rc.top) / 2;
153 			core.sys.windows.winuser.ClientToScreen(this._hParent, &center);
154 
155 			int x = center.x - (this._rc.right - this._rc.left) / 2;
156 			int y = center.y - (this._rc.bottom - this._rc.top) / 2;
157 
158 			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);
159 		}
160 
161 	nothrow @nogc
162 	override void destroy()
163 
164 		do
165 		{
166 			npp_api.pluginfunc.npp_msgs.send_NPPM_MODELESSDIALOG(this._hParent, npp_api.powereditor.misc.pluginsmanager.notepad_plus_msgs.MODELESSDIALOGREMOVE, this._hSelf);
167 			core.sys.windows.winuser.DestroyWindow(this._hSelf);
168 		}
169 
170 	nothrow @nogc
171 	override void display(bool toShow, bool enhancedPositioningCheckWhenShowing = false)
172 
173 		do
174 		{
175 			if (toShow) {
176 				if (enhancedPositioningCheckWhenShowing) {
177 					core.sys.windows.windef.RECT testPositionRc;
178 					super.getWindowRect(testPositionRc);
179 
180 					core.sys.windows.windef.RECT candidateRc = this.getViewablePositionRect(testPositionRc);
181 
182 					if ((testPositionRc.left != candidateRc.left) || (testPositionRc.top != candidateRc.top)) {
183 						core.sys.windows.winuser.MoveWindow(_hSelf, candidateRc.left, candidateRc.top, candidateRc.right - candidateRc.left, candidateRc.bottom - candidateRc.top, core.sys.windows.windef.TRUE);
184 					}
185 				} else {
186 					// If the user has switched from a dual monitor to a single monitor since we last
187 					// displayed the dialog, then ensure that it's still visible on the single monitor.
188 					core.sys.windows.windef.RECT workAreaRect;
189 					core.sys.windows.windef.RECT rc;
190 					core.sys.windows.winuser.SystemParametersInfoW(core.sys.windows.winuser.SPI_GETWORKAREA, 0, &workAreaRect, 0);
191 					core.sys.windows.winuser.GetWindowRect(_hSelf, &rc);
192 					int newLeft = rc.left;
193 					int newTop = rc.top;
194 					int margin = core.sys.windows.winuser.GetSystemMetrics(core.sys.windows.winuser.SM_CYSMCAPTION);
195 
196 					if (newLeft > core.sys.windows.winuser.GetSystemMetrics(core.sys.windows.winuser.SM_CXVIRTUALSCREEN) - margin) {
197 						newLeft -= rc.right - workAreaRect.right;
198 					}
199 
200 					if ((newLeft + (rc.right - rc.left)) < (core.sys.windows.winuser.GetSystemMetrics(core.sys.windows.winuser.SM_XVIRTUALSCREEN) + margin)) {
201 						newLeft = workAreaRect.left;
202 					}
203 
204 					if (newTop > (core.sys.windows.winuser.GetSystemMetrics(core.sys.windows.winuser.SM_CYVIRTUALSCREEN) - margin)) {
205 						newTop -= rc.bottom - workAreaRect.bottom;
206 					}
207 
208 					if ((newTop + (rc.bottom - rc.top)) < (core.sys.windows.winuser.GetSystemMetrics(core.sys.windows.winuser.SM_YVIRTUALSCREEN) + margin)) {
209 						newTop = workAreaRect.top;
210 					}
211 
212 					if ((newLeft != rc.left) || (newTop != rc.top)) {
213 						// then the virtual screen size has shrunk
214 						// Remember that core.sys.windows.winuser.MoveWindow wants width/height.
215 						core.sys.windows.winuser.MoveWindow(_hSelf, newLeft, newTop, rc.right - rc.left, rc.bottom - rc.top, core.sys.windows.windef.TRUE);
216 					}
217 				}
218 			}
219 
220 			super.display(toShow);
221 		}
222 
223 	nothrow @nogc
224 	core.sys.windows.windef.RECT getViewablePositionRect(core.sys.windows.windef.RECT testPositionRc) const
225 
226 		do
227 		{
228 			core.sys.windows.windef.HMONITOR hMon = core.sys.windows.winuser.MonitorFromRect(&testPositionRc, core.sys.windows.winuser.MONITOR_DEFAULTTONULL);
229 
230 			core.sys.windows.winuser.MONITORINFO mi =
231 			{
232 				cbSize: core.sys.windows.winuser.MONITORINFO.sizeof,
233 			};
234 
235 			bool rectPosViewableWithoutChange = false;
236 
237 			if (hMon != core.sys.windows.windef.NULL) {
238 				// rect would be at least partially visible on a monitor
239 
240 				core.sys.windows.winuser.GetMonitorInfoW(hMon, &mi);
241 
242 				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);
243 
244 				// 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
245 				if ((testPositionRc.top >= mi.rcWork.top) && ((testPositionRc.top + margin) <= mi.rcWork.bottom) &&
246 						// require that some reasonable amount of width of the title bar be in the viewable area:
247 						((testPositionRc.right - (margin * 2)) > mi.rcWork.left) && ((testPositionRc.left + (margin * 2)) < mi.rcWork.right)) {
248 					rectPosViewableWithoutChange = true;
249 				}
250 			} else {
251 				// rect would not have been visible on a monitor; get info about the nearest monitor to it
252 
253 				hMon = core.sys.windows.winuser.MonitorFromRect(&testPositionRc, core.sys.windows.winuser.MONITOR_DEFAULTTONEAREST);
254 
255 				core.sys.windows.winuser.GetMonitorInfoW(hMon, &mi);
256 			}
257 
258 			core.sys.windows.windef.RECT returnRc = testPositionRc;
259 
260 			if (!rectPosViewableWithoutChange) {
261 				// reposition rect so that it would be viewable on current/nearest monitor, centering if reasonable
262 
263 				core.sys.windows.windef.LONG testRectWidth = testPositionRc.right - testPositionRc.left;
264 				core.sys.windows.windef.LONG testRectHeight = testPositionRc.bottom - testPositionRc.top;
265 				core.sys.windows.windef.LONG monWidth = mi.rcWork.right - mi.rcWork.left;
266 				core.sys.windows.windef.LONG monHeight = mi.rcWork.bottom - mi.rcWork.top;
267 
268 				returnRc.left = mi.rcWork.left;
269 
270 				if (testRectWidth < monWidth) {
271 					returnRc.left += (monWidth - testRectWidth) / 2;
272 				}
273 
274 				returnRc.right = returnRc.left + testRectWidth;
275 
276 				returnRc.top = mi.rcWork.top;
277 
278 				if (testRectHeight < monHeight) {
279 					returnRc.top += (monHeight - testRectHeight) / 2;
280 				}
281 
282 				returnRc.bottom = returnRc.top + testRectHeight;
283 			}
284 
285 			return returnRc;
286 		}
287 
288 	nothrow @nogc
289 	core.sys.windows.windef.POINT getTopPoint(core.sys.windows.windef.HWND hwnd, bool isLeft)
290 
291 		do
292 		{
293 			core.sys.windows.windef.RECT rc;
294 			core.sys.windows.winuser.GetWindowRect(hwnd, &rc);
295 
296 			core.sys.windows.windef.POINT p;
297 
298 			if (isLeft) {
299 				p.x = rc.left;
300 			} else {
301 				p.x = rc.right;
302 			}
303 
304 			p.y = rc.top;
305 			core.sys.windows.winuser.ScreenToClient(this._hSelf, &p);
306 
307 			return p;
308 		}
309 
310 protected:
311 	core.sys.windows.windef.RECT _rc;
312 
313 	extern (Windows)
314 	nothrow @nogc
315 	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)
316 
317 		in
318 		{
319 		}
320 
321 		do
322 		{
323 			switch (message) {
324 				case core.sys.windows.winuser.WM_INITDIALOG:
325 					StaticDialog* pStaticDlg = cast(StaticDialog*)(lParam);
326 
327 					(*pStaticDlg)._hSelf = hwnd;
328 					core.sys.windows.winuser.SetWindowLongPtrW(hwnd, core.sys.windows.winuser.GWLP_USERDATA, cast(core.sys.windows.basetsd.LONG_PTR)(lParam));
329 					core.sys.windows.winuser.GetWindowRect(hwnd, &((*pStaticDlg)._rc));
330 					(*pStaticDlg).run_dlgProc(message, wParam, lParam);
331 
332 					return core.sys.windows.windef.TRUE;
333 
334 				default:
335 					StaticDialog* pStaticDlg = cast(StaticDialog*)(core.sys.windows.winuser.GetWindowLongPtrW(hwnd, core.sys.windows.winuser.GWLP_USERDATA));
336 
337 					if (pStaticDlg == core.sys.windows.windef.NULL) {
338 						return core.sys.windows.windef.FALSE;
339 					}
340 
341 					return (*pStaticDlg).run_dlgProc(message, wParam, lParam);
342 			}
343 		}
344 
345 	extern (Windows)
346 	nothrow @nogc
347 	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);
348 
349 	nothrow @nogc
350 	void alignWith(core.sys.windows.windef.HWND handle, core.sys.windows.windef.HWND handle2Align, .PosAlign pos, ref core.sys.windows.windef.POINT point)
351 
352 		in
353 		{
354 		}
355 
356 		do
357 		{
358 			core.sys.windows.windef.RECT rc, rc2;
359 			core.sys.windows.winuser.GetWindowRect(handle, &rc);
360 
361 			point.x = rc.left;
362 			point.y = rc.top;
363 
364 			switch (pos) {
365 				case .PosAlign.left:
366 					core.sys.windows.winuser.GetWindowRect(handle2Align, &rc2);
367 					point.x -= rc2.right - rc2.left;
368 
369 					break;
370 
371 				case .PosAlign.right:
372 					core.sys.windows.winuser.GetWindowRect(handle, &rc2);
373 					point.x += rc2.right - rc2.left;
374 
375 					break;
376 
377 				case .PosAlign.top:
378 					core.sys.windows.winuser.GetWindowRect(handle2Align, &rc2);
379 					point.y -= rc2.bottom - rc2.top;
380 
381 					break;
382 
383 				case .PosAlign.bottom:
384 					core.sys.windows.winuser.GetWindowRect(handle, &rc2);
385 					point.y += rc2.bottom - rc2.top;
386 
387 					break;
388 
389 				default:
390 					break;
391 			}
392 
393 			core.sys.windows.winuser.ScreenToClient(this._hSelf, &point);
394 		}
395 
396 	nothrow @nogc
397 	core.sys.windows.windef.HGLOBAL makeRTLResource(int dialogID, core.sys.windows.winuser.DLGTEMPLATE** ppMyDlgTemplate)
398 
399 		in
400 		{
401 			assert(ppMyDlgTemplate != null);
402 		}
403 
404 		do
405 		{
406 			// Get Dlg Template resource
407 			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);
408 
409 			if (hDialogRC == core.sys.windows.windef.NULL) {
410 				return core.sys.windows.windef.NULL;
411 			}
412 
413 			core.sys.windows.windef.HGLOBAL hDlgTemplate = core.sys.windows.winbase.LoadResource(this._hInst, hDialogRC);
414 
415 			if (hDlgTemplate == core.sys.windows.windef.NULL) {
416 				return core.sys.windows.windef.NULL;
417 			}
418 
419 			core.sys.windows.winuser.DLGTEMPLATE* pDlgTemplate = cast(core.sys.windows.winuser.DLGTEMPLATE*)(core.sys.windows.winbase.LockResource(hDlgTemplate));
420 
421 			if (pDlgTemplate == core.sys.windows.windef.NULL) {
422 				return core.sys.windows.windef.NULL;
423 			}
424 
425 			// Duplicate Dlg Template resource
426 			core.stdc.config.c_ulong sizeDlg = core.sys.windows.winbase.SizeofResource(this._hInst, hDialogRC);
427 			core.sys.windows.windef.HGLOBAL hMyDlgTemplate = core.sys.windows.winbase.GlobalAlloc(core.sys.windows.winbase.GPTR, sizeDlg);
428 			*ppMyDlgTemplate = cast(core.sys.windows.winuser.DLGTEMPLATE*)(core.sys.windows.winbase.GlobalLock(hMyDlgTemplate));
429 
430 			core.stdc..string.memcpy(*ppMyDlgTemplate, pDlgTemplate, sizeDlg);
431 
432 			.DLGTEMPLATEEX* pMyDlgTemplateEx = cast(.DLGTEMPLATEEX*)(*ppMyDlgTemplate);
433 
434 			if ((*pMyDlgTemplateEx).signature == 0xFFFF) {
435 				(*pMyDlgTemplateEx).exStyle |= core.sys.windows.winuser.WS_EX_LAYOUTRTL;
436 			} else {
437 				(*ppMyDlgTemplate).dwExtendedStyle |= core.sys.windows.winuser.WS_EX_LAYOUTRTL;
438 			}
439 
440 			return hMyDlgTemplate;
441 		}
442 }