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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
25 // Martin Fuchs, 22.08.2003
31 #include "traynotify.h"
33 #include "../notifyhook/notifyhook.h"
35 NotifyHook::NotifyHook()
36 : WM_GETMODULEPATH(InstallNotifyHook())
40 NotifyHook::~NotifyHook()
42 DeinstallNotifyHook();
45 void NotifyHook::GetModulePath(HWND hwnd
, HWND hwndCallback
)
47 PostMessage(hwnd
, WM_GETMODULEPATH
, (WPARAM
)hwndCallback
, 0);
50 bool NotifyHook::ModulePathCopyData(LPARAM lparam
, HWND
* phwnd
, String
& path
)
52 char buffer
[MAX_PATH
];
54 int l
= GetWindowModulePathCopyData(lparam
, phwnd
, buffer
, COUNTOF(buffer
));
57 path
.assign(buffer
, l
);
64 NotifyIconIndex::NotifyIconIndex(NOTIFYICONDATA
* pnid
)
69 // special handling for windows task manager
74 NotifyIconIndex::NotifyIconIndex()
81 NotifyInfo::NotifyInfo()
86 _uCallbackMessage
= 0;
90 _lastChange
= GetTickCount();
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
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))
104 bool NotifyInfo::modify(NOTIFYICONDATA
* pnid
)
106 bool changes
= false;
108 if (_hWnd
!=pnid
->hWnd
|| _uID
!=pnid
->uID
) {
115 if (pnid
->uFlags
& NIF_MESSAGE
) {
116 if (_uCallbackMessage
!= pnid
->uCallbackMessage
) {
117 _uCallbackMessage
= pnid
->uCallbackMessage
;
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.
128 _hIcon
= (HICON
) CopyImage(pnid
->hIcon
, IMAGE_ICON
, NOTIFYICON_SIZE
, NOTIFYICON_SIZE
, 0);
130 changes
= true; ///@todo compare icon
133 if (pnid
->uFlags
& NIF_STATE
) {
134 DWORD new_state
= (_dwState
&~pnid
->dwStateMask
) | (pnid
->dwState
&pnid
->dwStateMask
);
136 if (_dwState
!= new_state
) {
137 _dwState
= new_state
;
142 // store tool tip text
143 if (pnid
->uFlags
& NIF_TIP
) {
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;
151 // get tooltip string length
153 for(; l
<max_len
; ++l
)
157 new_text
.assign(txt
, l
);
159 if (new_text
!= _tipText
) {
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;
168 for(int l
=0; l
<max_len
; ++l
)
172 new_text
.assign(txt
, l
);
174 if (new_text
!= _tipText
) {
181 TCHAR title
[MAX_PATH
];
184 GetWindowThreadProcessId(_hWnd
, &pid
);
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
;
197 _lastChange
= GetTickCount();
204 NotifyArea::NotifyArea(HWND hwnd
)
210 _last_icon_count
= 0;
211 _show_hidden
= false;
212 _hide_inactive
= true;
216 NotifyArea::~NotifyArea()
223 static bool get_hide_clock_from_registry()
225 HKEY hkeyStuckRects
= 0;
227 DWORD len
= sizeof(buffer
);
229 bool hide_clock
= false;
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;
238 RegCloseKey(hkeyStuckRects
);
243 void NotifyArea::read_config()
245 bool clock_visible
= true;
247 // read notification icon settings from XML configuration
248 XMLPos cfg_pos
= g_Globals
.get_cfg();
250 if (!g_Globals
._SHRestricted
|| !SHRestricted(REST_HIDECLOCK
))
252 if (cfg_pos
.go_down("desktopbar")) {
253 clock_visible
= XMLBoolRef(XMLPos(cfg_pos
,"options"), "show-clock", !get_hide_clock_from_registry());
258 if (cfg_pos
.go_down("notify-icons")) {
259 XMLPos
options(cfg_pos
, "options");
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);
265 XMLChildrenFilter
icons(cfg_pos
, "icon");
267 for(XMLChildrenFilter::iterator it
=icons
.begin(); it
!=icons
.end(); ++it
) {
268 const XMLNode
& node
= **it
;
270 NotifyIconConfig cfg
;
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");
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
;
291 show_clock(clock_visible
);
294 void NotifyArea::write_config()
296 // write notification icon settings to XML configuration file
297 XMLPos cfg_pos
= g_Globals
.get_cfg();
299 cfg_pos
.smart_create("desktopbar");
300 XMLBoolRef
boolRef(XMLPos(cfg_pos
,"options"), "show-clock");
301 boolRef
= _hwndClock
!=0;
304 cfg_pos
.smart_create("notify-icons");
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
;
311 for(NotifyIconCfgList::iterator it
=_cfg
.begin(); it
!=_cfg
.end(); ++it
) {
312 NotifyIconConfig
& cfg
= *it
;
314 // search for the corresponding node using the original name
315 cfg_pos
.smart_create("icon", "name", cfg
._name
);
317 // refresh unique name
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();
329 cfg_pos
.back(); // smart_create
332 void NotifyArea::show_clock(bool flag
)
334 bool vis
= _hwndClock
!=0;
338 // create clock window
339 _hwndClock
= ClockWindow::Create(_hwnd
);
342 ClientRect
clock_size(_hwndClock
);
343 _clock_width
= clock_size
.right
;
346 DestroyWindow(_hwndClock
);
351 SendMessage(GetParent(_hwnd
), PM_RESIZE_CHILDREN
, 0, 0);
355 LRESULT
NotifyArea::Init(LPCREATESTRUCT pcs
)
357 if (super::Init(pcs
))
362 SetTimer(_hwnd
, 0, 1000, NULL
);
367 HWND
NotifyArea::Create(HWND hwndParent
)
369 static BtnWindowClass
wcTrayNotify(CLASSNAME_TRAYNOTIFY
, CS_DBLCLKS
);
371 ClientRect
clnt(hwndParent
);
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
);
378 LRESULT
NotifyArea::WndProc(UINT nmsg
, WPARAM wparam
, LPARAM lparam
)
388 ClockWindow
* clock_window
= GET_WINDOW(ClockWindow
, _hwndClock
);
391 clock_window
->TimerTick();
399 int cx
= LOWORD(lparam
);
400 SetWindowPos(_hwndClock
, 0, cx
-_clock_width
, 0, 0, 0, SWP_NOSIZE
|SWP_NOZORDER
|SWP_NOACTIVATE
);
404 int w
= _sorted_icons
.size()*NOTIFYICON_DIST
+ NOTIFYAREA_SPACE
+ _clock_width
;
406 w
+= NOTIFYICON_DIST
;
409 case PM_REFRESH_CONFIG
:
413 case WM_CONTEXTMENU
: {
418 ScreenToClient(_hwnd
, &pt
);
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
);
429 case WM_COPYDATA
: { // receive NotifyHook answers
433 if (_hook
.ModulePathCopyData(lparam
, &hwnd
, path
))
434 _window_modules
[hwnd
] = path
;
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
450 NotifyIconSet::const_iterator found
= IconHitTest(pt
);
452 if (found
!= _sorted_icons
.end()) {
453 const NotifyInfo
& entry
= const_cast<NotifyInfo
&>(*found
); // Why does GCC 3.3 need this additional const_cast ?!
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
||
461 nmsg
== WM_RBUTTONDOWN
) {
462 _icon_map
[entry
]._lastChange
= GetTickCount();
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
||
473 nmsg
== WM_RBUTTONDOWN
)
474 PostMessage(entry
._hWnd
, entry
._uCallbackMessage
, entry
._uID
, nmsg
);
476 // allow SetForegroundWindow() in client process
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");
483 if (AllowSetForegroundWindow
)
484 (*AllowSetForegroundWindow
)(pid
);
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
);
491 SendMessage(entry
._hWnd
, entry
._uCallbackMessage
, entry
._uID
, nmsg
);
494 else if (_icon_map
.erase(entry
)) // delete icons without valid owner window
497 // handle clicks on notification area button "show hidden icons"
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);
505 return super::WndProc(nmsg
, wparam
, lparam
);
511 int NotifyArea::Command(int id
, int code
)
514 case ID_SHOW_HIDDEN_ICONS
:
515 _show_hidden
= !_show_hidden
;
519 case ID_SHOW_ICON_BUTTON
:
520 _show_button
= !_show_button
;
524 case ID_CONFIG_NOTIFYAREA
:
525 Dialog::DoModal(IDD_NOTIFYAREA
, WINDOW_CREATOR(TrayNotifyDlg
), GetParent(_hwnd
));
529 launch_cpanel(_hwnd
, TEXT("timedate.cpl"));
533 SendParent(WM_COMMAND
, MAKELONG(id
,code
), 0);
539 int NotifyArea::Notify(int id
, NMHDR
* pnmh
)
541 if (pnmh
->code
== TTN_GETDISPINFO
) {
542 LPNMTTDISPINFO pdi
= (LPNMTTDISPINFO
)pnmh
;
544 Point
pt(GetMessagePos());
545 ScreenToClient(_hwnd
, &pt
);
548 pt
.x
>=NOTIFYICON_X
&& pt
.x
<NOTIFYICON_X
+NOTIFYICON_SIZE
&&
549 pt
.y
>=NOTIFYICON_Y
&& pt
.y
<NOTIFYICON_Y
+NOTIFYICON_SIZE
)
551 static ResString
sShowIcons(IDS_SHOW_HIDDEN_ICONS
);
552 static ResString
sHideIcons(IDS_HIDE_ICONS
);
554 pdi
->lpszText
= (_show_hidden
? sHideIcons
: sShowIcons
).str();
556 NotifyIconSet::iterator found
= IconHitTest(pt
);
558 if (found
!= _sorted_icons
.end()) {
559 NotifyInfo
& entry
= const_cast<NotifyInfo
&>(*found
); // Why does GCC 3.3 need this additional const_cast ?!
561 // enable multiline tooltips (break at CR/LF and for very long one-line strings)
562 SendMessage(pnmh
->hwndFrom
, TTM_SETMAXTIPWIDTH
, 0, 400);
564 pdi
->lpszText
= entry
._tipText
.str();
572 void NotifyArea::CancelModes()
574 PostMessage(HWND_BROADCAST
, WM_CANCELMODE
, 0, 0);
576 for(NotifyIconSet::const_iterator it
=_sorted_icons
.begin(); it
!=_sorted_icons
.end(); ++it
)
577 PostMessage(it
->_hWnd
, WM_CANCELMODE
, 0, 0);
580 LRESULT
NotifyArea::ProcessTrayNotification(int notify_code
, NOTIFYICONDATA
* pnid
)
582 switch(notify_code
) {
585 if ((int)pnid
->uID
>= 0) { ///@todo This is a fix for Windows Task Manager.
586 NotifyInfo
& entry
= _icon_map
[pnid
];
589 if (entry
._idx
== -1)
590 entry
._idx
= ++_next_idx
;
591 /* equivalent code using iterator::find();
592 NotifyIconMap::iterator found = _icon_map.find(pnid);
595 if (found == _icon_map.end()) {
596 pentry = &_icon_map[pnid];
597 pentry->_idx = ++_next_idx;
599 pentry = &found->second;
601 NotifyInfo& entry = *pentry;
603 bool changes
= entry
.modify(pnid
);
605 if (DetermineHideState(entry
) && entry
._mode
==NIM_HIDE
) {
606 entry
._dwState
|= NIS_HIDDEN
;
611 UpdateIcons(); ///@todo call only if really changes occurred
618 NotifyIconMap::iterator found
= _icon_map
.find(pnid
);
620 if (found
!= _icon_map
.end()) {
621 if (found
->second
._hIcon
)
622 DestroyIcon(found
->second
._hIcon
);
623 _icon_map
.erase(found
);
630 SetForegroundWindow(_hwnd
);
634 NotifyIconMap::iterator found
= _icon_map
.find(pnid
);
636 if (found
!= _icon_map
.end()) {
637 found
->second
._version
= pnid
->UNION_MEMBER(uVersion
);
646 void NotifyArea::UpdateIcons()
648 _sorted_icons
.clear();
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
;
654 if (_show_hidden
|| !(entry
._dwState
& NIS_HIDDEN
))
655 _sorted_icons
.insert(entry
);
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
};
665 _tooltip
.add(_hwnd
, tt_idx
++, rect
);
667 rect
.left
+= NOTIFYICON_DIST
;
668 rect
.right
+= NOTIFYICON_DIST
;
671 size_t icon_cnt
= _sorted_icons
.size();
672 while(tt_idx
< icon_cnt
) {
673 _tooltip
.add(_hwnd
, tt_idx
++, rect
);
675 rect
.left
+= NOTIFYICON_DIST
;
676 rect
.right
+= NOTIFYICON_DIST
;
679 while(tt_idx
< _last_icon_count
)
680 _tooltip
.remove(_hwnd
, tt_idx
++);
682 _last_icon_count
= _sorted_icons
.size();
685 SendMessage(GetParent(_hwnd
), PM_RESIZE_CHILDREN
, 0, 0);
687 InvalidateRect(_hwnd
, NULL
, FALSE
); // refresh icon display
691 #ifndef _NO_ALPHABLEND
693 #pragma comment(lib, "msimg32") // for AlphaBlend()
697 void NotifyArea::Paint()
699 BufferedPaintCanvas
canvas(_hwnd
);
701 // first fill with the background color
702 FillRect(canvas
, &canvas
.rcPaint
, GetSysColorBrush(COLOR_BTNFACE
));
705 int x
= NOTIFYICON_X
;
706 int y
= NOTIFYICON_Y
;
709 static SmallIcon
leftArrowIcon(IDI_NOTIFY_L
);
710 static SmallIcon
rightArrowIcon(IDI_NOTIFY_R
);
712 DrawIconEx(canvas
, x
, y
, _show_hidden
?rightArrowIcon
:leftArrowIcon
, NOTIFYICON_SIZE
, NOTIFYICON_SIZE
, 0, 0, DI_NORMAL
);
713 x
+= NOTIFYICON_DIST
;
716 #ifndef _NO_ALPHABLEND
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
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
);
731 DrawIconEx(canvas
, x
, y
, it
->_hIcon
, NOTIFYICON_SIZE
, NOTIFYICON_SIZE
, 0, 0, DI_NORMAL
);
733 x
+= NOTIFYICON_DIST
;
737 void NotifyArea::Refresh(bool update
)
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
;
744 if (!IsWindow(entry
._hWnd
))
745 if (_icon_map
.erase(entry
)) // delete icons without valid owner window
749 DWORD now
= GetTickCount();
751 // handle icon hiding
752 for(NotifyIconMap::iterator it
=_icon_map
.begin(); it
!=_icon_map
.end(); ++it
) {
753 NotifyInfo
& entry
= it
->second
;
755 DetermineHideState(entry
);
757 switch(entry
._mode
) {
759 if (!(entry
._dwState
& NIS_HIDDEN
)) {
760 entry
._dwState
|= NIS_HIDDEN
;
766 if (entry
._dwState
&NIS_HIDDEN
) {
767 entry
._dwState
&= ~NIS_HIDDEN
;
773 // automatically hide icons after long periods of inactivity
775 if (!(entry
._dwState
& NIS_HIDDEN
))
776 if (now
-entry
._lastChange
> ICON_AUTOHIDE_SECONDS
*1000) {
777 entry
._dwState
|= NIS_HIDDEN
;
788 /// search for a icon at a given client coordinate position
789 NotifyIconSet::iterator
NotifyArea::IconHitTest(const POINT
& pos
)
791 if (pos
.y
<NOTIFYICON_Y
|| pos
.y
>=NOTIFYICON_Y
+NOTIFYICON_SIZE
)
792 return _sorted_icons
.end();
794 NotifyIconSet::iterator it
= _sorted_icons
.begin();
796 int x
= NOTIFYICON_X
;
799 x
+= NOTIFYICON_DIST
;
801 for(; it
!=_sorted_icons
.end(); ++it
) {
802 //NotifyInfo& entry = const_cast<NotifyInfo&>(*it); // Why does GCC 3.3 need this additional const_cast ?!
804 if (pos
.x
>=x
&& pos
.x
<x
+NOTIFYICON_SIZE
)
807 x
+= NOTIFYICON_DIST
;
814 void NotifyIconConfig::create_name()
816 _name
= FmtString(TEXT("'%s' - '%s' - '%s'"), _tipText
.c_str(), _windowTitle
.c_str(), _modulePath
.c_str());
820 bool NotifyIconConfig::match(const NotifyIconConfig
& props
) const
822 if (!_tipText
.empty() && !props
._tipText
.empty())
823 if (props
._tipText
== _tipText
)
826 if (!_windowTitle
.empty() && !props
._windowTitle
.empty())
827 if (_tcsstr(props
._windowTitle
, _windowTitle
))
830 if (!_modulePath
.empty() && !props
._modulePath
.empty())
831 if (!_tcsicmp(props
._modulePath
, _modulePath
))
837 bool NotifyArea::DetermineHideState(NotifyInfo
& entry
)
839 if (entry
._modulePath
.empty()) {
840 const String
& modulePath
= _window_modules
[entry
._hWnd
];
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
;
846 _hook
.GetModulePath(entry
._hWnd
, _hwnd
);
849 for(NotifyIconCfgList::const_iterator it
=_cfg
.begin(); it
!=_cfg
.end(); ++it
) {
850 const NotifyIconConfig
& cfg
= *it
;
852 if (cfg
.match(entry
)) {
853 entry
._mode
= cfg
._mode
;
863 String
string_from_mode(NOTIFYICONMODE mode
)
867 return ResString(IDS_NOTIFY_SHOW
);
870 return ResString(IDS_NOTIFY_HIDE
);
872 default: //case NIM_AUTO
873 return ResString(IDS_NOTIFY_AUTOHIDE
);
878 TrayNotifyDlg::TrayNotifyDlg(HWND 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))))
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
);
891 _cfg_org
= _pNotifyArea
->_cfg
;
892 _show_hidden_org
= _pNotifyArea
->_show_hidden
;
895 SetWindowIcon(hwnd
, IDI_REACTOS
);
897 _haccel
= LoadAccelerators(g_Globals
._hInstance
, MAKEINTRESOURCE(IDA_TRAYNOTIFY
));
900 WindowCanvas
canvas(_hwnd
);
901 HBRUSH hbkgnd
= GetStockBrush(WHITE_BRUSH
);
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
);
908 (void)TreeView_SetImageList(_tree_ctrl
, _himl
, TVSIL_NORMAL
);
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
);
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
);
923 _resize_mgr
.Add(IDC_PICTURE
, MOVE
);
924 _resize_mgr
.Add(ID_SHOW_HIDDEN_ICONS
,MOVE_Y
);
926 _resize_mgr
.Add(IDC_LABEL6
, MOVE_Y
);
927 _resize_mgr
.Add(IDC_LAST_CHANGE
, MOVE_Y
);
929 _resize_mgr
.Add(IDOK
, MOVE
);
930 _resize_mgr
.Add(IDCANCEL
, MOVE
);
932 _resize_mgr
.Resize(+150, +200);
936 SetTimer(_hwnd
, 0, 3000, NULL
);
937 register_pretranslate(hwnd
);
940 TrayNotifyDlg::~TrayNotifyDlg()
943 unregister_pretranslate(_hwnd
);
944 ImageList_Destroy(_himl
);
947 void TrayNotifyDlg::Refresh()
949 ///@todo refresh incrementally
951 HiddenWindow
hide(_tree_ctrl
);
953 TreeView_DeleteAllItems(_tree_ctrl
);
958 tvi
.hInsertAfter
= TVI_LAST
;
960 TV_ITEM
& tv
= tvi
.item
;
961 tv
.mask
= TVIF_TEXT
|TVIF_IMAGE
|TVIF_SELECTEDIMAGE
;
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
);
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
);
973 tvi
.hParent
= _hitemCurrent
;
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
);
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
);
988 tv
.mask
|= TVIF_PARAM
;
990 WindowCanvas
canvas(_hwnd
);
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
;
996 InsertItem(entry
._dwState
&NIS_HIDDEN
? _hitemCurrent_hidden
: _hitemCurrent_visible
, TVI_LAST
, entry
, canvas
);
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
;
1006 if (!cfg_entry
._modulePath
.empty()) {
1007 if ((int)ExtractIconEx(cfg_entry
._modulePath
, 0, NULL
, &hicon
, 1) <= 0)
1013 if (SHGetFileInfo(cfg_entry
._modulePath
, 0, &sfi
, sizeof(sfi
), SHGFI_ICON
|SHGFI_SMALLICON
))
1018 InsertItem(_hitemConfig
, TVI_SORT
, cfg_entry
, canvas
, hicon
, cfg_entry
._mode
);
1024 CheckDlgButton(_hwnd
, ID_SHOW_HIDDEN_ICONS
, _pNotifyArea
->_show_hidden
? BST_CHECKED
: BST_UNCHECKED
);
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
);
1032 TreeView_EnsureVisible(_tree_ctrl
, _hitemCurrent_visible
);
1035 void TrayNotifyDlg::InsertItem(HTREEITEM hparent
, HTREEITEM after
, const NotifyInfo
& entry
, HDC hdc
)
1037 InsertItem(hparent
, after
, entry
, hdc
, entry
._hIcon
, entry
._mode
);
1040 void TrayNotifyDlg::InsertItem(HTREEITEM hparent
, HTREEITEM after
, const NotifyIconDlgInfo
& entry
,
1041 HDC hdc
, HICON hicon
, NOTIFYICONMODE mode
)
1043 int idx
= _info
.size() + 1;
1046 String mode_str
= string_from_mode(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
);
1054 FmtString
txt(TEXT("%s - %s [%s]"), entry
._tipText
.c_str(), entry
._windowTitle
.c_str(), mode_str
.c_str());
1056 TV_INSERTSTRUCT tvi
;
1058 tvi
.hParent
= hparent
;
1059 tvi
.hInsertAfter
= after
;
1061 TV_ITEM
& tv
= tvi
.item
;
1062 tv
.mask
= TVIF_TEXT
|TVIF_IMAGE
|TVIF_SELECTEDIMAGE
|TVIF_PARAM
;
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
);
1070 LRESULT
TrayNotifyDlg::WndProc(UINT nmsg
, WPARAM wparam
, LPARAM lparam
)
1073 case PM_TRANSLATE_MSG
: {
1074 MSG
* pmsg
= (MSG
*) lparam
;
1076 if (TranslateAccelerator(_hwnd
, _haccel
, pmsg
))
1086 return super::WndProc(nmsg
, wparam
, lparam
);
1092 int TrayNotifyDlg::Command(int id
, int code
)
1094 if (code
== BN_CLICKED
) {
1100 case IDC_NOTIFY_SHOW
:
1101 SetIconMode(NIM_SHOW
);
1104 case IDC_NOTIFY_HIDE
:
1105 SetIconMode(NIM_HIDE
);
1108 case IDC_NOTIFY_AUTOHIDE
:
1109 SetIconMode(NIM_AUTO
);
1112 case ID_SHOW_HIDDEN_ICONS
:
1114 SendMessage(*_pNotifyArea
, WM_COMMAND
, MAKEWPARAM(id
,code
), 0);
1118 EndDialog(_hwnd
, id
);
1124 // restore original icon states and configuration data
1125 _pNotifyArea
->_cfg
= _cfg_org
;
1126 _pNotifyArea
->_show_hidden
= _show_hidden_org
;
1128 for(IconStateMap::const_iterator it
=_icon_states_org
.begin(); it
!=_icon_states_org
.end(); ++it
) {
1129 NotifyInfo
& info
= _pNotifyArea
->_icon_map
[it
->first
];
1131 info
._mode
= it
->second
.first
;
1132 info
._dwState
= it
->second
.second
;
1135 SendMessage(*_pNotifyArea
, PM_REFRESH
, 0, 0);
1138 EndDialog(_hwnd
, id
);
1148 int TrayNotifyDlg::Notify(int id
, NMHDR
* pnmh
)
1150 switch(pnmh
->code
) {
1151 case TVN_SELCHANGED
: {
1152 NMTREEVIEW
* pnmtv
= (NMTREEVIEW
*)pnmh
;
1153 int idx
= pnmtv
->itemNew
.lParam
;
1156 RefreshProperties(_info
[idx
]);
1157 _selectedItem
= pnmtv
->itemNew
.hItem
;
1160 SetDlgItemText(_hwnd, IDC_NOTIFY_TOOLTIP, NULL);
1161 SetDlgItemText(_hwnd, IDC_NOTIFY_TITLE, NULL);
1162 SetDlgItemText(_hwnd, IDC_NOTIFY_MODULE, NULL);
1164 CheckRadioButton(_hwnd
, IDC_NOTIFY_SHOW
, IDC_NOTIFY_AUTOHIDE
, 0);
1172 void TrayNotifyDlg::RefreshProperties(const NotifyIconDlgInfo
& entry
)
1174 SetDlgItemText(_hwnd
, IDC_NOTIFY_TOOLTIP
, entry
._tipText
);
1175 SetDlgItemText(_hwnd
, IDC_NOTIFY_TITLE
, entry
._windowTitle
);
1176 SetDlgItemText(_hwnd
, IDC_NOTIFY_MODULE
, entry
._modulePath
);
1178 CheckRadioButton(_hwnd
, IDC_NOTIFY_SHOW
, IDC_NOTIFY_AUTOHIDE
, IDC_NOTIFY_SHOW
+entry
._mode
);
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
);
1185 HICON hicon
= 0; //get_window_icon_big(entry._hWnd, false);
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);
1194 if (SHGetFileInfo(entry
._modulePath
, 0, &sfi
, sizeof(sfi
), SHGFI_ICON
|SHGFI_LARGEICON
))
1200 SendMessage(GetDlgItem(_hwnd
, IDC_PICTURE
), STM_SETICON
, (LPARAM
)hicon
, 0);
1203 SendMessage(GetDlgItem(_hwnd
, IDC_PICTURE
), STM_SETICON
, 0, 0);
1206 void TrayNotifyDlg::SetIconMode(NOTIFYICONMODE mode
)
1208 int idx
= TreeView_GetItemData(_tree_ctrl
, _selectedItem
);
1213 NotifyIconConfig
& entry
= _info
[idx
];
1215 if (entry
._mode
!= mode
) {
1218 // trigger refresh in notify area and this dialog
1220 SendMessage(*_pNotifyArea
, PM_REFRESH
, 0, 0);
1226 NotifyIconCfgList
& cfg
= _pNotifyArea
->_cfg
;
1227 for(NotifyIconCfgList::iterator it
=cfg
.begin(); it
!=cfg
.end(); ++it
) {
1228 NotifyIconConfig
& cfg_entry
= *it
;
1230 if (cfg_entry
.match(entry
)) {
1231 cfg_entry
._mode
= mode
;
1238 // insert new configuration entry
1239 NotifyIconConfig cfg_entry
= entry
;
1241 cfg_entry
._mode
= mode
;
1243 _pNotifyArea
->_cfg
.push_back(cfg_entry
);
1248 ///@todo select treeview item at new position in tree view -> refresh HTREEITEM in _selectedItem
1252 ClockWindow::ClockWindow(HWND hwnd
)
1256 *_time
= TEXT('\0');
1259 _tooltip
.add(_hwnd
, _hwnd
);
1262 HWND
ClockWindow::Create(HWND hwndParent
)
1264 static BtnWindowClass
wcClock(CLASSNAME_CLOCKWINDOW
, CS_DBLCLKS
);
1266 ClientRect
clnt(hwndParent
);
1268 WindowCanvas
canvas(hwndParent
);
1269 FontSelection
font(canvas
, GetStockFont(ANSI_VAR_FONT
));
1271 RECT rect
= {0, 0, 0, 0};
1273 // Arbitrary high time so that the created clock window is big enough
1274 SYSTEMTIME st
= { 1601, 1, 0, 1, 23, 59, 59, 999 };
1276 if (!GetTimeFormat(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, &st
, NULL
, buffer
, sizeof(buffer
)/sizeof(TCHAR
)))
1277 _tcscpy(buffer
, TEXT("00:00"));
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;
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
);
1288 LRESULT
ClockWindow::WndProc(UINT nmsg
, WPARAM wparam
, LPARAM lparam
)
1295 case WM_LBUTTONDBLCLK
:
1296 launch_cpanel(_hwnd
, TEXT("timedate.cpl"));
1300 return super::WndProc(nmsg
, wparam
, lparam
);
1306 int ClockWindow::Notify(int id
, NMHDR
* pnmh
)
1308 if (pnmh
->code
== TTN_GETDISPINFO
) {
1309 LPNMTTDISPINFO pdi
= (LPNMTTDISPINFO
)pnmh
;
1314 GetLocalTime(&systime
);
1316 if (GetDateFormat(LOCALE_USER_DEFAULT
, DATE_LONGDATE
, &systime
, NULL
, buffer
, 64))
1317 _tcscpy(pdi
->szText
, buffer
);
1319 pdi
->szText
[0] = '\0';
1325 void ClockWindow::TimerTick()
1328 InvalidateRect(_hwnd
, NULL
, TRUE
); // refresh displayed time
1331 bool ClockWindow::FormatTime()
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.
1341 return false; // no change
1344 void ClockWindow::Paint()
1346 PaintCanvas
canvas(_hwnd
);
1348 FillRect(canvas
, &canvas
.rcPaint
, GetSysColorBrush(COLOR_BTNFACE
));
1350 BkMode
bkmode(canvas
, TRANSPARENT
);
1351 FontSelection
font(canvas
, GetStockFont(ANSI_VAR_FONT
));
1353 DrawText(canvas
, _time
, -1, ClientRect(_hwnd
), DT_SINGLELINE
|DT_VCENTER
|DT_NOPREFIX
);