1 // Written by Christopher E. Miller 2 // See the included license.txt for copyright and license details. 3 4 5 /// 6 module dfl.notifyicon; 7 8 private import dfl.internal.winapi, dfl.base, dfl.drawing; 9 private import dfl.control, dfl.form, dfl.application; 10 private import dfl.event, dfl.internal.utf, dfl.internal.dlib; 11 12 version(DFL_NO_MENUS) 13 { 14 } 15 else 16 { 17 private import dfl.menu; 18 } 19 20 21 /// 22 class NotifyIcon // docmain 23 { 24 version(DFL_NO_MENUS) 25 { 26 } 27 else 28 { 29 /// 30 final @property void contextMenu(ContextMenu menu) // setter 31 { 32 this.cmenu = menu; 33 } 34 35 /// ditto 36 final @property ContextMenu contextMenu() // getter 37 { 38 return cmenu; 39 } 40 } 41 42 43 /// 44 final @property void icon(Icon ico) // setter 45 { 46 _icon = ico; 47 nid.hIcon = ico ? ico.handle : null; 48 49 if(visible) 50 { 51 nid.uFlags = NIF_ICON; 52 Shell_NotifyIconA(NIM_MODIFY, &nid); 53 } 54 } 55 56 /// ditto 57 final @property Icon icon() // getter 58 { 59 return _icon; 60 } 61 62 63 /// 64 // Must be less than 64 chars. 65 // To-do: hold reference to setter's string, use that for getter.. ? 66 final @property void text(Dstring txt) // setter 67 { 68 if(txt.length >= nid.szTip.length) 69 throw new DflException("Notify icon text too long"); 70 71 // To-do: support Unicode. 72 73 txt = unsafeAnsi(txt); // ... 74 nid.szTip[txt.length] = 0; 75 nid.szTip[0 .. txt.length] = txt[]; 76 tipLen = txt.length; 77 78 if(visible) 79 { 80 nid.uFlags = NIF_TIP; 81 Shell_NotifyIconA(NIM_MODIFY, &nid); 82 } 83 } 84 85 /// ditto 86 final @property Dstring text() // getter 87 { 88 //return nid.szTip[0 .. tipLen]; // Returning possibly mutated text! 89 //return nid.szTip[0 .. tipLen].dup; 90 //return nid.szTip[0 .. tipLen].idup; // Needed in D2. Doesn't work in D1. 91 return cast(Dstring)nid.szTip[0 .. tipLen].dup; // Needed in D2. Doesn't work in D1. 92 } 93 94 95 /// 96 final @property void visible(bool byes) // setter 97 { 98 if(byes) 99 { 100 if(!nid.uID) 101 { 102 nid.uID = allocNotifyIconID(); 103 assert(nid.uID); 104 allNotifyIcons[nid.uID] = this; 105 } 106 107 _forceAdd(); 108 } 109 else if(nid.uID) 110 { 111 _forceDelete(); 112 113 //delete allNotifyIcons[nid.uID]; 114 allNotifyIcons.remove(nid.uID); 115 nid.uID = 0; 116 } 117 } 118 119 /// ditto 120 final @property bool visible() // getter 121 { 122 return nid.uID != 0; 123 } 124 125 126 /// 127 final void show() 128 { 129 visible = true; 130 } 131 132 /// ditto 133 final void hide() 134 { 135 visible = false; 136 } 137 138 139 //EventHandler click; 140 Event!(NotifyIcon, EventArgs) click; /// 141 //EventHandler doubleClick; 142 Event!(NotifyIcon, EventArgs) doubleClick; /// 143 //MouseEventHandler mouseDown; 144 Event!(NotifyIcon, MouseEventArgs) mouseDown; /// 145 //MouseEventHandler mouseUp; 146 Event!(NotifyIcon, MouseEventArgs) mouseUp; /// 147 //MouseEventHandler mouseMove; 148 Event!(NotifyIcon, MouseEventArgs) mouseMove; /// 149 150 151 this() 152 { 153 if(!ctrlNotifyIcon) 154 _init(); 155 156 nid.cbSize = nid.sizeof; 157 nid.hWnd = ctrlNotifyIcon.handle; 158 nid.uID = 0; 159 nid.uCallbackMessage = WM_NOTIFYICON; 160 nid.hIcon = null; 161 nid.szTip[0] = '\0'; 162 } 163 164 165 ~this() 166 { 167 if(nid.uID) 168 { 169 _forceDelete(); 170 //delete allNotifyIcons[nid.uID]; 171 allNotifyIcons.remove(nid.uID); 172 } 173 174 //delete allNotifyIcons[nid.uID]; 175 //allNotifyIcons.remove(nid.uID); 176 177 /+ 178 if(!allNotifyIcons.length) 179 { 180 delete ctrlNotifyIcon; 181 ctrlNotifyIcon = null; 182 } 183 +/ 184 } 185 186 187 /// 188 // Extra. 189 void minimize(IWindow win) 190 { 191 LONG style; 192 HWND hwnd; 193 194 hwnd = win.handle; 195 style = GetWindowLongA(hwnd, GWL_STYLE); 196 197 if(style & WS_VISIBLE) 198 { 199 ShowOwnedPopups(hwnd, FALSE); 200 201 if(!(style & WS_MINIMIZE) && _animation()) 202 { 203 RECT myRect, areaRect; 204 205 GetWindowRect(hwnd, &myRect); 206 _area(areaRect); 207 DrawAnimatedRects(hwnd, 3, &myRect, &areaRect); 208 } 209 210 ShowWindow(hwnd, SW_HIDE); 211 } 212 } 213 214 215 /// 216 // Extra. 217 void restore(IWindow win) 218 { 219 LONG style; 220 HWND hwnd; 221 222 hwnd = win.handle; 223 style = GetWindowLongA(hwnd, GWL_STYLE); 224 225 if(!(style & WS_VISIBLE)) 226 { 227 if(style & WS_MINIMIZE) 228 { 229 ShowWindow(hwnd, SW_RESTORE); 230 } 231 else 232 { 233 if(_animation()) 234 { 235 RECT myRect, areaRect; 236 237 GetWindowRect(hwnd, &myRect); 238 _area(areaRect); 239 DrawAnimatedRects(hwnd, 3, &areaRect, &myRect); 240 } 241 242 ShowWindow(hwnd, SW_SHOW); 243 244 ShowOwnedPopups(hwnd, TRUE); 245 } 246 } 247 else 248 { 249 if(style & WS_MINIMIZE) 250 ShowWindow(hwnd, SW_RESTORE); 251 } 252 253 SetForegroundWindow(hwnd); 254 } 255 256 257 private: 258 259 NOTIFYICONDATA nid; 260 int tipLen = 0; 261 version(DFL_NO_MENUS) 262 { 263 } 264 else 265 { 266 ContextMenu cmenu; 267 } 268 Icon _icon; 269 270 271 package final void _forceAdd() 272 { 273 nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; 274 Shell_NotifyIconA(NIM_ADD, &nid); 275 } 276 277 278 package final void _forceDelete() 279 { 280 Shell_NotifyIconA(NIM_DELETE, &nid); 281 } 282 283 284 // Returns true if min/restore animation is on. 285 static bool _animation() 286 { 287 ANIMATIONINFO ai; 288 289 ai.cbSize = ai.sizeof; 290 SystemParametersInfoA(SPI_GETANIMATION, ai.sizeof, &ai, 0); 291 292 return ai.iMinAnimate ? true : false; 293 } 294 295 296 // Gets the tray area. 297 static void _area(out RECT rect) 298 { 299 HWND hwTaskbar, hw; 300 301 hwTaskbar = FindWindowExA(null, null, "Shell_TrayWnd", null); 302 if(hwTaskbar) 303 { 304 hw = FindWindowExA(hwTaskbar, null, "TrayNotifyWnd", null); 305 if(hw) 306 { 307 GetWindowRect(hw, &rect); 308 return; 309 } 310 } 311 312 APPBARDATA abd; 313 314 abd.cbSize = abd.sizeof; 315 if(SHAppBarMessage(ABM_GETTASKBARPOS, &abd)) 316 { 317 switch(abd.uEdge) 318 { 319 case ABE_LEFT: 320 case ABE_RIGHT: 321 rect.top = abd.rc.bottom - 100; 322 rect.bottom = abd.rc.bottom - 16; 323 rect.left = abd.rc.left; 324 rect.right = abd.rc.right; 325 break; 326 327 case ABE_TOP: 328 case ABE_BOTTOM: 329 rect.top = abd.rc.top; 330 rect.bottom = abd.rc.bottom; 331 rect.left = abd.rc.right - 100; 332 rect.right = abd.rc.right - 16; 333 break; 334 335 default: 336 } 337 } 338 else if(hwTaskbar) 339 { 340 GetWindowRect(hwTaskbar, &rect); 341 if(rect.right - rect.left > 150) 342 rect.left = rect.right - 150; 343 if(rect.bottom - rect.top > 30) 344 rect.top = rect.bottom - 30; 345 } 346 else 347 { 348 SystemParametersInfoA(SPI_GETWORKAREA, 0, &rect, 0); 349 rect.left = rect.right - 150; 350 rect.top = rect.bottom - 30; 351 } 352 } 353 } 354 355 356 package: 357 358 359 enum UINT WM_NOTIFYICON = WM_USER + 34; // -wparam- is id, -lparam- is the mouse message such as WM_LBUTTONDBLCLK. 360 UINT wmTaskbarCreated; 361 NotifyIcon[UINT] allNotifyIcons; // Indexed by ID. 362 UINT lastId = 1; 363 NotifyIconControl ctrlNotifyIcon; 364 365 366 class NotifyIconControl: Control 367 { 368 override void createHandle() 369 { 370 //if(created) 371 if(isHandleCreated) 372 return; 373 374 if(killing) 375 { 376 create_err: 377 throw new DflException("Notify icon initialization failure"); 378 } 379 380 Application.creatingControl(this); 381 hwnd = CreateWindowExA(wexstyle, CONTROL_CLASSNAME.ptr, "NotifyIcon", 0, 0, 0, 0, 0, null, null, 382 Application.getInstance(), null); 383 if(!hwnd) 384 goto create_err; 385 } 386 387 388 protected override void wndProc(ref Message msg) 389 { 390 if(msg.msg == WM_NOTIFYICON) 391 { 392 if(cast(UINT)msg.wParam in allNotifyIcons) 393 { 394 NotifyIcon ni; 395 Point pt; 396 397 ni = allNotifyIcons[cast(UINT)msg.wParam]; 398 399 switch(cast(UINT)msg.lParam) // msg. 400 { 401 case WM_MOUSEMOVE: 402 pt = Cursor.position; 403 ni.mouseMove(ni, new MouseEventArgs(Control.mouseButtons(), 0, pt.x, pt.y, 0)); 404 break; 405 406 case WM_LBUTTONUP: 407 pt = Cursor.position; 408 ni.mouseUp(ni, new MouseEventArgs(MouseButtons.LEFT, 1, pt.x, pt.y, 0)); 409 410 ni.click(ni, EventArgs.empty); 411 break; 412 413 case WM_RBUTTONUP: 414 pt = Cursor.position; 415 ni.mouseUp(ni, new MouseEventArgs(MouseButtons.RIGHT, 1, pt.x, pt.y, 0)); 416 417 version(DFL_NO_MENUS) 418 { 419 } 420 else 421 { 422 if(ni.cmenu) 423 ni.cmenu.show(ctrlNotifyIcon, pt); 424 } 425 break; 426 427 case WM_LBUTTONDOWN: 428 pt = Cursor.position; 429 ni.mouseDown(ni, new MouseEventArgs(MouseButtons.LEFT, 0, pt.x, pt.y, 0)); 430 break; 431 432 case WM_RBUTTONDOWN: 433 pt = Cursor.position; 434 ni.mouseDown(ni, new MouseEventArgs(MouseButtons.RIGHT, 0, pt.x, pt.y, 0)); 435 break; 436 437 case WM_LBUTTONDBLCLK: 438 ni.doubleClick(ni, EventArgs.empty); 439 break; 440 441 default: 442 } 443 } 444 } 445 else if(msg.msg == wmTaskbarCreated) 446 { 447 // Show all visible NotifyIcon's. 448 foreach(NotifyIcon ni; allNotifyIcons) 449 { 450 if(ni.visible) 451 ni._forceAdd(); 452 } 453 } 454 455 super.wndProc(msg); 456 } 457 } 458 459 460 static ~this() 461 { 462 // Due to all items not being destructed at program exit, 463 // remove all visible notify icons because the OS won't. 464 foreach(NotifyIcon ni; allNotifyIcons) 465 { 466 if(ni.visible) 467 ni._forceDelete(); 468 } 469 470 allNotifyIcons = null; 471 } 472 473 474 UINT allocNotifyIconID() 475 { 476 UINT prev; 477 prev = lastId; 478 for(;;) 479 { 480 lastId++; 481 if(lastId == ushort.max) 482 lastId = 1; 483 if(lastId == prev) 484 throw new DflException("Too many notify icons"); 485 486 if(!(lastId in allNotifyIcons)) 487 break; 488 } 489 return lastId; 490 } 491 492 493 void _init() 494 { 495 wmTaskbarCreated = RegisterWindowMessageA("TaskbarCreated"); 496 497 ctrlNotifyIcon = new NotifyIconControl; 498 ctrlNotifyIcon.visible = false; 499 } 500