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