create two new functions String::str() and String::toLower() to avoid _tcslwr() calls...
[reactos.git] / reactos / subsys / system / explorer / taskbar / traynotify.cpp
1 /*
2 * Copyright 2003, 2004, 2005 Martin Fuchs
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 */
18
19
20 //
21 // Explorer clone
22 //
23 // traynotify.cpp
24 //
25 // Martin Fuchs, 22.08.2003
26 //
27
28
29 #include <precomp.h>
30
31 #include "../resource.h"
32
33 #include "traynotify.h"
34
35
36 #include "../notifyhook/notifyhook.h"
37
38 NotifyHook::NotifyHook()
39 : WM_GETMODULEPATH(InstallNotifyHook())
40 {
41 }
42
43 NotifyHook::~NotifyHook()
44 {
45 DeinstallNotifyHook();
46 }
47
48 void NotifyHook::GetModulePath(HWND hwnd, HWND hwndCallback)
49 {
50 PostMessage(hwnd, WM_GETMODULEPATH, (WPARAM)hwndCallback, 0);
51 }
52
53 bool NotifyHook::ModulePathCopyData(LPARAM lparam, HWND* phwnd, String& path)
54 {
55 char buffer[MAX_PATH];
56
57 int l = GetWindowModulePathCopyData(lparam, phwnd, buffer, MAX_PATH);
58
59 if (l) {
60 path.assign(buffer, l);
61 return true;
62 } else
63 return false;
64 }
65
66
67 NotifyIconIndex::NotifyIconIndex(NOTIFYICONDATA* pnid)
68 {
69 _hWnd = pnid->hWnd;
70 _uID = pnid->uID;
71
72 // special handling for windows task manager
73 if ((int)_uID < 0)
74 _uID = 0;
75 }
76
77 NotifyIconIndex::NotifyIconIndex()
78 {
79 _hWnd = 0;
80 _uID = 0;
81 }
82
83
84 NotifyInfo::NotifyInfo()
85 {
86 _idx = -1;
87 _hIcon = 0;
88 _dwState = 0;
89 _uCallbackMessage = 0;
90 _version = 0;
91
92 _mode = NIM_AUTO;
93 _lastChange = GetTickCount();
94 }
95
96
97 // WCHAR versions von NOTIFYICONDATA
98 #define NID_SIZE_W6 sizeof(NOTIFYICONDATAW) // _WIN32_IE = 0x600
99 #define NID_SIZE_W5 (sizeof(NOTIFYICONDATAW)-sizeof(GUID)) // _WIN32_IE = 0x500
100 #define NID_SIZE_W3 (sizeof(NOTIFYICONDATAW)-sizeof(GUID)-(128-64)*sizeof(WCHAR)) // _WIN32_IE < 0x500
101
102 // CHAR versions von NOTIFYICONDATA
103 #define NID_SIZE_A6 sizeof(NOTIFYICONDATAA)
104 #define NID_SIZE_A5 (sizeof(NOTIFYICONDATAA)-sizeof(GUID))
105 #define NID_SIZE_A3 (sizeof(NOTIFYICONDATAA)-sizeof(GUID)-(128-64)*sizeof(CHAR))
106
107 bool NotifyInfo::modify(NOTIFYICONDATA* pnid)
108 {
109 bool changes = false;
110
111 if (_hWnd!=pnid->hWnd || _uID!=pnid->uID) {
112 _hWnd = pnid->hWnd;
113 _uID = pnid->uID;
114
115 changes = true;
116 }
117
118 if (pnid->uFlags & NIF_MESSAGE) {
119 if (_uCallbackMessage != pnid->uCallbackMessage) {
120 _uCallbackMessage = pnid->uCallbackMessage;
121 changes = true;
122 }
123 }
124
125 if (pnid->uFlags & NIF_ICON) {
126 // Some applications destroy the icon immediatelly after completing the
127 // NIM_ADD/MODIFY message, so we have to make a copy of it.
128 if (_hIcon)
129 DestroyIcon(_hIcon);
130
131 _hIcon = (HICON) CopyImage(pnid->hIcon, IMAGE_ICON, NOTIFYICON_SIZE, NOTIFYICON_SIZE, 0);
132
133 changes = true; ///@todo compare icon
134 }
135
136 #ifdef NIF_STATE // as of 21.08.2003 missing in MinGW headers
137 if (pnid->uFlags & NIF_STATE) {
138 DWORD new_state = (_dwState&~pnid->dwStateMask) | (pnid->dwState&pnid->dwStateMask);
139
140 if (_dwState != new_state) {
141 _dwState = new_state;
142 changes = true;
143 }
144 }
145 #endif
146
147 // store tool tip text
148 if (pnid->uFlags & NIF_TIP) {
149 String new_text;
150
151 if (pnid->cbSize==NID_SIZE_W6 || pnid->cbSize==NID_SIZE_W5 || pnid->cbSize==NID_SIZE_W3) {
152 // UNICODE version of NOTIFYICONDATA structure
153 LPCWSTR txt = (LPCWSTR)pnid->szTip;
154 int max_len = pnid->cbSize==NID_SIZE_W3? 64: 128;
155
156 // get tooltip string length
157 int l = 0;
158 for(; l<max_len; ++l)
159 if (!txt[l])
160 break;
161
162 new_text.assign(txt, l);
163
164 if (new_text != _tipText) {
165 _tipText = new_text;
166 changes = true;
167 }
168 } else if (pnid->cbSize==NID_SIZE_A6 || pnid->cbSize==NID_SIZE_A5 || pnid->cbSize==NID_SIZE_A3) {
169 LPCSTR txt = (LPCSTR)pnid->szTip;
170 int max_len = pnid->cbSize==NID_SIZE_A3? 64: 128;
171
172 int l = 0;
173 for(int l=0; l<max_len; ++l)
174 if (!txt[l])
175 break;
176
177 new_text.assign(txt, l);
178
179 if (new_text != _tipText) {
180 _tipText = new_text;
181 changes = true;
182 }
183 }
184 }
185
186 TCHAR title[MAX_PATH];
187
188 DWORD pid;
189 GetWindowThreadProcessId(_hWnd, &pid);
190
191 // avoid to send WM_GETTEXT messages to the own process
192 if (pid != GetCurrentProcessId())
193 if (GetWindowText(_hWnd, title, MAX_PATH)) {
194 if (_windowTitle != title) {
195 _windowTitle = title;
196 changes = true;
197 }
198 }
199
200 if (changes) {
201 create_name();
202 _lastChange = GetTickCount();
203 }
204
205 return changes;
206 }
207
208
209 NotifyArea::NotifyArea(HWND hwnd)
210 : super(hwnd),
211 _tooltip(hwnd)
212 {
213 _next_idx = 0;
214 _clock_width = 0;
215 _last_icon_count = 0;
216 _show_hidden = false;
217 _hide_inactive = true;
218 _show_button = true;
219 }
220
221 NotifyArea::~NotifyArea()
222 {
223 KillTimer(_hwnd, 0);
224
225 write_config();
226 }
227
228 static bool get_hide_clock_from_registry()
229 {
230 HKEY hkeyStuckRects = 0;
231 DWORD buffer[10];
232 DWORD len = sizeof(buffer);
233
234 bool hide_clock = false;
235
236 // check if the clock should be hidden
237 if (!RegOpenKey(HKEY_CURRENT_USER, TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StuckRects2"), &hkeyStuckRects) &&
238 !RegQueryValueEx(hkeyStuckRects, TEXT("Settings"), 0, NULL, (LPBYTE)buffer, &len) &&
239 len==sizeof(buffer) && buffer[0]==sizeof(buffer))
240 hide_clock = buffer[2] & 0x08? true: false;
241
242 if (hkeyStuckRects)
243 RegCloseKey(hkeyStuckRects);
244
245 return hide_clock;
246 }
247
248 void NotifyArea::read_config()
249 {
250 bool clock_visible = true;
251
252 // read notification icon settings from XML configuration
253 XMLPos cfg_pos = g_Globals.get_cfg();
254
255 #ifndef __MINGW32__ // SHRestricted() missing in MinGW (as of 29.10.2003)
256 if (!g_Globals._SHRestricted || !SHRestricted(REST_HIDECLOCK))
257 #endif
258 {
259 if (cfg_pos.go_down("desktopbar")) {
260 clock_visible = XMLBoolRef(XMLPos(cfg_pos,"options"), "show-clock", !get_hide_clock_from_registry());
261 cfg_pos.back();
262 }
263 }
264
265 if (cfg_pos.go_down("notify-icons")) {
266 XMLPos options(cfg_pos, "options");
267
268 _hide_inactive = XMLBool(options, "hide-inactive", true); ///@todo read default setting from registry
269 _show_hidden = XMLBool(options, "show-hidden", false); ///@todo read default setting from registry
270 _show_button = XMLBool(options, "show-button", true);
271
272 XMLChildrenFilter icons(cfg_pos, "icon");
273
274 for(XMLChildrenFilter::iterator it=icons.begin(); it!=icons.end(); ++it) {
275 const XMLNode& node = **it;
276
277 NotifyIconConfig cfg;
278
279 cfg._name = node.get("name").c_str();
280 cfg._tipText = node.get("text").c_str();
281 cfg._windowTitle = node.get("window").c_str();
282 cfg._modulePath = node.get("module").c_str();
283 const string& mode = node.get("show");
284
285 if (mode == "show")
286 cfg._mode = NIM_SHOW;
287 else if (mode == "hide")
288 cfg._mode = NIM_HIDE;
289 else //if (mode == "auto")
290 cfg._mode = NIM_HIDE;
291
292 _cfg.push_back(cfg);
293 }
294
295 cfg_pos.back();
296 }
297
298 show_clock(clock_visible);
299 }
300
301 void NotifyArea::write_config()
302 {
303 // write notification icon settings to XML configuration file
304 XMLPos cfg_pos = g_Globals.get_cfg();
305
306 cfg_pos.smart_create("desktopbar");
307 XMLBoolRef boolRef(XMLPos(cfg_pos,"options"), "show-clock");
308 boolRef = _hwndClock!=0;
309 cfg_pos.back();
310
311 cfg_pos.smart_create("notify-icons");
312
313 XMLPos options(cfg_pos, "options");
314 XMLBoolRef(options, "hide-inactive") = _hide_inactive;
315 XMLBoolRef(options, "show-hidden") = _show_hidden;
316 XMLBoolRef(options, "show-button") = _show_button;
317
318 for(NotifyIconCfgList::iterator it=_cfg.begin(); it!=_cfg.end(); ++it) {
319 NotifyIconConfig& cfg = *it;
320
321 // search for the corresponding node using the original name
322 cfg_pos.smart_create("icon", "name", cfg._name);
323
324 // refresh unique name
325 cfg.create_name();
326
327 cfg_pos["name"] = cfg._name.c_str();
328 cfg_pos["text"] = cfg._tipText.c_str();
329 cfg_pos["window"] = cfg._windowTitle.c_str();
330 cfg_pos["module"] = cfg._modulePath.c_str();
331 cfg_pos["show"] = string_from_mode(cfg._mode).c_str();
332
333 cfg_pos.back();
334 }
335
336 cfg_pos.back(); // smart_create
337 }
338
339 void NotifyArea::show_clock(bool flag)
340 {
341 bool vis = _hwndClock!=0;
342
343 if (vis != flag) {
344 if (flag) {
345 // create clock window
346 _hwndClock = ClockWindow::Create(_hwnd);
347
348 if (_hwndClock) {
349 ClientRect clock_size(_hwndClock);
350 _clock_width = clock_size.right;
351 }
352 } else {
353 DestroyWindow(_hwndClock);
354 _hwndClock = 0;
355 _clock_width = 0;
356 }
357
358 SendMessage(GetParent(_hwnd), PM_RESIZE_CHILDREN, 0, 0);
359 }
360 }
361
362 LRESULT NotifyArea::Init(LPCREATESTRUCT pcs)
363 {
364 if (super::Init(pcs))
365 return 1;
366
367 read_config();
368
369 SetTimer(_hwnd, 0, 1000, NULL);
370
371 return 0;
372 }
373
374 HWND NotifyArea::Create(HWND hwndParent)
375 {
376 static BtnWindowClass wcTrayNotify(CLASSNAME_TRAYNOTIFY, CS_DBLCLKS);
377
378 ClientRect clnt(hwndParent);
379
380 return Window::Create(WINDOW_CREATOR(NotifyArea), WS_EX_STATICEDGE,
381 wcTrayNotify, TITLE_TRAYNOTIFY, WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN,
382 clnt.right-(NOTIFYAREA_WIDTH_DEF+1), 1, NOTIFYAREA_WIDTH_DEF, clnt.bottom-2, hwndParent);
383 }
384
385 LRESULT NotifyArea::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam)
386 {
387 switch(nmsg) {
388 case WM_PAINT:
389 Paint();
390 break;
391
392 case WM_TIMER: {
393 Refresh();
394
395 ClockWindow* clock_window = GET_WINDOW(ClockWindow, _hwndClock);
396
397 if (clock_window)
398 clock_window->TimerTick();
399 break;}
400
401 case PM_REFRESH:
402 Refresh(true);
403 break;
404
405 case WM_SIZE: {
406 int cx = LOWORD(lparam);
407 SetWindowPos(_hwndClock, 0, cx-_clock_width, 0, 0, 0, SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
408 break;}
409
410 case PM_GET_WIDTH: {
411 int w = _sorted_icons.size()*NOTIFYICON_DIST + NOTIFYAREA_SPACE + _clock_width;
412 if (_show_button)
413 w += NOTIFYICON_DIST;
414 return w;}
415
416 case PM_REFRESH_CONFIG:
417 read_config();
418 break;
419
420 case WM_CONTEXTMENU: {
421 Point pt(lparam);
422 ScreenToClient(_hwnd, &pt);
423
424 if (IconHitTest(pt) == _sorted_icons.end()) { // display menu only when no icon clicked
425 PopupMenu menu(IDM_NOTIFYAREA);
426 SetMenuDefaultItem(menu, 0, MF_BYPOSITION);
427 CheckMenuItem(menu, ID_SHOW_HIDDEN_ICONS, MF_BYCOMMAND|(_show_hidden?MF_CHECKED:MF_UNCHECKED));
428 CheckMenuItem(menu, ID_SHOW_ICON_BUTTON, MF_BYCOMMAND|(_show_button?MF_CHECKED:MF_UNCHECKED));
429 menu.TrackPopupMenu(_hwnd, MAKEPOINTS(lparam));
430 }
431 break;}
432
433 case WM_COPYDATA: { // receive NotifyHook answers
434 String path;
435 HWND hwnd;
436
437 if (_hook.ModulePathCopyData(lparam, &hwnd, path))
438 _window_modules[hwnd] = path;
439 break;}
440
441 default:
442 if (nmsg>=WM_MOUSEFIRST && nmsg<=WM_MOUSELAST) {
443 // close startup menu and other popup menus
444 // This functionality is missing in MS Windows.
445 if (nmsg==WM_LBUTTONDOWN || nmsg==WM_MBUTTONDOWN || nmsg==WM_RBUTTONDOWN
446 #ifdef WM_XBUTTONDOWN
447 || nmsg==WM_XBUTTONDOWN
448 #endif
449 )
450
451 CancelModes();
452
453 Point pt(lparam);
454 NotifyIconSet::const_iterator found = IconHitTest(pt);
455
456 if (found != _sorted_icons.end()) {
457 const NotifyInfo& entry = const_cast<NotifyInfo&>(*found); // Why does GCC 3.3 need this additional const_cast ?!
458
459 // set activation time stamp
460 if (nmsg == WM_LBUTTONDOWN || // Some programs need PostMessage() instead of SendMessage().
461 nmsg == WM_MBUTTONDOWN || // So call SendMessage() only for BUTTONUP and BLCLK messages
462 #ifdef WM_XBUTTONDOWN
463 nmsg == WM_XBUTTONDOWN ||
464 #endif
465 nmsg == WM_RBUTTONDOWN) {
466 _icon_map[entry]._lastChange = GetTickCount();
467 }
468
469 // Notify the message if the owner is still alive
470 if (IsWindow(entry._hWnd)) {
471 if (nmsg == WM_MOUSEMOVE || // avoid to call blocking SendMessage() for merely moving the mouse over icons
472 nmsg == WM_LBUTTONDOWN || // Some programs need PostMessage() instead of SendMessage().
473 nmsg == WM_MBUTTONDOWN || // So call SendMessage() only for BUTTONUP and BLCLK messages
474 #ifdef WM_XBUTTONDOWN
475 nmsg == WM_XBUTTONDOWN ||
476 #endif
477 nmsg == WM_RBUTTONDOWN)
478 PostMessage(entry._hWnd, entry._uCallbackMessage, entry._uID, nmsg);
479 else {
480 // allow SetForegroundWindow() in client process
481 DWORD pid;
482
483 if (GetWindowThreadProcessId(entry._hWnd, &pid)) {
484 // bind dynamically to AllowSetForegroundWindow() to be compatible to WIN98
485 static DynamicFct<BOOL(WINAPI*)(DWORD)> AllowSetForegroundWindow(TEXT("USER32"), "AllowSetForegroundWindow");
486
487 if (AllowSetForegroundWindow)
488 (*AllowSetForegroundWindow)(pid);
489 }
490
491 // use PostMessage() for notifcation icons of Shell Service Objects in the own process
492 if (pid == GetCurrentProcessId())
493 PostMessage(entry._hWnd, entry._uCallbackMessage, entry._uID, nmsg);
494 else
495 SendMessage(entry._hWnd, entry._uCallbackMessage, entry._uID, nmsg);
496 }
497 }
498 else if (_icon_map.erase(entry)) // delete icons without valid owner window
499 UpdateIcons();
500 } else
501 // handle clicks on notification area button "show hidden icons"
502 if (_show_button)
503 if (nmsg == WM_LBUTTONDOWN)
504 if (pt.x>=NOTIFYICON_X && pt.x<NOTIFYICON_X+NOTIFYICON_SIZE &&
505 pt.y>=NOTIFYICON_Y && pt.y<NOTIFYICON_Y+NOTIFYICON_SIZE)
506 PostMessage(_hwnd, WM_COMMAND, MAKEWPARAM(ID_SHOW_HIDDEN_ICONS,0), 0);
507 }
508
509 return super::WndProc(nmsg, wparam, lparam);
510 }
511
512 return 0;
513 }
514
515 int NotifyArea::Command(int id, int code)
516 {
517 switch(id) {
518 case ID_SHOW_HIDDEN_ICONS:
519 _show_hidden = !_show_hidden;
520 UpdateIcons();
521 break;
522
523 case ID_SHOW_ICON_BUTTON:
524 _show_button = !_show_button;
525 UpdateIcons();
526 break;
527
528 case ID_CONFIG_NOTIFYAREA:
529 Dialog::DoModal(IDD_NOTIFYAREA, WINDOW_CREATOR(TrayNotifyDlg), GetParent(_hwnd));
530 break;
531
532 case ID_CONFIG_TIME:
533 launch_cpanel(_hwnd, TEXT("timedate.cpl"));
534 break;
535
536 default:
537 SendParent(WM_COMMAND, MAKELONG(id,code), 0);
538 }
539
540 return 0;
541 }
542
543 int NotifyArea::Notify(int id, NMHDR* pnmh)
544 {
545 if (pnmh->code == TTN_GETDISPINFO) {
546 LPNMTTDISPINFO pdi = (LPNMTTDISPINFO)pnmh;
547
548 Point pt(GetMessagePos());
549 ScreenToClient(_hwnd, &pt);
550
551 if (_show_button &&
552 pt.x>=NOTIFYICON_X && pt.x<NOTIFYICON_X+NOTIFYICON_SIZE &&
553 pt.y>=NOTIFYICON_Y && pt.y<NOTIFYICON_Y+NOTIFYICON_SIZE)
554 {
555 static ResString sShowIcons(IDS_SHOW_HIDDEN_ICONS);
556 static ResString sHideIcons(IDS_HIDE_ICONS);
557
558 pdi->lpszText = (_show_hidden? sHideIcons: sShowIcons).str();
559 } else {
560 NotifyIconSet::iterator found = IconHitTest(pt);
561
562 if (found != _sorted_icons.end()) {
563 NotifyInfo& entry = const_cast<NotifyInfo&>(*found); // Why does GCC 3.3 need this additional const_cast ?!
564
565 // enable multiline tooltips (break at CR/LF and for very long one-line strings)
566 SendMessage(pnmh->hwndFrom, TTM_SETMAXTIPWIDTH, 0, 400);
567
568 pdi->lpszText = entry._tipText.str();
569 }
570 }
571 }
572
573 return 0;
574 }
575
576 void NotifyArea::CancelModes()
577 {
578 PostMessage(HWND_BROADCAST, WM_CANCELMODE, 0, 0);
579
580 for(NotifyIconSet::const_iterator it=_sorted_icons.begin(); it!=_sorted_icons.end(); ++it)
581 PostMessage(it->_hWnd, WM_CANCELMODE, 0, 0);
582 }
583
584 LRESULT NotifyArea::ProcessTrayNotification(int notify_code, NOTIFYICONDATA* pnid)
585 {
586 switch(notify_code) {
587 case NIM_ADD:
588 case NIM_MODIFY:
589 if ((int)pnid->uID >= 0) { ///@todo This is a fix for Windows Task Manager.
590 NotifyInfo& entry = _icon_map[pnid];
591
592 // a new entry?
593 if (entry._idx == -1)
594 entry._idx = ++_next_idx;
595 /* equivalent code using iterator::find();
596 NotifyIconMap::iterator found = _icon_map.find(pnid);
597 NotifyInfo* pentry;
598 // a new entry?
599 if (found == _icon_map.end()) {
600 pentry = &_icon_map[pnid];
601 pentry->_idx = ++_next_idx;
602 } else {
603 pentry = &found->second;
604 }
605 NotifyInfo& entry = *pentry;
606 */
607 bool changes = entry.modify(pnid);
608
609 #if NOTIFYICON_VERSION>=3 // as of 21.08.2003 missing in MinGW headers
610 if (DetermineHideState(entry) && entry._mode==NIM_HIDE) {
611 entry._dwState |= NIS_HIDDEN;
612 changes = true;
613 }
614 #endif
615
616 if (changes)
617 UpdateIcons(); ///@todo call only if really changes occurred
618
619 return TRUE;
620 }
621 break;
622
623 case NIM_DELETE: {
624 NotifyIconMap::iterator found = _icon_map.find(pnid);
625
626 if (found != _icon_map.end()) {
627 if (found->second._hIcon)
628 DestroyIcon(found->second._hIcon);
629 _icon_map.erase(found);
630 UpdateIcons();
631 return TRUE;
632 }
633 break;}
634
635 #if NOTIFYICON_VERSION>=3 // as of 21.08.2003 missing in MinGW headers
636 case NIM_SETFOCUS:
637 SetForegroundWindow(_hwnd);
638 return TRUE;
639
640 case NIM_SETVERSION:
641 NotifyIconMap::iterator found = _icon_map.find(pnid);
642
643 if (found != _icon_map.end()) {
644 found->second._version = pnid->UNION_MEMBER(uVersion);
645 return TRUE;
646 } else
647 return FALSE;
648 #endif
649 }
650
651 return FALSE;
652 }
653
654 void NotifyArea::UpdateIcons()
655 {
656 _sorted_icons.clear();
657
658 // sort icon infos by display index
659 for(NotifyIconMap::const_iterator it=_icon_map.begin(); it!=_icon_map.end(); ++it) {
660 const NotifyInfo& entry = it->second;
661
662 #ifdef NIF_STATE // as of 21.08.2003 missing in MinGW headers
663 if (_show_hidden || !(entry._dwState & NIS_HIDDEN))
664 #endif
665 _sorted_icons.insert(entry);
666 }
667
668 // sync tooltip areas to current icon number
669 if (_sorted_icons.size() != _last_icon_count) {
670 RECT rect = {NOTIFYICON_X, NOTIFYICON_Y, NOTIFYICON_X+NOTIFYICON_SIZE, NOTIFYICON_Y+NOTIFYICON_SIZE};
671
672 size_t tt_idx = 0;
673
674 if (_show_button) {
675 _tooltip.add(_hwnd, tt_idx++, rect);
676
677 rect.left += NOTIFYICON_DIST;
678 rect.right += NOTIFYICON_DIST;
679 }
680
681 size_t icon_cnt = _sorted_icons.size();
682 while(tt_idx < icon_cnt) {
683 _tooltip.add(_hwnd, tt_idx++, rect);
684
685 rect.left += NOTIFYICON_DIST;
686 rect.right += NOTIFYICON_DIST;
687 }
688
689 while(tt_idx < _last_icon_count)
690 _tooltip.remove(_hwnd, tt_idx++);
691
692 _last_icon_count = _sorted_icons.size();
693 }
694
695 SendMessage(GetParent(_hwnd), PM_RESIZE_CHILDREN, 0, 0);
696
697 InvalidateRect(_hwnd, NULL, FALSE); // refresh icon display
698 UpdateWindow(_hwnd);
699 }
700
701 #ifndef _NO_ALPHABLEND
702 #ifdef _MSC_VER
703 #pragma comment(lib, "msimg32") // for AlphaBlend()
704 #endif
705 #endif
706
707 void NotifyArea::Paint()
708 {
709 BufferedPaintCanvas canvas(_hwnd);
710
711 // first fill with the background color
712 FillRect(canvas, &canvas.rcPaint, GetSysColorBrush(COLOR_BTNFACE));
713
714 // draw icons
715 int x = NOTIFYICON_X;
716 int y = NOTIFYICON_Y;
717
718 if (_show_button) {
719 static SmallIcon leftArrowIcon(IDI_NOTIFY_L);
720 static SmallIcon rightArrowIcon(IDI_NOTIFY_R);
721
722 DrawIconEx(canvas, x, y, _show_hidden?rightArrowIcon:leftArrowIcon, NOTIFYICON_SIZE, NOTIFYICON_SIZE, 0, 0, DI_NORMAL);
723 x += NOTIFYICON_DIST;
724 }
725
726 #ifndef _NO_ALPHABLEND
727 MemCanvas mem_dc;
728 SelectedBitmap bmp(mem_dc, CreateCompatibleBitmap(canvas, NOTIFYICON_SIZE, NOTIFYICON_SIZE));
729 RECT rect = {0, 0, NOTIFYICON_SIZE, NOTIFYICON_SIZE};
730 BLENDFUNCTION blend = {AC_SRC_OVER, 0, 128, 0}; // 50 % visible
731 #endif
732
733 for(NotifyIconSet::const_iterator it=_sorted_icons.begin(); it!=_sorted_icons.end(); ++it) {
734 #ifndef _NO_ALPHABLEND
735 if (it->_dwState & NIS_HIDDEN) {
736 FillRect(mem_dc, &rect, GetSysColorBrush(COLOR_BTNFACE));
737 DrawIconEx(mem_dc, 0, 0, it->_hIcon, NOTIFYICON_SIZE, NOTIFYICON_SIZE, 0, 0, DI_NORMAL);
738 AlphaBlend(canvas, x, y, NOTIFYICON_SIZE, NOTIFYICON_SIZE, mem_dc, 0, 0, NOTIFYICON_SIZE, NOTIFYICON_SIZE, blend);
739 } else
740 #endif
741 DrawIconEx(canvas, x, y, it->_hIcon, NOTIFYICON_SIZE, NOTIFYICON_SIZE, 0, 0, DI_NORMAL);
742
743 x += NOTIFYICON_DIST;
744 }
745 }
746
747 void NotifyArea::Refresh(bool update)
748 {
749 // Look for task icons without valid owner window.
750 // This is an extended feature missing in MS Windows.
751 for(NotifyIconSet::const_iterator it=_sorted_icons.begin(); it!=_sorted_icons.end(); ++it) {
752 const NotifyInfo& entry = *it;
753
754 if (!IsWindow(entry._hWnd))
755 if (_icon_map.erase(entry)) // delete icons without valid owner window
756 ++update;
757 }
758
759 DWORD now = GetTickCount();
760
761 // handle icon hiding
762 for(NotifyIconMap::iterator it=_icon_map.begin(); it!=_icon_map.end(); ++it) {
763 NotifyInfo& entry = it->second;
764
765 DetermineHideState(entry);
766
767 switch(entry._mode) {
768 case NIM_HIDE:
769 if (!(entry._dwState & NIS_HIDDEN)) {
770 entry._dwState |= NIS_HIDDEN;
771 ++update;
772 }
773 break;
774
775 case NIM_SHOW:
776 if (entry._dwState&NIS_HIDDEN) {
777 entry._dwState &= ~NIS_HIDDEN;
778 ++update;
779 }
780 break;
781
782 case NIM_AUTO:
783 // automatically hide icons after long periods of inactivity
784 if (_hide_inactive)
785 if (!(entry._dwState & NIS_HIDDEN))
786 if (now-entry._lastChange > ICON_AUTOHIDE_SECONDS*1000) {
787 entry._dwState |= NIS_HIDDEN;
788 ++update;
789 }
790 break;
791 }
792 }
793
794 if (update)
795 UpdateIcons();
796 }
797
798 /// search for a icon at a given client coordinate position
799 NotifyIconSet::iterator NotifyArea::IconHitTest(const POINT& pos)
800 {
801 if (pos.y<NOTIFYICON_Y || pos.y>=NOTIFYICON_Y+NOTIFYICON_SIZE)
802 return _sorted_icons.end();
803
804 NotifyIconSet::iterator it = _sorted_icons.begin();
805
806 int x = NOTIFYICON_X;
807
808 if (_show_button)
809 x += NOTIFYICON_DIST;
810
811 for(; it!=_sorted_icons.end(); ++it) {
812 //NotifyInfo& entry = const_cast<NotifyInfo&>(*it); // Why does GCC 3.3 need this additional const_cast ?!
813
814 if (pos.x>=x && pos.x<x+NOTIFYICON_SIZE)
815 break;
816
817 x += NOTIFYICON_DIST;
818 }
819
820 return it;
821 }
822
823
824 void NotifyIconConfig::create_name()
825 {
826 _name = FmtString(TEXT("'%s' - '%s' - '%s'"), _tipText.c_str(), _windowTitle.c_str(), _modulePath.c_str());
827 }
828
829
830 #if NOTIFYICON_VERSION>=3 // as of 21.08.2003 missing in MinGW headers
831
832 bool NotifyIconConfig::match(const NotifyIconConfig& props) const
833 {
834 if (!_tipText.empty() && !props._tipText.empty())
835 if (props._tipText == _tipText)
836 return true;
837
838 if (!_windowTitle.empty() && !props._windowTitle.empty())
839 if (_tcsstr(props._windowTitle, _windowTitle))
840 return true;
841
842 if (!_modulePath.empty() && !props._modulePath.empty())
843 if (!_tcsicmp(props._modulePath, _modulePath))
844 return true;
845
846 return false;
847 }
848
849 bool NotifyArea::DetermineHideState(NotifyInfo& entry)
850 {
851 if (entry._modulePath.empty()) {
852 const String& modulePath = _window_modules[entry._hWnd];
853
854 // request module path for new windows (We will get an asynchronous answer by a WM_COPYDATA message.)
855 if (!modulePath.empty())
856 entry._modulePath = modulePath;
857 else
858 _hook.GetModulePath(entry._hWnd, _hwnd);
859 }
860
861 for(NotifyIconCfgList::const_iterator it=_cfg.begin(); it!=_cfg.end(); ++it) {
862 const NotifyIconConfig& cfg = *it;
863
864 if (cfg.match(entry)) {
865 entry._mode = cfg._mode;
866 return true;
867 }
868 }
869
870 return false;
871 }
872
873 #endif
874
875
876 String string_from_mode(NOTIFYICONMODE mode)
877 {
878 switch(mode) {
879 case NIM_SHOW:
880 return ResString(IDS_NOTIFY_SHOW);
881
882 case NIM_HIDE:
883 return ResString(IDS_NOTIFY_HIDE);
884
885 default: //case NIM_AUTO
886 return ResString(IDS_NOTIFY_AUTOHIDE);
887 }
888 }
889
890
891 TrayNotifyDlg::TrayNotifyDlg(HWND hwnd)
892 : super(hwnd),
893 _tree_ctrl(GetDlgItem(hwnd, IDC_NOTIFY_ICONS)),
894 _himl(ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR24, 3, 0)),
895 _pNotifyArea(static_cast<NotifyArea*>(Window::get_window((HWND)SendMessage(g_Globals._hwndDesktopBar, PM_GET_NOTIFYAREA, 0, 0))))
896 {
897 _selectedItem = 0;
898
899 if (_pNotifyArea) {
900 // save original icon states and configuration data
901 for(NotifyIconMap::const_iterator it=_pNotifyArea->_icon_map.begin(); it!=_pNotifyArea->_icon_map.end(); ++it)
902 _icon_states_org[it->first] = IconStatePair(it->second._mode, it->second._dwState);
903
904 _cfg_org = _pNotifyArea->_cfg;
905 _show_hidden_org = _pNotifyArea->_show_hidden;
906 }
907
908 SetWindowIcon(hwnd, IDI_REACTOS);
909
910 _haccel = LoadAccelerators(g_Globals._hInstance, MAKEINTRESOURCE(IDA_TRAYNOTIFY));
911
912 {
913 WindowCanvas canvas(_hwnd);
914 HBRUSH hbkgnd = GetStockBrush(WHITE_BRUSH);
915
916 ImageList_AddAlphaIcon(_himl, SmallIcon(IDI_DOT), hbkgnd, canvas);
917 ImageList_AddAlphaIcon(_himl, SmallIcon(IDI_DOT_TRANS), hbkgnd, canvas);
918 ImageList_AddAlphaIcon(_himl, SmallIcon(IDI_DOT_RED), hbkgnd, canvas);
919 }
920
921 (void)TreeView_SetImageList(_tree_ctrl, _himl, TVSIL_NORMAL);
922
923 _resize_mgr.Add(IDC_NOTIFY_ICONS, RESIZE);
924 _resize_mgr.Add(IDC_LABEL1, MOVE_Y);
925 _resize_mgr.Add(IDC_NOTIFY_TOOLTIP, RESIZE_X|MOVE_Y);
926 _resize_mgr.Add(IDC_LABEL2, MOVE_Y);
927 _resize_mgr.Add(IDC_NOTIFY_TITLE, RESIZE_X|MOVE_Y);
928 _resize_mgr.Add(IDC_LABEL3, MOVE_Y);
929 _resize_mgr.Add(IDC_NOTIFY_MODULE, RESIZE_X|MOVE_Y);
930
931 _resize_mgr.Add(IDC_LABEL4, MOVE_Y);
932 _resize_mgr.Add(IDC_NOTIFY_SHOW, MOVE_Y);
933 _resize_mgr.Add(IDC_NOTIFY_HIDE, MOVE_Y);
934 _resize_mgr.Add(IDC_NOTIFY_AUTOHIDE,MOVE_Y);
935
936 _resize_mgr.Add(IDC_PICTURE, MOVE);
937 _resize_mgr.Add(ID_SHOW_HIDDEN_ICONS,MOVE_Y);
938
939 _resize_mgr.Add(IDC_LABEL6, MOVE_Y);
940 _resize_mgr.Add(IDC_LAST_CHANGE, MOVE_Y);
941
942 _resize_mgr.Add(IDOK, MOVE);
943 _resize_mgr.Add(IDCANCEL, MOVE);
944
945 _resize_mgr.Resize(+150, +200);
946
947 Refresh();
948
949 SetTimer(_hwnd, 0, 3000, NULL);
950 register_pretranslate(hwnd);
951 }
952
953 TrayNotifyDlg::~TrayNotifyDlg()
954 {
955 KillTimer(_hwnd, 0);
956 unregister_pretranslate(_hwnd);
957 ImageList_Destroy(_himl);
958 }
959
960 void TrayNotifyDlg::Refresh()
961 {
962 ///@todo refresh incrementally
963
964 HiddenWindow hide(_tree_ctrl);
965
966 TreeView_DeleteAllItems(_tree_ctrl);
967
968 TV_INSERTSTRUCT tvi;
969
970 tvi.hParent = 0;
971 tvi.hInsertAfter = TVI_LAST;
972
973 TV_ITEM& tv = tvi.item;
974 tv.mask = TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE;
975
976 ResString str_cur(IDS_ITEMS_CUR);
977 tv.pszText = str_cur.str();
978 tv.iSelectedImage = tv.iImage = 0; // IDI_DOT
979 _hitemCurrent = TreeView_InsertItem(_tree_ctrl, &tvi);
980
981 ResString str_conf(IDS_ITEMS_CONFIGURED);
982 tv.pszText = str_conf.str();
983 tv.iSelectedImage = tv.iImage = 2; // IDI_DOT_RED
984 _hitemConfig = TreeView_InsertItem(_tree_ctrl, &tvi);
985
986 tvi.hParent = _hitemCurrent;
987
988 ResString str_visible(IDS_ITEMS_VISIBLE);
989 tv.pszText = str_visible.str();
990 tv.iSelectedImage = tv.iImage = 0; // IDI_DOT
991 _hitemCurrent_visible = TreeView_InsertItem(_tree_ctrl, &tvi);
992
993 ResString str_hidden(IDS_ITEMS_HIDDEN);
994 tv.pszText = str_hidden.str();
995 tv.iSelectedImage = tv.iImage = 1; // IDI_DOT_TRANS
996 _hitemCurrent_hidden = TreeView_InsertItem(_tree_ctrl, &tvi);
997
998 if (_pNotifyArea) {
999 _info.clear();
1000
1001 tv.mask |= TVIF_PARAM;
1002
1003 WindowCanvas canvas(_hwnd);
1004
1005 // insert current (visible and hidden) items
1006 for(NotifyIconMap::const_iterator it=_pNotifyArea->_icon_map.begin(); it!=_pNotifyArea->_icon_map.end(); ++it) {
1007 const NotifyInfo& entry = it->second;
1008
1009 InsertItem(entry._dwState&NIS_HIDDEN? _hitemCurrent_hidden: _hitemCurrent_visible, TVI_LAST, entry, canvas);
1010 }
1011
1012 // insert configured items in tree view
1013 const NotifyIconCfgList& cfg = _pNotifyArea->_cfg;
1014 for(NotifyIconCfgList::const_iterator it=cfg.begin(); it!=cfg.end(); ++it) {
1015 const NotifyIconConfig& cfg_entry = *it;
1016
1017 HICON hicon = 0;
1018
1019 if (!cfg_entry._modulePath.empty()) {
1020 if ((int)ExtractIconEx(cfg_entry._modulePath, 0, NULL, &hicon, 1) <= 0)
1021 hicon = 0;
1022
1023 if (!hicon) {
1024 SHFILEINFO sfi;
1025
1026 if (SHGetFileInfo(cfg_entry._modulePath, 0, &sfi, sizeof(sfi), SHGFI_ICON|SHGFI_SMALLICON))
1027 hicon = sfi.hIcon;
1028 }
1029 }
1030
1031 InsertItem(_hitemConfig, TVI_SORT, cfg_entry, canvas, hicon, cfg_entry._mode);
1032
1033 if (hicon)
1034 DestroyIcon(hicon);
1035 }
1036
1037 CheckDlgButton(_hwnd, ID_SHOW_HIDDEN_ICONS, _pNotifyArea->_show_hidden? BST_CHECKED: BST_UNCHECKED);
1038 }
1039
1040 TreeView_Expand(_tree_ctrl, _hitemCurrent_visible, TVE_EXPAND);
1041 TreeView_Expand(_tree_ctrl, _hitemCurrent_hidden, TVE_EXPAND);
1042 TreeView_Expand(_tree_ctrl, _hitemCurrent, TVE_EXPAND);
1043 TreeView_Expand(_tree_ctrl, _hitemConfig, TVE_EXPAND);
1044
1045 TreeView_EnsureVisible(_tree_ctrl, _hitemCurrent_visible);
1046 }
1047
1048 void TrayNotifyDlg::InsertItem(HTREEITEM hparent, HTREEITEM after, const NotifyInfo& entry, HDC hdc)
1049 {
1050 InsertItem(hparent, after, entry, hdc, entry._hIcon, entry._mode);
1051 }
1052
1053 void TrayNotifyDlg::InsertItem(HTREEITEM hparent, HTREEITEM after, const NotifyIconDlgInfo& entry,
1054 HDC hdc, HICON hicon, NOTIFYICONMODE mode)
1055 {
1056 int idx = _info.size() + 1;
1057 _info[idx] = entry;
1058
1059 String mode_str = string_from_mode(mode);
1060
1061 switch(mode) {
1062 case NIM_SHOW: mode_str = ResString(IDS_NOTIFY_SHOW); break;
1063 case NIM_HIDE: mode_str = ResString(IDS_NOTIFY_HIDE); break;
1064 case NIM_AUTO: mode_str = ResString(IDS_NOTIFY_AUTOHIDE);
1065 }
1066
1067 FmtString txt(TEXT("%s - %s [%s]"), entry._tipText.c_str(), entry._windowTitle.c_str(), mode_str.c_str());
1068
1069 TV_INSERTSTRUCT tvi;
1070
1071 tvi.hParent = hparent;
1072 tvi.hInsertAfter = after;
1073
1074 TV_ITEM& tv = tvi.item;
1075 tv.mask = TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE|TVIF_PARAM;
1076
1077 tv.lParam = (LPARAM)idx;
1078 tv.pszText = txt.str();
1079 tv.iSelectedImage = tv.iImage = ImageList_AddAlphaIcon(_himl, hicon, GetStockBrush(WHITE_BRUSH), hdc);
1080 (void)TreeView_InsertItem(_tree_ctrl, &tvi);
1081 }
1082
1083 LRESULT TrayNotifyDlg::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam)
1084 {
1085 switch(nmsg) {
1086 case PM_TRANSLATE_MSG: {
1087 MSG* pmsg = (MSG*) lparam;
1088
1089 if (TranslateAccelerator(_hwnd, _haccel, pmsg))
1090 return TRUE;
1091
1092 return FALSE;}
1093
1094 case WM_TIMER:
1095 Refresh();
1096 break;
1097
1098 default:
1099 return super::WndProc(nmsg, wparam, lparam);
1100 }
1101
1102 return 0;
1103 }
1104
1105 int TrayNotifyDlg::Command(int id, int code)
1106 {
1107 if (code == BN_CLICKED) {
1108 switch(id) {
1109 case ID_REFRESH:
1110 Refresh();
1111 break;
1112
1113 case IDC_NOTIFY_SHOW:
1114 SetIconMode(NIM_SHOW);
1115 break;
1116
1117 case IDC_NOTIFY_HIDE:
1118 SetIconMode(NIM_HIDE);
1119 break;
1120
1121 case IDC_NOTIFY_AUTOHIDE:
1122 SetIconMode(NIM_AUTO);
1123 break;
1124
1125 case ID_SHOW_HIDDEN_ICONS:
1126 if (_pNotifyArea)
1127 SendMessage(*_pNotifyArea, WM_COMMAND, MAKEWPARAM(id,code), 0);
1128 break;
1129
1130 case IDOK:
1131 EndDialog(_hwnd, id);
1132 break;
1133
1134 case IDCANCEL:
1135 // rollback changes
1136 if (_pNotifyArea) {
1137 // restore original icon states and configuration data
1138 _pNotifyArea->_cfg = _cfg_org;
1139 _pNotifyArea->_show_hidden = _show_hidden_org;
1140
1141 for(IconStateMap::const_iterator it=_icon_states_org.begin(); it!=_icon_states_org.end(); ++it) {
1142 NotifyInfo& info = _pNotifyArea->_icon_map[it->first];
1143
1144 info._mode = it->second.first;
1145 info._dwState = it->second.second;
1146 }
1147
1148 SendMessage(*_pNotifyArea, PM_REFRESH, 0, 0);
1149 }
1150
1151 EndDialog(_hwnd, id);
1152 break;
1153 }
1154
1155 return 0;
1156 }
1157
1158 return 1;
1159 }
1160
1161 int TrayNotifyDlg::Notify(int id, NMHDR* pnmh)
1162 {
1163 switch(pnmh->code) {
1164 case TVN_SELCHANGED: {
1165 NMTREEVIEW* pnmtv = (NMTREEVIEW*)pnmh;
1166 int idx = pnmtv->itemNew.lParam;
1167
1168 if (idx) {
1169 RefreshProperties(_info[idx]);
1170 _selectedItem = pnmtv->itemNew.hItem;
1171 } else {
1172 /*
1173 SetDlgItemText(_hwnd, IDC_NOTIFY_TOOLTIP, NULL);
1174 SetDlgItemText(_hwnd, IDC_NOTIFY_TITLE, NULL);
1175 SetDlgItemText(_hwnd, IDC_NOTIFY_MODULE, NULL);
1176 */
1177 CheckRadioButton(_hwnd, IDC_NOTIFY_SHOW, IDC_NOTIFY_AUTOHIDE, 0);
1178 }
1179 break;}
1180 }
1181
1182 return 0;
1183 }
1184
1185 void TrayNotifyDlg::RefreshProperties(const NotifyIconDlgInfo& entry)
1186 {
1187 SetDlgItemText(_hwnd, IDC_NOTIFY_TOOLTIP, entry._tipText);
1188 SetDlgItemText(_hwnd, IDC_NOTIFY_TITLE, entry._windowTitle);
1189 SetDlgItemText(_hwnd, IDC_NOTIFY_MODULE, entry._modulePath);
1190
1191 CheckRadioButton(_hwnd, IDC_NOTIFY_SHOW, IDC_NOTIFY_AUTOHIDE, IDC_NOTIFY_SHOW+entry._mode);
1192
1193 String change_str;
1194 if (entry._lastChange)
1195 change_str.printf(TEXT("before %d s"), (GetTickCount()-entry._lastChange+500)/1000);
1196 SetDlgItemText(_hwnd, IDC_LAST_CHANGE, change_str);
1197
1198 HICON hicon = 0; //get_window_icon_big(entry._hWnd, false);
1199
1200 // If we could not find an icon associated with the owner window, try to load one from the owning module.
1201 if (!hicon && !entry._modulePath.empty()) {
1202 hicon = ExtractIcon(g_Globals._hInstance, entry._modulePath, 0);
1203
1204 if (!hicon) {
1205 SHFILEINFO sfi;
1206
1207 if (SHGetFileInfo(entry._modulePath, 0, &sfi, sizeof(sfi), SHGFI_ICON|SHGFI_LARGEICON))
1208 hicon = sfi.hIcon;
1209 }
1210 }
1211
1212 if (hicon) {
1213 SendMessage(GetDlgItem(_hwnd, IDC_PICTURE), STM_SETICON, (LPARAM)hicon, 0);
1214 DestroyIcon(hicon);
1215 } else
1216 SendMessage(GetDlgItem(_hwnd, IDC_PICTURE), STM_SETICON, 0, 0);
1217 }
1218
1219 void TrayNotifyDlg::SetIconMode(NOTIFYICONMODE mode)
1220 {
1221 int idx = TreeView_GetItemData(_tree_ctrl, _selectedItem);
1222
1223 if (!idx)
1224 return;
1225
1226 NotifyIconConfig& entry = _info[idx];
1227
1228 if (entry._mode != mode) {
1229 entry._mode = mode;
1230
1231 // trigger refresh in notify area and this dialog
1232 if (_pNotifyArea)
1233 SendMessage(*_pNotifyArea, PM_REFRESH, 0, 0);
1234 }
1235
1236 if (_pNotifyArea) {
1237 bool found = false;
1238
1239 NotifyIconCfgList& cfg = _pNotifyArea->_cfg;
1240 for(NotifyIconCfgList::iterator it=cfg.begin(); it!=cfg.end(); ++it) {
1241 NotifyIconConfig& cfg_entry = *it;
1242
1243 if (cfg_entry.match(entry)) {
1244 cfg_entry._mode = mode;
1245 ++found;
1246 break;
1247 }
1248 }
1249
1250 if (!found) {
1251 // insert new configuration entry
1252 NotifyIconConfig cfg_entry = entry;
1253
1254 cfg_entry._mode = mode;
1255
1256 _pNotifyArea->_cfg.push_back(cfg_entry);
1257 }
1258 }
1259
1260 Refresh();
1261 ///@todo select treeview item at new position in tree view -> refresh HTREEITEM in _selectedItem
1262 }
1263
1264
1265 ClockWindow::ClockWindow(HWND hwnd)
1266 : super(hwnd),
1267 _tooltip(hwnd)
1268 {
1269 *_time = TEXT('\0');
1270 FormatTime();
1271
1272 _tooltip.add(_hwnd, _hwnd);
1273 }
1274
1275 HWND ClockWindow::Create(HWND hwndParent)
1276 {
1277 static BtnWindowClass wcClock(CLASSNAME_CLOCKWINDOW, CS_DBLCLKS);
1278
1279 ClientRect clnt(hwndParent);
1280
1281 WindowCanvas canvas(hwndParent);
1282 FontSelection font(canvas, GetStockFont(DEFAULT_GUI_FONT));
1283
1284 RECT rect = {0, 0, 0, 0};
1285 TCHAR buffer[16];
1286
1287 if (!GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL, NULL, buffer, sizeof(buffer)/sizeof(TCHAR)))
1288 _tcscpy(buffer, TEXT("00:00"));
1289
1290 DrawText(canvas, buffer, -1, &rect, DT_SINGLELINE|DT_NOPREFIX|DT_CALCRECT);
1291 int clockwindowWidth = rect.right-rect.left + 4;
1292
1293 return Window::Create(WINDOW_CREATOR(ClockWindow), 0,
1294 wcClock, NULL, WS_CHILD|WS_VISIBLE,
1295 clnt.right-(clockwindowWidth), 1, clockwindowWidth, clnt.bottom-2, hwndParent);
1296 }
1297
1298 LRESULT ClockWindow::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam)
1299 {
1300 switch(nmsg) {
1301 case WM_PAINT:
1302 Paint();
1303 break;
1304
1305 case WM_LBUTTONDBLCLK:
1306 launch_cpanel(_hwnd, TEXT("timedate.cpl"));
1307 break;
1308
1309 default:
1310 return super::WndProc(nmsg, wparam, lparam);
1311 }
1312
1313 return 0;
1314 }
1315
1316 int ClockWindow::Notify(int id, NMHDR* pnmh)
1317 {
1318 if (pnmh->code == TTN_GETDISPINFO) {
1319 LPNMTTDISPINFO pdi = (LPNMTTDISPINFO)pnmh;
1320
1321 SYSTEMTIME systime;
1322 TCHAR buffer[64];
1323
1324 GetLocalTime(&systime);
1325
1326 if (GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, &systime, NULL, buffer, 64))
1327 _tcscpy(pdi->szText, buffer);
1328 else
1329 pdi->szText[0] = '\0';
1330 }
1331
1332 return 0;
1333 }
1334
1335 void ClockWindow::TimerTick()
1336 {
1337 if (FormatTime())
1338 InvalidateRect(_hwnd, NULL, TRUE); // refresh displayed time
1339 }
1340
1341 bool ClockWindow::FormatTime()
1342 {
1343 TCHAR buffer[16];
1344
1345 if (GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL, NULL, buffer, sizeof(buffer)/sizeof(TCHAR)))
1346 if (_tcscmp(buffer, _time)) {
1347 _tcscpy(_time, buffer);
1348 return true; // The text to display has changed.
1349 }
1350
1351 return false; // no change
1352 }
1353
1354 void ClockWindow::Paint()
1355 {
1356 PaintCanvas canvas(_hwnd);
1357
1358 FillRect(canvas, &canvas.rcPaint, GetSysColorBrush(COLOR_BTNFACE));
1359
1360 BkMode bkmode(canvas, TRANSPARENT);
1361 FontSelection font(canvas, GetStockFont(ANSI_VAR_FONT));
1362
1363 DrawText(canvas, _time, -1, ClientRect(_hwnd), DT_SINGLELINE|DT_VCENTER|DT_NOPREFIX);
1364 }