2 * Copyright 2003, 2004, 2005 Martin Fuchs
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.
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.
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
25 // Martin Fuchs, 22.08.2003
31 #include "../resource.h"
33 #include "traynotify.h"
36 #include "../notifyhook/notifyhook.h"
38 NotifyHook::NotifyHook()
39 : WM_GETMODULEPATH(InstallNotifyHook())
43 NotifyHook::~NotifyHook()
45 DeinstallNotifyHook();
48 void NotifyHook::GetModulePath(HWND hwnd
, HWND hwndCallback
)
50 PostMessage(hwnd
, WM_GETMODULEPATH
, (WPARAM
)hwndCallback
, 0);
53 bool NotifyHook::ModulePathCopyData(LPARAM lparam
, HWND
* phwnd
, String
& path
)
55 char buffer
[MAX_PATH
];
57 int l
= GetWindowModulePathCopyData(lparam
, phwnd
, buffer
, MAX_PATH
);
60 path
.assign(buffer
, l
);
67 NotifyIconIndex::NotifyIconIndex(NOTIFYICONDATA
* pnid
)
72 // special handling for windows task manager
77 NotifyIconIndex::NotifyIconIndex()
84 NotifyInfo::NotifyInfo()
89 _uCallbackMessage
= 0;
93 _lastChange
= GetTickCount();
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
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))
107 bool NotifyInfo::modify(NOTIFYICONDATA
* pnid
)
109 bool changes
= false;
111 if (_hWnd
!=pnid
->hWnd
|| _uID
!=pnid
->uID
) {
118 if (pnid
->uFlags
& NIF_MESSAGE
) {
119 if (_uCallbackMessage
!= pnid
->uCallbackMessage
) {
120 _uCallbackMessage
= pnid
->uCallbackMessage
;
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.
131 _hIcon
= (HICON
) CopyImage(pnid
->hIcon
, IMAGE_ICON
, NOTIFYICON_SIZE
, NOTIFYICON_SIZE
, 0);
133 changes
= true; ///@todo compare icon
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
);
140 if (_dwState
!= new_state
) {
141 _dwState
= new_state
;
147 // store tool tip text
148 if (pnid
->uFlags
& NIF_TIP
) {
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;
156 // get tooltip string length
158 for(; l
<max_len
; ++l
)
162 new_text
.assign(txt
, l
);
164 if (new_text
!= _tipText
) {
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;
173 for(int l
=0; l
<max_len
; ++l
)
177 new_text
.assign(txt
, l
);
179 if (new_text
!= _tipText
) {
186 TCHAR title
[MAX_PATH
];
189 GetWindowThreadProcessId(_hWnd
, &pid
);
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
;
202 _lastChange
= GetTickCount();
209 NotifyArea::NotifyArea(HWND hwnd
)
215 _last_icon_count
= 0;
216 _show_hidden
= false;
217 _hide_inactive
= true;
221 NotifyArea::~NotifyArea()
228 static bool get_hide_clock_from_registry()
230 HKEY hkeyStuckRects
= 0;
232 DWORD len
= sizeof(buffer
);
234 bool hide_clock
= false;
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;
243 RegCloseKey(hkeyStuckRects
);
248 void NotifyArea::read_config()
250 bool clock_visible
= true;
252 // read notification icon settings from XML configuration
253 XMLPos cfg_pos
= g_Globals
.get_cfg();
255 #ifndef __MINGW32__ // SHRestricted() missing in MinGW (as of 29.10.2003)
256 if (!g_Globals
._SHRestricted
|| !SHRestricted(REST_HIDECLOCK
))
259 if (cfg_pos
.go_down("desktopbar")) {
260 clock_visible
= XMLBoolRef(XMLPos(cfg_pos
,"options"), "show-clock", !get_hide_clock_from_registry());
265 if (cfg_pos
.go_down("notify-icons")) {
266 XMLPos
options(cfg_pos
, "options");
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);
272 XMLChildrenFilter
icons(cfg_pos
, "icon");
274 for(XMLChildrenFilter::iterator it
=icons
.begin(); it
!=icons
.end(); ++it
) {
275 const XMLNode
& node
= **it
;
277 NotifyIconConfig cfg
;
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");
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
;
298 show_clock(clock_visible
);
301 void NotifyArea::write_config()
303 // write notification icon settings to XML configuration file
304 XMLPos cfg_pos
= g_Globals
.get_cfg();
306 cfg_pos
.smart_create("desktopbar");
307 XMLBoolRef
boolRef(XMLPos(cfg_pos
,"options"), "show-clock");
308 boolRef
= _hwndClock
!=0;
311 cfg_pos
.smart_create("notify-icons");
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
;
318 for(NotifyIconCfgList::iterator it
=_cfg
.begin(); it
!=_cfg
.end(); ++it
) {
319 NotifyIconConfig
& cfg
= *it
;
321 // search for the corresponding node using the original name
322 cfg_pos
.smart_create("icon", "name", cfg
._name
);
324 // refresh unique name
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();
336 cfg_pos
.back(); // smart_create
339 void NotifyArea::show_clock(bool flag
)
341 bool vis
= _hwndClock
!=0;
345 // create clock window
346 _hwndClock
= ClockWindow::Create(_hwnd
);
349 ClientRect
clock_size(_hwndClock
);
350 _clock_width
= clock_size
.right
;
353 DestroyWindow(_hwndClock
);
358 SendMessage(GetParent(_hwnd
), PM_RESIZE_CHILDREN
, 0, 0);
362 LRESULT
NotifyArea::Init(LPCREATESTRUCT pcs
)
364 if (super::Init(pcs
))
369 SetTimer(_hwnd
, 0, 1000, NULL
);
374 HWND
NotifyArea::Create(HWND hwndParent
)
376 static BtnWindowClass
wcTrayNotify(CLASSNAME_TRAYNOTIFY
, CS_DBLCLKS
);
378 ClientRect
clnt(hwndParent
);
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
);
385 LRESULT
NotifyArea::WndProc(UINT nmsg
, WPARAM wparam
, LPARAM lparam
)
395 ClockWindow
* clock_window
= GET_WINDOW(ClockWindow
, _hwndClock
);
398 clock_window
->TimerTick();
406 int cx
= LOWORD(lparam
);
407 SetWindowPos(_hwndClock
, 0, cx
-_clock_width
, 0, 0, 0, SWP_NOSIZE
|SWP_NOZORDER
|SWP_NOACTIVATE
);
411 int w
= _sorted_icons
.size()*NOTIFYICON_DIST
+ NOTIFYAREA_SPACE
+ _clock_width
;
413 w
+= NOTIFYICON_DIST
;
416 case PM_REFRESH_CONFIG
:
420 case WM_CONTEXTMENU
: {
422 ScreenToClient(_hwnd
, &pt
);
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
));
433 case WM_COPYDATA
: { // receive NotifyHook answers
437 if (_hook
.ModulePathCopyData(lparam
, &hwnd
, path
))
438 _window_modules
[hwnd
] = path
;
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
454 NotifyIconSet::const_iterator found
= IconHitTest(pt
);
456 if (found
!= _sorted_icons
.end()) {
457 const NotifyInfo
& entry
= const_cast<NotifyInfo
&>(*found
); // Why does GCC 3.3 need this additional const_cast ?!
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
||
465 nmsg
== WM_RBUTTONDOWN
) {
466 _icon_map
[entry
]._lastChange
= GetTickCount();
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
||
477 nmsg
== WM_RBUTTONDOWN
)
478 PostMessage(entry
._hWnd
, entry
._uCallbackMessage
, entry
._uID
, nmsg
);
480 // allow SetForegroundWindow() in client process
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");
487 if (AllowSetForegroundWindow
)
488 (*AllowSetForegroundWindow
)(pid
);
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
);
495 SendMessage(entry
._hWnd
, entry
._uCallbackMessage
, entry
._uID
, nmsg
);
498 else if (_icon_map
.erase(entry
)) // delete icons without valid owner window
501 // handle clicks on notification area button "show hidden icons"
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);
509 return super::WndProc(nmsg
, wparam
, lparam
);
515 int NotifyArea::Command(int id
, int code
)
518 case ID_SHOW_HIDDEN_ICONS
:
519 _show_hidden
= !_show_hidden
;
523 case ID_SHOW_ICON_BUTTON
:
524 _show_button
= !_show_button
;
528 case ID_CONFIG_NOTIFYAREA
:
529 Dialog::DoModal(IDD_NOTIFYAREA
, WINDOW_CREATOR(TrayNotifyDlg
), GetParent(_hwnd
));
533 launch_cpanel(_hwnd
, TEXT("timedate.cpl"));
537 SendParent(WM_COMMAND
, MAKELONG(id
,code
), 0);
543 int NotifyArea::Notify(int id
, NMHDR
* pnmh
)
545 if (pnmh
->code
== TTN_GETDISPINFO
) {
546 LPNMTTDISPINFO pdi
= (LPNMTTDISPINFO
)pnmh
;
548 Point
pt(GetMessagePos());
549 ScreenToClient(_hwnd
, &pt
);
552 pt
.x
>=NOTIFYICON_X
&& pt
.x
<NOTIFYICON_X
+NOTIFYICON_SIZE
&&
553 pt
.y
>=NOTIFYICON_Y
&& pt
.y
<NOTIFYICON_Y
+NOTIFYICON_SIZE
)
555 static ResString
sShowIcons(IDS_SHOW_HIDDEN_ICONS
);
556 static ResString
sHideIcons(IDS_HIDE_ICONS
);
558 pdi
->lpszText
= (_show_hidden
? sHideIcons
: sShowIcons
).str();
560 NotifyIconSet::iterator found
= IconHitTest(pt
);
562 if (found
!= _sorted_icons
.end()) {
563 NotifyInfo
& entry
= const_cast<NotifyInfo
&>(*found
); // Why does GCC 3.3 need this additional const_cast ?!
565 // enable multiline tooltips (break at CR/LF and for very long one-line strings)
566 SendMessage(pnmh
->hwndFrom
, TTM_SETMAXTIPWIDTH
, 0, 400);
568 pdi
->lpszText
= entry
._tipText
.str();
576 void NotifyArea::CancelModes()
578 PostMessage(HWND_BROADCAST
, WM_CANCELMODE
, 0, 0);
580 for(NotifyIconSet::const_iterator it
=_sorted_icons
.begin(); it
!=_sorted_icons
.end(); ++it
)
581 PostMessage(it
->_hWnd
, WM_CANCELMODE
, 0, 0);
584 LRESULT
NotifyArea::ProcessTrayNotification(int notify_code
, NOTIFYICONDATA
* pnid
)
586 switch(notify_code
) {
589 if ((int)pnid
->uID
>= 0) { ///@todo This is a fix for Windows Task Manager.
590 NotifyInfo
& entry
= _icon_map
[pnid
];
593 if (entry
._idx
== -1)
594 entry
._idx
= ++_next_idx
;
595 /* equivalent code using iterator::find();
596 NotifyIconMap::iterator found = _icon_map.find(pnid);
599 if (found == _icon_map.end()) {
600 pentry = &_icon_map[pnid];
601 pentry->_idx = ++_next_idx;
603 pentry = &found->second;
605 NotifyInfo& entry = *pentry;
607 bool changes
= entry
.modify(pnid
);
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
;
617 UpdateIcons(); ///@todo call only if really changes occurred
624 NotifyIconMap::iterator found
= _icon_map
.find(pnid
);
626 if (found
!= _icon_map
.end()) {
627 if (found
->second
._hIcon
)
628 DestroyIcon(found
->second
._hIcon
);
629 _icon_map
.erase(found
);
635 #if NOTIFYICON_VERSION>=3 // as of 21.08.2003 missing in MinGW headers
637 SetForegroundWindow(_hwnd
);
641 NotifyIconMap::iterator found
= _icon_map
.find(pnid
);
643 if (found
!= _icon_map
.end()) {
644 found
->second
._version
= pnid
->UNION_MEMBER(uVersion
);
654 void NotifyArea::UpdateIcons()
656 _sorted_icons
.clear();
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
;
662 #ifdef NIF_STATE // as of 21.08.2003 missing in MinGW headers
663 if (_show_hidden
|| !(entry
._dwState
& NIS_HIDDEN
))
665 _sorted_icons
.insert(entry
);
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
};
675 _tooltip
.add(_hwnd
, tt_idx
++, rect
);
677 rect
.left
+= NOTIFYICON_DIST
;
678 rect
.right
+= NOTIFYICON_DIST
;
681 size_t icon_cnt
= _sorted_icons
.size();
682 while(tt_idx
< icon_cnt
) {
683 _tooltip
.add(_hwnd
, tt_idx
++, rect
);
685 rect
.left
+= NOTIFYICON_DIST
;
686 rect
.right
+= NOTIFYICON_DIST
;
689 while(tt_idx
< _last_icon_count
)
690 _tooltip
.remove(_hwnd
, tt_idx
++);
692 _last_icon_count
= _sorted_icons
.size();
695 SendMessage(GetParent(_hwnd
), PM_RESIZE_CHILDREN
, 0, 0);
697 InvalidateRect(_hwnd
, NULL
, FALSE
); // refresh icon display
701 #ifndef _NO_ALPHABLEND
703 #pragma comment(lib, "msimg32") // for AlphaBlend()
707 void NotifyArea::Paint()
709 BufferedPaintCanvas
canvas(_hwnd
);
711 // first fill with the background color
712 FillRect(canvas
, &canvas
.rcPaint
, GetSysColorBrush(COLOR_BTNFACE
));
715 int x
= NOTIFYICON_X
;
716 int y
= NOTIFYICON_Y
;
719 static SmallIcon
leftArrowIcon(IDI_NOTIFY_L
);
720 static SmallIcon
rightArrowIcon(IDI_NOTIFY_R
);
722 DrawIconEx(canvas
, x
, y
, _show_hidden
?rightArrowIcon
:leftArrowIcon
, NOTIFYICON_SIZE
, NOTIFYICON_SIZE
, 0, 0, DI_NORMAL
);
723 x
+= NOTIFYICON_DIST
;
726 #ifndef _NO_ALPHABLEND
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
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
);
741 DrawIconEx(canvas
, x
, y
, it
->_hIcon
, NOTIFYICON_SIZE
, NOTIFYICON_SIZE
, 0, 0, DI_NORMAL
);
743 x
+= NOTIFYICON_DIST
;
747 void NotifyArea::Refresh(bool update
)
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
;
754 if (!IsWindow(entry
._hWnd
))
755 if (_icon_map
.erase(entry
)) // delete icons without valid owner window
759 DWORD now
= GetTickCount();
761 // handle icon hiding
762 for(NotifyIconMap::iterator it
=_icon_map
.begin(); it
!=_icon_map
.end(); ++it
) {
763 NotifyInfo
& entry
= it
->second
;
765 DetermineHideState(entry
);
767 switch(entry
._mode
) {
769 if (!(entry
._dwState
& NIS_HIDDEN
)) {
770 entry
._dwState
|= NIS_HIDDEN
;
776 if (entry
._dwState
&NIS_HIDDEN
) {
777 entry
._dwState
&= ~NIS_HIDDEN
;
783 // automatically hide icons after long periods of inactivity
785 if (!(entry
._dwState
& NIS_HIDDEN
))
786 if (now
-entry
._lastChange
> ICON_AUTOHIDE_SECONDS
*1000) {
787 entry
._dwState
|= NIS_HIDDEN
;
798 /// search for a icon at a given client coordinate position
799 NotifyIconSet::iterator
NotifyArea::IconHitTest(const POINT
& pos
)
801 if (pos
.y
<NOTIFYICON_Y
|| pos
.y
>=NOTIFYICON_Y
+NOTIFYICON_SIZE
)
802 return _sorted_icons
.end();
804 NotifyIconSet::iterator it
= _sorted_icons
.begin();
806 int x
= NOTIFYICON_X
;
809 x
+= NOTIFYICON_DIST
;
811 for(; it
!=_sorted_icons
.end(); ++it
) {
812 //NotifyInfo& entry = const_cast<NotifyInfo&>(*it); // Why does GCC 3.3 need this additional const_cast ?!
814 if (pos
.x
>=x
&& pos
.x
<x
+NOTIFYICON_SIZE
)
817 x
+= NOTIFYICON_DIST
;
824 void NotifyIconConfig::create_name()
826 _name
= FmtString(TEXT("'%s' - '%s' - '%s'"), _tipText
.c_str(), _windowTitle
.c_str(), _modulePath
.c_str());
830 #if NOTIFYICON_VERSION>=3 // as of 21.08.2003 missing in MinGW headers
832 bool NotifyIconConfig::match(const NotifyIconConfig
& props
) const
834 if (!_tipText
.empty() && !props
._tipText
.empty())
835 if (props
._tipText
== _tipText
)
838 if (!_windowTitle
.empty() && !props
._windowTitle
.empty())
839 if (_tcsstr(props
._windowTitle
, _windowTitle
))
842 if (!_modulePath
.empty() && !props
._modulePath
.empty())
843 if (!_tcsicmp(props
._modulePath
, _modulePath
))
849 bool NotifyArea::DetermineHideState(NotifyInfo
& entry
)
851 if (entry
._modulePath
.empty()) {
852 const String
& modulePath
= _window_modules
[entry
._hWnd
];
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
;
858 _hook
.GetModulePath(entry
._hWnd
, _hwnd
);
861 for(NotifyIconCfgList::const_iterator it
=_cfg
.begin(); it
!=_cfg
.end(); ++it
) {
862 const NotifyIconConfig
& cfg
= *it
;
864 if (cfg
.match(entry
)) {
865 entry
._mode
= cfg
._mode
;
876 String
string_from_mode(NOTIFYICONMODE mode
)
880 return ResString(IDS_NOTIFY_SHOW
);
883 return ResString(IDS_NOTIFY_HIDE
);
885 default: //case NIM_AUTO
886 return ResString(IDS_NOTIFY_AUTOHIDE
);
891 TrayNotifyDlg::TrayNotifyDlg(HWND 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))))
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
);
904 _cfg_org
= _pNotifyArea
->_cfg
;
905 _show_hidden_org
= _pNotifyArea
->_show_hidden
;
908 SetWindowIcon(hwnd
, IDI_REACTOS
);
910 _haccel
= LoadAccelerators(g_Globals
._hInstance
, MAKEINTRESOURCE(IDA_TRAYNOTIFY
));
913 WindowCanvas
canvas(_hwnd
);
914 HBRUSH hbkgnd
= GetStockBrush(WHITE_BRUSH
);
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
);
921 (void)TreeView_SetImageList(_tree_ctrl
, _himl
, TVSIL_NORMAL
);
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
);
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
);
936 _resize_mgr
.Add(IDC_PICTURE
, MOVE
);
937 _resize_mgr
.Add(ID_SHOW_HIDDEN_ICONS
,MOVE_Y
);
939 _resize_mgr
.Add(IDC_LABEL6
, MOVE_Y
);
940 _resize_mgr
.Add(IDC_LAST_CHANGE
, MOVE_Y
);
942 _resize_mgr
.Add(IDOK
, MOVE
);
943 _resize_mgr
.Add(IDCANCEL
, MOVE
);
945 _resize_mgr
.Resize(+150, +200);
949 SetTimer(_hwnd
, 0, 3000, NULL
);
950 register_pretranslate(hwnd
);
953 TrayNotifyDlg::~TrayNotifyDlg()
956 unregister_pretranslate(_hwnd
);
957 ImageList_Destroy(_himl
);
960 void TrayNotifyDlg::Refresh()
962 ///@todo refresh incrementally
964 HiddenWindow
hide(_tree_ctrl
);
966 TreeView_DeleteAllItems(_tree_ctrl
);
971 tvi
.hInsertAfter
= TVI_LAST
;
973 TV_ITEM
& tv
= tvi
.item
;
974 tv
.mask
= TVIF_TEXT
|TVIF_IMAGE
|TVIF_SELECTEDIMAGE
;
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
);
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
);
986 tvi
.hParent
= _hitemCurrent
;
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
);
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
);
1001 tv
.mask
|= TVIF_PARAM
;
1003 WindowCanvas
canvas(_hwnd
);
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
;
1009 InsertItem(entry
._dwState
&NIS_HIDDEN
? _hitemCurrent_hidden
: _hitemCurrent_visible
, TVI_LAST
, entry
, canvas
);
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
;
1019 if (!cfg_entry
._modulePath
.empty()) {
1020 if ((int)ExtractIconEx(cfg_entry
._modulePath
, 0, NULL
, &hicon
, 1) <= 0)
1026 if (SHGetFileInfo(cfg_entry
._modulePath
, 0, &sfi
, sizeof(sfi
), SHGFI_ICON
|SHGFI_SMALLICON
))
1031 InsertItem(_hitemConfig
, TVI_SORT
, cfg_entry
, canvas
, hicon
, cfg_entry
._mode
);
1037 CheckDlgButton(_hwnd
, ID_SHOW_HIDDEN_ICONS
, _pNotifyArea
->_show_hidden
? BST_CHECKED
: BST_UNCHECKED
);
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
);
1045 TreeView_EnsureVisible(_tree_ctrl
, _hitemCurrent_visible
);
1048 void TrayNotifyDlg::InsertItem(HTREEITEM hparent
, HTREEITEM after
, const NotifyInfo
& entry
, HDC hdc
)
1050 InsertItem(hparent
, after
, entry
, hdc
, entry
._hIcon
, entry
._mode
);
1053 void TrayNotifyDlg::InsertItem(HTREEITEM hparent
, HTREEITEM after
, const NotifyIconDlgInfo
& entry
,
1054 HDC hdc
, HICON hicon
, NOTIFYICONMODE mode
)
1056 int idx
= _info
.size() + 1;
1059 String mode_str
= string_from_mode(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
);
1067 FmtString
txt(TEXT("%s - %s [%s]"), entry
._tipText
.c_str(), entry
._windowTitle
.c_str(), mode_str
.c_str());
1069 TV_INSERTSTRUCT tvi
;
1071 tvi
.hParent
= hparent
;
1072 tvi
.hInsertAfter
= after
;
1074 TV_ITEM
& tv
= tvi
.item
;
1075 tv
.mask
= TVIF_TEXT
|TVIF_IMAGE
|TVIF_SELECTEDIMAGE
|TVIF_PARAM
;
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
);
1083 LRESULT
TrayNotifyDlg::WndProc(UINT nmsg
, WPARAM wparam
, LPARAM lparam
)
1086 case PM_TRANSLATE_MSG
: {
1087 MSG
* pmsg
= (MSG
*) lparam
;
1089 if (TranslateAccelerator(_hwnd
, _haccel
, pmsg
))
1099 return super::WndProc(nmsg
, wparam
, lparam
);
1105 int TrayNotifyDlg::Command(int id
, int code
)
1107 if (code
== BN_CLICKED
) {
1113 case IDC_NOTIFY_SHOW
:
1114 SetIconMode(NIM_SHOW
);
1117 case IDC_NOTIFY_HIDE
:
1118 SetIconMode(NIM_HIDE
);
1121 case IDC_NOTIFY_AUTOHIDE
:
1122 SetIconMode(NIM_AUTO
);
1125 case ID_SHOW_HIDDEN_ICONS
:
1127 SendMessage(*_pNotifyArea
, WM_COMMAND
, MAKEWPARAM(id
,code
), 0);
1131 EndDialog(_hwnd
, id
);
1137 // restore original icon states and configuration data
1138 _pNotifyArea
->_cfg
= _cfg_org
;
1139 _pNotifyArea
->_show_hidden
= _show_hidden_org
;
1141 for(IconStateMap::const_iterator it
=_icon_states_org
.begin(); it
!=_icon_states_org
.end(); ++it
) {
1142 NotifyInfo
& info
= _pNotifyArea
->_icon_map
[it
->first
];
1144 info
._mode
= it
->second
.first
;
1145 info
._dwState
= it
->second
.second
;
1148 SendMessage(*_pNotifyArea
, PM_REFRESH
, 0, 0);
1151 EndDialog(_hwnd
, id
);
1161 int TrayNotifyDlg::Notify(int id
, NMHDR
* pnmh
)
1163 switch(pnmh
->code
) {
1164 case TVN_SELCHANGED
: {
1165 NMTREEVIEW
* pnmtv
= (NMTREEVIEW
*)pnmh
;
1166 int idx
= pnmtv
->itemNew
.lParam
;
1169 RefreshProperties(_info
[idx
]);
1170 _selectedItem
= pnmtv
->itemNew
.hItem
;
1173 SetDlgItemText(_hwnd, IDC_NOTIFY_TOOLTIP, NULL);
1174 SetDlgItemText(_hwnd, IDC_NOTIFY_TITLE, NULL);
1175 SetDlgItemText(_hwnd, IDC_NOTIFY_MODULE, NULL);
1177 CheckRadioButton(_hwnd
, IDC_NOTIFY_SHOW
, IDC_NOTIFY_AUTOHIDE
, 0);
1185 void TrayNotifyDlg::RefreshProperties(const NotifyIconDlgInfo
& entry
)
1187 SetDlgItemText(_hwnd
, IDC_NOTIFY_TOOLTIP
, entry
._tipText
);
1188 SetDlgItemText(_hwnd
, IDC_NOTIFY_TITLE
, entry
._windowTitle
);
1189 SetDlgItemText(_hwnd
, IDC_NOTIFY_MODULE
, entry
._modulePath
);
1191 CheckRadioButton(_hwnd
, IDC_NOTIFY_SHOW
, IDC_NOTIFY_AUTOHIDE
, IDC_NOTIFY_SHOW
+entry
._mode
);
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
);
1198 HICON hicon
= 0; //get_window_icon_big(entry._hWnd, false);
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);
1207 if (SHGetFileInfo(entry
._modulePath
, 0, &sfi
, sizeof(sfi
), SHGFI_ICON
|SHGFI_LARGEICON
))
1213 SendMessage(GetDlgItem(_hwnd
, IDC_PICTURE
), STM_SETICON
, (LPARAM
)hicon
, 0);
1216 SendMessage(GetDlgItem(_hwnd
, IDC_PICTURE
), STM_SETICON
, 0, 0);
1219 void TrayNotifyDlg::SetIconMode(NOTIFYICONMODE mode
)
1221 int idx
= TreeView_GetItemData(_tree_ctrl
, _selectedItem
);
1226 NotifyIconConfig
& entry
= _info
[idx
];
1228 if (entry
._mode
!= mode
) {
1231 // trigger refresh in notify area and this dialog
1233 SendMessage(*_pNotifyArea
, PM_REFRESH
, 0, 0);
1239 NotifyIconCfgList
& cfg
= _pNotifyArea
->_cfg
;
1240 for(NotifyIconCfgList::iterator it
=cfg
.begin(); it
!=cfg
.end(); ++it
) {
1241 NotifyIconConfig
& cfg_entry
= *it
;
1243 if (cfg_entry
.match(entry
)) {
1244 cfg_entry
._mode
= mode
;
1251 // insert new configuration entry
1252 NotifyIconConfig cfg_entry
= entry
;
1254 cfg_entry
._mode
= mode
;
1256 _pNotifyArea
->_cfg
.push_back(cfg_entry
);
1261 ///@todo select treeview item at new position in tree view -> refresh HTREEITEM in _selectedItem
1265 ClockWindow::ClockWindow(HWND hwnd
)
1269 *_time
= TEXT('\0');
1272 _tooltip
.add(_hwnd
, _hwnd
);
1275 HWND
ClockWindow::Create(HWND hwndParent
)
1277 static BtnWindowClass
wcClock(CLASSNAME_CLOCKWINDOW
, CS_DBLCLKS
);
1279 ClientRect
clnt(hwndParent
);
1281 WindowCanvas
canvas(hwndParent
);
1282 FontSelection
font(canvas
, GetStockFont(DEFAULT_GUI_FONT
));
1284 RECT rect
= {0, 0, 0, 0};
1287 if (!GetTimeFormat(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, NULL
, NULL
, buffer
, sizeof(buffer
)/sizeof(TCHAR
)))
1288 _tcscpy(buffer
, TEXT("00:00"));
1290 DrawText(canvas
, buffer
, -1, &rect
, DT_SINGLELINE
|DT_NOPREFIX
|DT_CALCRECT
);
1291 int clockwindowWidth
= rect
.right
-rect
.left
+ 4;
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
);
1298 LRESULT
ClockWindow::WndProc(UINT nmsg
, WPARAM wparam
, LPARAM lparam
)
1305 case WM_LBUTTONDBLCLK
:
1306 launch_cpanel(_hwnd
, TEXT("timedate.cpl"));
1310 return super::WndProc(nmsg
, wparam
, lparam
);
1316 int ClockWindow::Notify(int id
, NMHDR
* pnmh
)
1318 if (pnmh
->code
== TTN_GETDISPINFO
) {
1319 LPNMTTDISPINFO pdi
= (LPNMTTDISPINFO
)pnmh
;
1324 GetLocalTime(&systime
);
1326 if (GetDateFormat(LOCALE_USER_DEFAULT
, DATE_LONGDATE
, &systime
, NULL
, buffer
, 64))
1327 _tcscpy(pdi
->szText
, buffer
);
1329 pdi
->szText
[0] = '\0';
1335 void ClockWindow::TimerTick()
1338 InvalidateRect(_hwnd
, NULL
, TRUE
); // refresh displayed time
1341 bool ClockWindow::FormatTime()
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.
1351 return false; // no change
1354 void ClockWindow::Paint()
1356 PaintCanvas
canvas(_hwnd
);
1358 FillRect(canvas
, &canvas
.rcPaint
, GetSysColorBrush(COLOR_BTNFACE
));
1360 BkMode
bkmode(canvas
, TRANSPARENT
);
1361 FontSelection
font(canvas
, GetStockFont(ANSI_VAR_FONT
));
1363 DrawText(canvas
, _time
, -1, ClientRect(_hwnd
), DT_SINGLELINE
|DT_VCENTER
|DT_NOPREFIX
);