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 = cast(int)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