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