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