[HAL]
[reactos.git] / reactos / base / shell / 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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, COUNTOF(buffer));
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, COUNTOF(title))) {
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 POINTS p;
423 p.x = (SHORT) pt.x;
424 p.y = (SHORT) pt.y;
425 ScreenToClient(_hwnd, &pt);
426
427 if (IconHitTest(pt) == _sorted_icons.end()) { // display menu only when no icon clicked
428 PopupMenu menu(IDM_NOTIFYAREA);
429 SetMenuDefaultItem(menu, 0, MF_BYPOSITION);
430 CheckMenuItem(menu, ID_SHOW_HIDDEN_ICONS, MF_BYCOMMAND|(_show_hidden?MF_CHECKED:MF_UNCHECKED));
431 CheckMenuItem(menu, ID_SHOW_ICON_BUTTON, MF_BYCOMMAND|(_show_button?MF_CHECKED:MF_UNCHECKED));
432 menu.TrackPopupMenu(_hwnd, p);
433 }
434 break;}
435
436 case WM_COPYDATA: { // receive NotifyHook answers
437 String path;
438 HWND hwnd;
439
440 if (_hook.ModulePathCopyData(lparam, &hwnd, path))
441 _window_modules[hwnd] = path;
442 break;}
443
444 default:
445 if (nmsg>=WM_MOUSEFIRST && nmsg<=WM_MOUSELAST) {
446 // close startup menu and other popup menus
447 // This functionality is missing in MS Windows.
448 if (nmsg==WM_LBUTTONDOWN || nmsg==WM_MBUTTONDOWN || nmsg==WM_RBUTTONDOWN
449 #ifdef WM_XBUTTONDOWN
450 || nmsg==WM_XBUTTONDOWN
451 #endif
452 )
453
454 CancelModes();
455
456 Point pt(lparam);
457 NotifyIconSet::const_iterator found = IconHitTest(pt);
458
459 if (found != _sorted_icons.end()) {
460 const NotifyInfo& entry = const_cast<NotifyInfo&>(*found); // Why does GCC 3.3 need this additional const_cast ?!
461
462 // set activation time stamp
463 if (nmsg == WM_LBUTTONDOWN || // Some programs need PostMessage() instead of SendMessage().
464 nmsg == WM_MBUTTONDOWN || // So call SendMessage() only for BUTTONUP and BLCLK messages
465 #ifdef WM_XBUTTONDOWN
466 nmsg == WM_XBUTTONDOWN ||
467 #endif
468 nmsg == WM_RBUTTONDOWN) {
469 _icon_map[entry]._lastChange = GetTickCount();
470 }
471
472 // Notify the message if the owner is still alive
473 if (IsWindow(entry._hWnd)) {
474 if (nmsg == WM_MOUSEMOVE || // avoid to call blocking SendMessage() for merely moving the mouse over icons
475 nmsg == WM_LBUTTONDOWN || // Some programs need PostMessage() instead of SendMessage().
476 nmsg == WM_MBUTTONDOWN || // So call SendMessage() only for BUTTONUP and BLCLK messages
477 #ifdef WM_XBUTTONDOWN
478 nmsg == WM_XBUTTONDOWN ||
479 #endif
480 nmsg == WM_RBUTTONDOWN)
481 PostMessage(entry._hWnd, entry._uCallbackMessage, entry._uID, nmsg);
482 else {
483 // allow SetForegroundWindow() in client process
484 DWORD pid;
485
486 if (GetWindowThreadProcessId(entry._hWnd, &pid)) {
487 // bind dynamically to AllowSetForegroundWindow() to be compatible to WIN98
488 static DynamicFct<BOOL(WINAPI*)(DWORD)> AllowSetForegroundWindow(TEXT("USER32"), "AllowSetForegroundWindow");
489
490 if (AllowSetForegroundWindow)
491 (*AllowSetForegroundWindow)(pid);
492 }
493
494 // use PostMessage() for notifcation icons of Shell Service Objects in the own process
495 if (pid == GetCurrentProcessId())
496 PostMessage(entry._hWnd, entry._uCallbackMessage, entry._uID, nmsg);
497 else
498 SendMessage(entry._hWnd, entry._uCallbackMessage, entry._uID, nmsg);
499 }
500 }
501 else if (_icon_map.erase(entry)) // delete icons without valid owner window
502 UpdateIcons();
503 } else
504 // handle clicks on notification area button "show hidden icons"
505 if (_show_button)
506 if (nmsg == WM_LBUTTONDOWN)
507 if (pt.x>=NOTIFYICON_X && pt.x<NOTIFYICON_X+NOTIFYICON_SIZE &&
508 pt.y>=NOTIFYICON_Y && pt.y<NOTIFYICON_Y+NOTIFYICON_SIZE)
509 PostMessage(_hwnd, WM_COMMAND, MAKEWPARAM(ID_SHOW_HIDDEN_ICONS,0), 0);
510 }
511
512 return super::WndProc(nmsg, wparam, lparam);
513 }
514
515 return 0;
516 }
517
518 int NotifyArea::Command(int id, int code)
519 {
520 switch(id) {
521 case ID_SHOW_HIDDEN_ICONS:
522 _show_hidden = !_show_hidden;
523 UpdateIcons();
524 break;
525
526 case ID_SHOW_ICON_BUTTON:
527 _show_button = !_show_button;
528 UpdateIcons();
529 break;
530
531 case ID_CONFIG_NOTIFYAREA:
532 Dialog::DoModal(IDD_NOTIFYAREA, WINDOW_CREATOR(TrayNotifyDlg), GetParent(_hwnd));
533 break;
534
535 case ID_CONFIG_TIME:
536 launch_cpanel(_hwnd, TEXT("timedate.cpl"));
537 break;
538
539 default:
540 SendParent(WM_COMMAND, MAKELONG(id,code), 0);
541 }
542
543 return 0;
544 }
545
546 int NotifyArea::Notify(int id, NMHDR* pnmh)
547 {
548 if (pnmh->code == TTN_GETDISPINFO) {
549 LPNMTTDISPINFO pdi = (LPNMTTDISPINFO)pnmh;
550
551 Point pt(GetMessagePos());
552 ScreenToClient(_hwnd, &pt);
553
554 if (_show_button &&
555 pt.x>=NOTIFYICON_X && pt.x<NOTIFYICON_X+NOTIFYICON_SIZE &&
556 pt.y>=NOTIFYICON_Y && pt.y<NOTIFYICON_Y+NOTIFYICON_SIZE)
557 {
558 static ResString sShowIcons(IDS_SHOW_HIDDEN_ICONS);
559 static ResString sHideIcons(IDS_HIDE_ICONS);
560
561 pdi->lpszText = (_show_hidden? sHideIcons: sShowIcons).str();
562 } else {
563 NotifyIconSet::iterator found = IconHitTest(pt);
564
565 if (found != _sorted_icons.end()) {
566 NotifyInfo& entry = const_cast<NotifyInfo&>(*found); // Why does GCC 3.3 need this additional const_cast ?!
567
568 // enable multiline tooltips (break at CR/LF and for very long one-line strings)
569 SendMessage(pnmh->hwndFrom, TTM_SETMAXTIPWIDTH, 0, 400);
570
571 pdi->lpszText = entry._tipText.str();
572 }
573 }
574 }
575
576 return 0;
577 }
578
579 void NotifyArea::CancelModes()
580 {
581 PostMessage(HWND_BROADCAST, WM_CANCELMODE, 0, 0);
582
583 for(NotifyIconSet::const_iterator it=_sorted_icons.begin(); it!=_sorted_icons.end(); ++it)
584 PostMessage(it->_hWnd, WM_CANCELMODE, 0, 0);
585 }
586
587 LRESULT NotifyArea::ProcessTrayNotification(int notify_code, NOTIFYICONDATA* pnid)
588 {
589 switch(notify_code) {
590 case NIM_ADD:
591 case NIM_MODIFY:
592 if ((int)pnid->uID >= 0) { ///@todo This is a fix for Windows Task Manager.
593 NotifyInfo& entry = _icon_map[pnid];
594
595 // a new entry?
596 if (entry._idx == -1)
597 entry._idx = ++_next_idx;
598 /* equivalent code using iterator::find();
599 NotifyIconMap::iterator found = _icon_map.find(pnid);
600 NotifyInfo* pentry;
601 // a new entry?
602 if (found == _icon_map.end()) {
603 pentry = &_icon_map[pnid];
604 pentry->_idx = ++_next_idx;
605 } else {
606 pentry = &found->second;
607 }
608 NotifyInfo& entry = *pentry;
609 */
610 bool changes = entry.modify(pnid);
611
612 #if NOTIFYICON_VERSION>=3 // as of 21.08.2003 missing in MinGW headers
613 if (DetermineHideState(entry) && entry._mode==NIM_HIDE) {
614 entry._dwState |= NIS_HIDDEN;
615 changes = true;
616 }
617 #endif
618
619 if (changes)
620 UpdateIcons(); ///@todo call only if really changes occurred
621
622 return TRUE;
623 }
624 break;
625
626 case NIM_DELETE: {
627 NotifyIconMap::iterator found = _icon_map.find(pnid);
628
629 if (found != _icon_map.end()) {
630 if (found->second._hIcon)
631 DestroyIcon(found->second._hIcon);
632 _icon_map.erase(found);
633 UpdateIcons();
634 return TRUE;
635 }
636 break;}
637
638 #if NOTIFYICON_VERSION>=3 // as of 21.08.2003 missing in MinGW headers
639 case NIM_SETFOCUS:
640 SetForegroundWindow(_hwnd);
641 return TRUE;
642
643 case NIM_SETVERSION:
644 NotifyIconMap::iterator found = _icon_map.find(pnid);
645
646 if (found != _icon_map.end()) {
647 found->second._version = pnid->UNION_MEMBER(uVersion);
648 return TRUE;
649 } else
650 return FALSE;
651 #endif
652 }
653
654 return FALSE;
655 }
656
657 void NotifyArea::UpdateIcons()
658 {
659 _sorted_icons.clear();
660
661 // sort icon infos by display index
662 for(NotifyIconMap::const_iterator it=_icon_map.begin(); it!=_icon_map.end(); ++it) {
663 const NotifyInfo& entry = it->second;
664
665 #ifdef NIF_STATE // as of 21.08.2003 missing in MinGW headers
666 if (_show_hidden || !(entry._dwState & NIS_HIDDEN))
667 #endif
668 _sorted_icons.insert(entry);
669 }
670
671 // sync tooltip areas to current icon number
672 if (_sorted_icons.size() != _last_icon_count) {
673 RECT rect = {NOTIFYICON_X, NOTIFYICON_Y, NOTIFYICON_X+NOTIFYICON_SIZE, NOTIFYICON_Y+NOTIFYICON_SIZE};
674
675 size_t tt_idx = 0;
676
677 if (_show_button) {
678 _tooltip.add(_hwnd, tt_idx++, rect);
679
680 rect.left += NOTIFYICON_DIST;
681 rect.right += NOTIFYICON_DIST;
682 }
683
684 size_t icon_cnt = _sorted_icons.size();
685 while(tt_idx < icon_cnt) {
686 _tooltip.add(_hwnd, tt_idx++, rect);
687
688 rect.left += NOTIFYICON_DIST;
689 rect.right += NOTIFYICON_DIST;
690 }
691
692 while(tt_idx < _last_icon_count)
693 _tooltip.remove(_hwnd, tt_idx++);
694
695 _last_icon_count = _sorted_icons.size();
696 }
697
698 SendMessage(GetParent(_hwnd), PM_RESIZE_CHILDREN, 0, 0);
699
700 InvalidateRect(_hwnd, NULL, FALSE); // refresh icon display
701 UpdateWindow(_hwnd);
702 }
703
704 #ifndef _NO_ALPHABLEND
705 #ifdef _MSC_VER
706 #pragma comment(lib, "msimg32") // for AlphaBlend()
707 #endif
708 #endif
709
710 void NotifyArea::Paint()
711 {
712 BufferedPaintCanvas canvas(_hwnd);
713
714 // first fill with the background color
715 FillRect(canvas, &canvas.rcPaint, GetSysColorBrush(COLOR_BTNFACE));
716
717 // draw icons
718 int x = NOTIFYICON_X;
719 int y = NOTIFYICON_Y;
720
721 if (_show_button) {
722 static SmallIcon leftArrowIcon(IDI_NOTIFY_L);
723 static SmallIcon rightArrowIcon(IDI_NOTIFY_R);
724
725 DrawIconEx(canvas, x, y, _show_hidden?rightArrowIcon:leftArrowIcon, NOTIFYICON_SIZE, NOTIFYICON_SIZE, 0, 0, DI_NORMAL);
726 x += NOTIFYICON_DIST;
727 }
728
729 #ifndef _NO_ALPHABLEND
730 MemCanvas mem_dc;
731 SelectedBitmap bmp(mem_dc, CreateCompatibleBitmap(canvas, NOTIFYICON_SIZE, NOTIFYICON_SIZE));
732 RECT rect = {0, 0, NOTIFYICON_SIZE, NOTIFYICON_SIZE};
733 BLENDFUNCTION blend = {AC_SRC_OVER, 0, 128, 0}; // 50 % visible
734 #endif
735
736 for(NotifyIconSet::const_iterator it=_sorted_icons.begin(); it!=_sorted_icons.end(); ++it) {
737 #ifndef _NO_ALPHABLEND
738 if (it->_dwState & NIS_HIDDEN) {
739 FillRect(mem_dc, &rect, GetSysColorBrush(COLOR_BTNFACE));
740 DrawIconEx(mem_dc, 0, 0, it->_hIcon, NOTIFYICON_SIZE, NOTIFYICON_SIZE, 0, 0, DI_NORMAL);
741 AlphaBlend(canvas, x, y, NOTIFYICON_SIZE, NOTIFYICON_SIZE, mem_dc, 0, 0, NOTIFYICON_SIZE, NOTIFYICON_SIZE, blend);
742 } else
743 #endif
744 DrawIconEx(canvas, x, y, it->_hIcon, NOTIFYICON_SIZE, NOTIFYICON_SIZE, 0, 0, DI_NORMAL);
745
746 x += NOTIFYICON_DIST;
747 }
748 }
749
750 void NotifyArea::Refresh(bool update)
751 {
752 // Look for task icons without valid owner window.
753 // This is an extended feature missing in MS Windows.
754 for(NotifyIconSet::const_iterator it=_sorted_icons.begin(); it!=_sorted_icons.end(); ++it) {
755 const NotifyInfo& entry = *it;
756
757 if (!IsWindow(entry._hWnd))
758 if (_icon_map.erase(entry)) // delete icons without valid owner window
759 ++update;
760 }
761
762 DWORD now = GetTickCount();
763
764 // handle icon hiding
765 for(NotifyIconMap::iterator it=_icon_map.begin(); it!=_icon_map.end(); ++it) {
766 NotifyInfo& entry = it->second;
767
768 DetermineHideState(entry);
769
770 switch(entry._mode) {
771 case NIM_HIDE:
772 if (!(entry._dwState & NIS_HIDDEN)) {
773 entry._dwState |= NIS_HIDDEN;
774 ++update;
775 }
776 break;
777
778 case NIM_SHOW:
779 if (entry._dwState&NIS_HIDDEN) {
780 entry._dwState &= ~NIS_HIDDEN;
781 ++update;
782 }
783 break;
784
785 case NIM_AUTO:
786 // automatically hide icons after long periods of inactivity
787 if (_hide_inactive)
788 if (!(entry._dwState & NIS_HIDDEN))
789 if (now-entry._lastChange > ICON_AUTOHIDE_SECONDS*1000) {
790 entry._dwState |= NIS_HIDDEN;
791 ++update;
792 }
793 break;
794 }
795 }
796
797 if (update)
798 UpdateIcons();
799 }
800
801 /// search for a icon at a given client coordinate position
802 NotifyIconSet::iterator NotifyArea::IconHitTest(const POINT& pos)
803 {
804 if (pos.y<NOTIFYICON_Y || pos.y>=NOTIFYICON_Y+NOTIFYICON_SIZE)
805 return _sorted_icons.end();
806
807 NotifyIconSet::iterator it = _sorted_icons.begin();
808
809 int x = NOTIFYICON_X;
810
811 if (_show_button)
812 x += NOTIFYICON_DIST;
813
814 for(; it!=_sorted_icons.end(); ++it) {
815 //NotifyInfo& entry = const_cast<NotifyInfo&>(*it); // Why does GCC 3.3 need this additional const_cast ?!
816
817 if (pos.x>=x && pos.x<x+NOTIFYICON_SIZE)
818 break;
819
820 x += NOTIFYICON_DIST;
821 }
822
823 return it;
824 }
825
826
827 void NotifyIconConfig::create_name()
828 {
829 _name = FmtString(TEXT("'%s' - '%s' - '%s'"), _tipText.c_str(), _windowTitle.c_str(), _modulePath.c_str());
830 }
831
832
833 #if NOTIFYICON_VERSION>=3 // as of 21.08.2003 missing in MinGW headers
834
835 bool NotifyIconConfig::match(const NotifyIconConfig& props) const
836 {
837 if (!_tipText.empty() && !props._tipText.empty())
838 if (props._tipText == _tipText)
839 return true;
840
841 if (!_windowTitle.empty() && !props._windowTitle.empty())
842 if (_tcsstr(props._windowTitle, _windowTitle))
843 return true;
844
845 if (!_modulePath.empty() && !props._modulePath.empty())
846 if (!_tcsicmp(props._modulePath, _modulePath))
847 return true;
848
849 return false;
850 }
851
852 bool NotifyArea::DetermineHideState(NotifyInfo& entry)
853 {
854 if (entry._modulePath.empty()) {
855 const String& modulePath = _window_modules[entry._hWnd];
856
857 // request module path for new windows (We will get an asynchronous answer by a WM_COPYDATA message.)
858 if (!modulePath.empty())
859 entry._modulePath = modulePath;
860 else
861 _hook.GetModulePath(entry._hWnd, _hwnd);
862 }
863
864 for(NotifyIconCfgList::const_iterator it=_cfg.begin(); it!=_cfg.end(); ++it) {
865 const NotifyIconConfig& cfg = *it;
866
867 if (cfg.match(entry)) {
868 entry._mode = cfg._mode;
869 return true;
870 }
871 }
872
873 return false;
874 }
875
876 #endif
877
878
879 String string_from_mode(NOTIFYICONMODE mode)
880 {
881 switch(mode) {
882 case NIM_SHOW:
883 return ResString(IDS_NOTIFY_SHOW);
884
885 case NIM_HIDE:
886 return ResString(IDS_NOTIFY_HIDE);
887
888 default: //case NIM_AUTO
889 return ResString(IDS_NOTIFY_AUTOHIDE);
890 }
891 }
892
893
894 TrayNotifyDlg::TrayNotifyDlg(HWND hwnd)
895 : super(hwnd),
896 _tree_ctrl(GetDlgItem(hwnd, IDC_NOTIFY_ICONS)),
897 _himl(ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR24, 3, 0)),
898 _pNotifyArea(static_cast<NotifyArea*>(Window::get_window((HWND)SendMessage(g_Globals._hwndDesktopBar, PM_GET_NOTIFYAREA, 0, 0))))
899 {
900 _selectedItem = 0;
901
902 if (_pNotifyArea) {
903 // save original icon states and configuration data
904 for(NotifyIconMap::const_iterator it=_pNotifyArea->_icon_map.begin(); it!=_pNotifyArea->_icon_map.end(); ++it)
905 _icon_states_org[it->first] = IconStatePair(it->second._mode, it->second._dwState);
906
907 _cfg_org = _pNotifyArea->_cfg;
908 _show_hidden_org = _pNotifyArea->_show_hidden;
909 }
910
911 SetWindowIcon(hwnd, IDI_REACTOS);
912
913 _haccel = LoadAccelerators(g_Globals._hInstance, MAKEINTRESOURCE(IDA_TRAYNOTIFY));
914
915 {
916 WindowCanvas canvas(_hwnd);
917 HBRUSH hbkgnd = GetStockBrush(WHITE_BRUSH);
918
919 ImageList_AddAlphaIcon(_himl, SmallIcon(IDI_DOT), hbkgnd, canvas);
920 ImageList_AddAlphaIcon(_himl, SmallIcon(IDI_DOT_TRANS), hbkgnd, canvas);
921 ImageList_AddAlphaIcon(_himl, SmallIcon(IDI_DOT_RED), hbkgnd, canvas);
922 }
923
924 (void)TreeView_SetImageList(_tree_ctrl, _himl, TVSIL_NORMAL);
925
926 _resize_mgr.Add(IDC_NOTIFY_ICONS, RESIZE);
927 _resize_mgr.Add(IDC_LABEL1, MOVE_Y);
928 _resize_mgr.Add(IDC_NOTIFY_TOOLTIP, RESIZE_X|MOVE_Y);
929 _resize_mgr.Add(IDC_LABEL2, MOVE_Y);
930 _resize_mgr.Add(IDC_NOTIFY_TITLE, RESIZE_X|MOVE_Y);
931 _resize_mgr.Add(IDC_LABEL3, MOVE_Y);
932 _resize_mgr.Add(IDC_NOTIFY_MODULE, RESIZE_X|MOVE_Y);
933
934 _resize_mgr.Add(IDC_LABEL4, MOVE_Y);
935 _resize_mgr.Add(IDC_NOTIFY_SHOW, MOVE_Y);
936 _resize_mgr.Add(IDC_NOTIFY_HIDE, MOVE_Y);
937 _resize_mgr.Add(IDC_NOTIFY_AUTOHIDE,MOVE_Y);
938
939 _resize_mgr.Add(IDC_PICTURE, MOVE);
940 _resize_mgr.Add(ID_SHOW_HIDDEN_ICONS,MOVE_Y);
941
942 _resize_mgr.Add(IDC_LABEL6, MOVE_Y);
943 _resize_mgr.Add(IDC_LAST_CHANGE, MOVE_Y);
944
945 _resize_mgr.Add(IDOK, MOVE);
946 _resize_mgr.Add(IDCANCEL, MOVE);
947
948 _resize_mgr.Resize(+150, +200);
949
950 Refresh();
951
952 SetTimer(_hwnd, 0, 3000, NULL);
953 register_pretranslate(hwnd);
954 }
955
956 TrayNotifyDlg::~TrayNotifyDlg()
957 {
958 KillTimer(_hwnd, 0);
959 unregister_pretranslate(_hwnd);
960 ImageList_Destroy(_himl);
961 }
962
963 void TrayNotifyDlg::Refresh()
964 {
965 ///@todo refresh incrementally
966
967 HiddenWindow hide(_tree_ctrl);
968
969 TreeView_DeleteAllItems(_tree_ctrl);
970
971 TV_INSERTSTRUCT tvi;
972
973 tvi.hParent = 0;
974 tvi.hInsertAfter = TVI_LAST;
975
976 TV_ITEM& tv = tvi.item;
977 tv.mask = TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE;
978
979 ResString str_cur(IDS_ITEMS_CUR);
980 tv.pszText = str_cur.str();
981 tv.iSelectedImage = tv.iImage = 0; // IDI_DOT
982 _hitemCurrent = TreeView_InsertItem(_tree_ctrl, &tvi);
983
984 ResString str_conf(IDS_ITEMS_CONFIGURED);
985 tv.pszText = str_conf.str();
986 tv.iSelectedImage = tv.iImage = 2; // IDI_DOT_RED
987 _hitemConfig = TreeView_InsertItem(_tree_ctrl, &tvi);
988
989 tvi.hParent = _hitemCurrent;
990
991 ResString str_visible(IDS_ITEMS_VISIBLE);
992 tv.pszText = str_visible.str();
993 tv.iSelectedImage = tv.iImage = 0; // IDI_DOT
994 _hitemCurrent_visible = TreeView_InsertItem(_tree_ctrl, &tvi);
995
996 ResString str_hidden(IDS_ITEMS_HIDDEN);
997 tv.pszText = str_hidden.str();
998 tv.iSelectedImage = tv.iImage = 1; // IDI_DOT_TRANS
999 _hitemCurrent_hidden = TreeView_InsertItem(_tree_ctrl, &tvi);
1000
1001 if (_pNotifyArea) {
1002 _info.clear();
1003
1004 tv.mask |= TVIF_PARAM;
1005
1006 WindowCanvas canvas(_hwnd);
1007
1008 // insert current (visible and hidden) items
1009 for(NotifyIconMap::const_iterator it=_pNotifyArea->_icon_map.begin(); it!=_pNotifyArea->_icon_map.end(); ++it) {
1010 const NotifyInfo& entry = it->second;
1011
1012 InsertItem(entry._dwState&NIS_HIDDEN? _hitemCurrent_hidden: _hitemCurrent_visible, TVI_LAST, entry, canvas);
1013 }
1014
1015 // insert configured items in tree view
1016 const NotifyIconCfgList& cfg = _pNotifyArea->_cfg;
1017 for(NotifyIconCfgList::const_iterator it=cfg.begin(); it!=cfg.end(); ++it) {
1018 const NotifyIconConfig& cfg_entry = *it;
1019
1020 HICON hicon = 0;
1021
1022 if (!cfg_entry._modulePath.empty()) {
1023 if ((int)ExtractIconEx(cfg_entry._modulePath, 0, NULL, &hicon, 1) <= 0)
1024 hicon = 0;
1025
1026 if (!hicon) {
1027 SHFILEINFO sfi;
1028
1029 if (SHGetFileInfo(cfg_entry._modulePath, 0, &sfi, sizeof(sfi), SHGFI_ICON|SHGFI_SMALLICON))
1030 hicon = sfi.hIcon;
1031 }
1032 }
1033
1034 InsertItem(_hitemConfig, TVI_SORT, cfg_entry, canvas, hicon, cfg_entry._mode);
1035
1036 if (hicon)
1037 DestroyIcon(hicon);
1038 }
1039
1040 CheckDlgButton(_hwnd, ID_SHOW_HIDDEN_ICONS, _pNotifyArea->_show_hidden? BST_CHECKED: BST_UNCHECKED);
1041 }
1042
1043 TreeView_Expand(_tree_ctrl, _hitemCurrent_visible, TVE_EXPAND);
1044 TreeView_Expand(_tree_ctrl, _hitemCurrent_hidden, TVE_EXPAND);
1045 TreeView_Expand(_tree_ctrl, _hitemCurrent, TVE_EXPAND);
1046 TreeView_Expand(_tree_ctrl, _hitemConfig, TVE_EXPAND);
1047
1048 TreeView_EnsureVisible(_tree_ctrl, _hitemCurrent_visible);
1049 }
1050
1051 void TrayNotifyDlg::InsertItem(HTREEITEM hparent, HTREEITEM after, const NotifyInfo& entry, HDC hdc)
1052 {
1053 InsertItem(hparent, after, entry, hdc, entry._hIcon, entry._mode);
1054 }
1055
1056 void TrayNotifyDlg::InsertItem(HTREEITEM hparent, HTREEITEM after, const NotifyIconDlgInfo& entry,
1057 HDC hdc, HICON hicon, NOTIFYICONMODE mode)
1058 {
1059 int idx = _info.size() + 1;
1060 _info[idx] = entry;
1061
1062 String mode_str = string_from_mode(mode);
1063
1064 switch(mode) {
1065 case NIM_SHOW: mode_str = ResString(IDS_NOTIFY_SHOW); break;
1066 case NIM_HIDE: mode_str = ResString(IDS_NOTIFY_HIDE); break;
1067 case NIM_AUTO: mode_str = ResString(IDS_NOTIFY_AUTOHIDE);
1068 }
1069
1070 FmtString txt(TEXT("%s - %s [%s]"), entry._tipText.c_str(), entry._windowTitle.c_str(), mode_str.c_str());
1071
1072 TV_INSERTSTRUCT tvi;
1073
1074 tvi.hParent = hparent;
1075 tvi.hInsertAfter = after;
1076
1077 TV_ITEM& tv = tvi.item;
1078 tv.mask = TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE|TVIF_PARAM;
1079
1080 tv.lParam = (LPARAM)idx;
1081 tv.pszText = txt.str();
1082 tv.iSelectedImage = tv.iImage = ImageList_AddAlphaIcon(_himl, hicon, GetStockBrush(WHITE_BRUSH), hdc);
1083 (void)TreeView_InsertItem(_tree_ctrl, &tvi);
1084 }
1085
1086 LRESULT TrayNotifyDlg::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam)
1087 {
1088 switch(nmsg) {
1089 case PM_TRANSLATE_MSG: {
1090 MSG* pmsg = (MSG*) lparam;
1091
1092 if (TranslateAccelerator(_hwnd, _haccel, pmsg))
1093 return TRUE;
1094
1095 return FALSE;}
1096
1097 case WM_TIMER:
1098 Refresh();
1099 break;
1100
1101 default:
1102 return super::WndProc(nmsg, wparam, lparam);
1103 }
1104
1105 return 0;
1106 }
1107
1108 int TrayNotifyDlg::Command(int id, int code)
1109 {
1110 if (code == BN_CLICKED) {
1111 switch(id) {
1112 case ID_REFRESH:
1113 Refresh();
1114 break;
1115
1116 case IDC_NOTIFY_SHOW:
1117 SetIconMode(NIM_SHOW);
1118 break;
1119
1120 case IDC_NOTIFY_HIDE:
1121 SetIconMode(NIM_HIDE);
1122 break;
1123
1124 case IDC_NOTIFY_AUTOHIDE:
1125 SetIconMode(NIM_AUTO);
1126 break;
1127
1128 case ID_SHOW_HIDDEN_ICONS:
1129 if (_pNotifyArea)
1130 SendMessage(*_pNotifyArea, WM_COMMAND, MAKEWPARAM(id,code), 0);
1131 break;
1132
1133 case IDOK:
1134 EndDialog(_hwnd, id);
1135 break;
1136
1137 case IDCANCEL:
1138 // rollback changes
1139 if (_pNotifyArea) {
1140 // restore original icon states and configuration data
1141 _pNotifyArea->_cfg = _cfg_org;
1142 _pNotifyArea->_show_hidden = _show_hidden_org;
1143
1144 for(IconStateMap::const_iterator it=_icon_states_org.begin(); it!=_icon_states_org.end(); ++it) {
1145 NotifyInfo& info = _pNotifyArea->_icon_map[it->first];
1146
1147 info._mode = it->second.first;
1148 info._dwState = it->second.second;
1149 }
1150
1151 SendMessage(*_pNotifyArea, PM_REFRESH, 0, 0);
1152 }
1153
1154 EndDialog(_hwnd, id);
1155 break;
1156 }
1157
1158 return 0;
1159 }
1160
1161 return 1;
1162 }
1163
1164 int TrayNotifyDlg::Notify(int id, NMHDR* pnmh)
1165 {
1166 switch(pnmh->code) {
1167 case TVN_SELCHANGED: {
1168 NMTREEVIEW* pnmtv = (NMTREEVIEW*)pnmh;
1169 int idx = pnmtv->itemNew.lParam;
1170
1171 if (idx) {
1172 RefreshProperties(_info[idx]);
1173 _selectedItem = pnmtv->itemNew.hItem;
1174 } else {
1175 /*
1176 SetDlgItemText(_hwnd, IDC_NOTIFY_TOOLTIP, NULL);
1177 SetDlgItemText(_hwnd, IDC_NOTIFY_TITLE, NULL);
1178 SetDlgItemText(_hwnd, IDC_NOTIFY_MODULE, NULL);
1179 */
1180 CheckRadioButton(_hwnd, IDC_NOTIFY_SHOW, IDC_NOTIFY_AUTOHIDE, 0);
1181 }
1182 break;}
1183 }
1184
1185 return 0;
1186 }
1187
1188 void TrayNotifyDlg::RefreshProperties(const NotifyIconDlgInfo& entry)
1189 {
1190 SetDlgItemText(_hwnd, IDC_NOTIFY_TOOLTIP, entry._tipText);
1191 SetDlgItemText(_hwnd, IDC_NOTIFY_TITLE, entry._windowTitle);
1192 SetDlgItemText(_hwnd, IDC_NOTIFY_MODULE, entry._modulePath);
1193
1194 CheckRadioButton(_hwnd, IDC_NOTIFY_SHOW, IDC_NOTIFY_AUTOHIDE, IDC_NOTIFY_SHOW+entry._mode);
1195
1196 String change_str;
1197 if (entry._lastChange)
1198 change_str.printf(TEXT("before %d s"), (GetTickCount()-entry._lastChange+500)/1000);
1199 SetDlgItemText(_hwnd, IDC_LAST_CHANGE, change_str);
1200
1201 HICON hicon = 0; //get_window_icon_big(entry._hWnd, false);
1202
1203 // If we could not find an icon associated with the owner window, try to load one from the owning module.
1204 if (!hicon && !entry._modulePath.empty()) {
1205 hicon = ExtractIcon(g_Globals._hInstance, entry._modulePath, 0);
1206
1207 if (!hicon) {
1208 SHFILEINFO sfi;
1209
1210 if (SHGetFileInfo(entry._modulePath, 0, &sfi, sizeof(sfi), SHGFI_ICON|SHGFI_LARGEICON))
1211 hicon = sfi.hIcon;
1212 }
1213 }
1214
1215 if (hicon) {
1216 SendMessage(GetDlgItem(_hwnd, IDC_PICTURE), STM_SETICON, (LPARAM)hicon, 0);
1217 DestroyIcon(hicon);
1218 } else
1219 SendMessage(GetDlgItem(_hwnd, IDC_PICTURE), STM_SETICON, 0, 0);
1220 }
1221
1222 void TrayNotifyDlg::SetIconMode(NOTIFYICONMODE mode)
1223 {
1224 int idx = TreeView_GetItemData(_tree_ctrl, _selectedItem);
1225
1226 if (!idx)
1227 return;
1228
1229 NotifyIconConfig& entry = _info[idx];
1230
1231 if (entry._mode != mode) {
1232 entry._mode = mode;
1233
1234 // trigger refresh in notify area and this dialog
1235 if (_pNotifyArea)
1236 SendMessage(*_pNotifyArea, PM_REFRESH, 0, 0);
1237 }
1238
1239 if (_pNotifyArea) {
1240 bool found = false;
1241
1242 NotifyIconCfgList& cfg = _pNotifyArea->_cfg;
1243 for(NotifyIconCfgList::iterator it=cfg.begin(); it!=cfg.end(); ++it) {
1244 NotifyIconConfig& cfg_entry = *it;
1245
1246 if (cfg_entry.match(entry)) {
1247 cfg_entry._mode = mode;
1248 ++found;
1249 break;
1250 }
1251 }
1252
1253 if (!found) {
1254 // insert new configuration entry
1255 NotifyIconConfig cfg_entry = entry;
1256
1257 cfg_entry._mode = mode;
1258
1259 _pNotifyArea->_cfg.push_back(cfg_entry);
1260 }
1261 }
1262
1263 Refresh();
1264 ///@todo select treeview item at new position in tree view -> refresh HTREEITEM in _selectedItem
1265 }
1266
1267
1268 ClockWindow::ClockWindow(HWND hwnd)
1269 : super(hwnd),
1270 _tooltip(hwnd)
1271 {
1272 *_time = TEXT('\0');
1273 FormatTime();
1274
1275 _tooltip.add(_hwnd, _hwnd);
1276 }
1277
1278 HWND ClockWindow::Create(HWND hwndParent)
1279 {
1280 static BtnWindowClass wcClock(CLASSNAME_CLOCKWINDOW, CS_DBLCLKS);
1281
1282 ClientRect clnt(hwndParent);
1283
1284 WindowCanvas canvas(hwndParent);
1285 FontSelection font(canvas, GetStockFont(DEFAULT_GUI_FONT));
1286
1287 RECT rect = {0, 0, 0, 0};
1288 TCHAR buffer[8];
1289
1290 if (!GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL, NULL, buffer, sizeof(buffer)/sizeof(TCHAR)))
1291 _tcscpy(buffer, TEXT("00:00"));
1292
1293 DrawText(canvas, buffer, -1, &rect, DT_SINGLELINE|DT_NOPREFIX|DT_CALCRECT);
1294 int clockwindowWidth = rect.right-rect.left + 4;
1295
1296 return Window::Create(WINDOW_CREATOR(ClockWindow), 0,
1297 wcClock, NULL, WS_CHILD|WS_VISIBLE,
1298 clnt.right-(clockwindowWidth), 1, clockwindowWidth, clnt.bottom-2, hwndParent);
1299 }
1300
1301 LRESULT ClockWindow::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam)
1302 {
1303 switch(nmsg) {
1304 case WM_PAINT:
1305 Paint();
1306 break;
1307
1308 case WM_LBUTTONDBLCLK:
1309 launch_cpanel(_hwnd, TEXT("timedate.cpl"));
1310 break;
1311
1312 default:
1313 return super::WndProc(nmsg, wparam, lparam);
1314 }
1315
1316 return 0;
1317 }
1318
1319 int ClockWindow::Notify(int id, NMHDR* pnmh)
1320 {
1321 if (pnmh->code == TTN_GETDISPINFO) {
1322 LPNMTTDISPINFO pdi = (LPNMTTDISPINFO)pnmh;
1323
1324 SYSTEMTIME systime;
1325 TCHAR buffer[64];
1326
1327 GetLocalTime(&systime);
1328
1329 if (GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, &systime, NULL, buffer, 64))
1330 _tcscpy(pdi->szText, buffer);
1331 else
1332 pdi->szText[0] = '\0';
1333 }
1334
1335 return 0;
1336 }
1337
1338 void ClockWindow::TimerTick()
1339 {
1340 if (FormatTime())
1341 InvalidateRect(_hwnd, NULL, TRUE); // refresh displayed time
1342 }
1343
1344 bool ClockWindow::FormatTime()
1345 {
1346 TCHAR buffer[16];
1347
1348 if (GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL, NULL, buffer, sizeof(buffer)/sizeof(TCHAR)))
1349 if (_tcscmp(buffer, _time)) {
1350 _tcscpy(_time, buffer);
1351 return true; // The text to display has changed.
1352 }
1353
1354 return false; // no change
1355 }
1356
1357 void ClockWindow::Paint()
1358 {
1359 PaintCanvas canvas(_hwnd);
1360
1361 FillRect(canvas, &canvas.rcPaint, GetSysColorBrush(COLOR_BTNFACE));
1362
1363 BkMode bkmode(canvas, TRANSPARENT);
1364 FontSelection font(canvas, GetStockFont(ANSI_VAR_FONT));
1365
1366 DrawText(canvas, _time, -1, ClientRect(_hwnd), DT_SINGLELINE|DT_VCENTER|DT_NOPREFIX);
1367 }