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, ¢er); 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 }