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 "../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
, COUNTOF(buffer
));
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
, COUNTOF(title
))) {
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
: {
425 ScreenToClient(_hwnd
, &pt
);
427 if (IconHitTest(pt
) == _sorted_icons
.end()) { // display menu only when no icon clicked
428 PopupMenu
menu(IDM_NOTIFYAREA
);
429 SetMenuDefaultItem(menu
, 0, MF_BYPOSITION
);
430 CheckMenuItem(menu
, ID_SHOW_HIDDEN_ICONS
, MF_BYCOMMAND
|(_show_hidden
?MF_CHECKED
:MF_UNCHECKED
));
431 CheckMenuItem(menu
, ID_SHOW_ICON_BUTTON
, MF_BYCOMMAND
|(_show_button
?MF_CHECKED
:MF_UNCHECKED
));
432 menu
.TrackPopupMenu(_hwnd
, p
);
436 case WM_COPYDATA
: { // receive NotifyHook answers
440 if (_hook
.ModulePathCopyData(lparam
, &hwnd
, path
))
441 _window_modules
[hwnd
] = path
;
445 if (nmsg
>=WM_MOUSEFIRST
&& nmsg
<=WM_MOUSELAST
) {
446 // close startup menu and other popup menus
447 // This functionality is missing in MS Windows.
448 if (nmsg
==WM_LBUTTONDOWN
|| nmsg
==WM_MBUTTONDOWN
|| nmsg
==WM_RBUTTONDOWN
449 #ifdef WM_XBUTTONDOWN
450 || nmsg
==WM_XBUTTONDOWN
457 NotifyIconSet::const_iterator found
= IconHitTest(pt
);
459 if (found
!= _sorted_icons
.end()) {
460 const NotifyInfo
& entry
= const_cast<NotifyInfo
&>(*found
); // Why does GCC 3.3 need this additional const_cast ?!
462 // set activation time stamp
463 if (nmsg
== WM_LBUTTONDOWN
|| // Some programs need PostMessage() instead of SendMessage().
464 nmsg
== WM_MBUTTONDOWN
|| // So call SendMessage() only for BUTTONUP and BLCLK messages
465 #ifdef WM_XBUTTONDOWN
466 nmsg
== WM_XBUTTONDOWN
||
468 nmsg
== WM_RBUTTONDOWN
) {
469 _icon_map
[entry
]._lastChange
= GetTickCount();
472 // Notify the message if the owner is still alive
473 if (IsWindow(entry
._hWnd
)) {
474 if (nmsg
== WM_MOUSEMOVE
|| // avoid to call blocking SendMessage() for merely moving the mouse over icons
475 nmsg
== WM_LBUTTONDOWN
|| // Some programs need PostMessage() instead of SendMessage().
476 nmsg
== WM_MBUTTONDOWN
|| // So call SendMessage() only for BUTTONUP and BLCLK messages
477 #ifdef WM_XBUTTONDOWN
478 nmsg
== WM_XBUTTONDOWN
||
480 nmsg
== WM_RBUTTONDOWN
)
481 PostMessage(entry
._hWnd
, entry
._uCallbackMessage
, entry
._uID
, nmsg
);
483 // allow SetForegroundWindow() in client process
486 if (GetWindowThreadProcessId(entry
._hWnd
, &pid
)) {
487 // bind dynamically to AllowSetForegroundWindow() to be compatible to WIN98
488 static DynamicFct
<BOOL(WINAPI
*)(DWORD
)> AllowSetForegroundWindow(TEXT("USER32"), "AllowSetForegroundWindow");
490 if (AllowSetForegroundWindow
)
491 (*AllowSetForegroundWindow
)(pid
);
494 // use PostMessage() for notifcation icons of Shell Service Objects in the own process
495 if (pid
== GetCurrentProcessId())
496 PostMessage(entry
._hWnd
, entry
._uCallbackMessage
, entry
._uID
, nmsg
);
498 SendMessage(entry
._hWnd
, entry
._uCallbackMessage
, entry
._uID
, nmsg
);
501 else if (_icon_map
.erase(entry
)) // delete icons without valid owner window
504 // handle clicks on notification area button "show hidden icons"
506 if (nmsg
== WM_LBUTTONDOWN
)
507 if (pt
.x
>=NOTIFYICON_X
&& pt
.x
<NOTIFYICON_X
+NOTIFYICON_SIZE
&&
508 pt
.y
>=NOTIFYICON_Y
&& pt
.y
<NOTIFYICON_Y
+NOTIFYICON_SIZE
)
509 PostMessage(_hwnd
, WM_COMMAND
, MAKEWPARAM(ID_SHOW_HIDDEN_ICONS
,0), 0);
512 return super::WndProc(nmsg
, wparam
, lparam
);
518 int NotifyArea::Command(int id
, int code
)
521 case ID_SHOW_HIDDEN_ICONS
:
522 _show_hidden
= !_show_hidden
;
526 case ID_SHOW_ICON_BUTTON
:
527 _show_button
= !_show_button
;
531 case ID_CONFIG_NOTIFYAREA
:
532 Dialog::DoModal(IDD_NOTIFYAREA
, WINDOW_CREATOR(TrayNotifyDlg
), GetParent(_hwnd
));
536 launch_cpanel(_hwnd
, TEXT("timedate.cpl"));
540 SendParent(WM_COMMAND
, MAKELONG(id
,code
), 0);
546 int NotifyArea::Notify(int id
, NMHDR
* pnmh
)
548 if (pnmh
->code
== TTN_GETDISPINFO
) {
549 LPNMTTDISPINFO pdi
= (LPNMTTDISPINFO
)pnmh
;
551 Point
pt(GetMessagePos());
552 ScreenToClient(_hwnd
, &pt
);
555 pt
.x
>=NOTIFYICON_X
&& pt
.x
<NOTIFYICON_X
+NOTIFYICON_SIZE
&&
556 pt
.y
>=NOTIFYICON_Y
&& pt
.y
<NOTIFYICON_Y
+NOTIFYICON_SIZE
)
558 static ResString
sShowIcons(IDS_SHOW_HIDDEN_ICONS
);
559 static ResString
sHideIcons(IDS_HIDE_ICONS
);
561 pdi
->lpszText
= (_show_hidden
? sHideIcons
: sShowIcons
).str();
563 NotifyIconSet::iterator found
= IconHitTest(pt
);
565 if (found
!= _sorted_icons
.end()) {
566 NotifyInfo
& entry
= const_cast<NotifyInfo
&>(*found
); // Why does GCC 3.3 need this additional const_cast ?!
568 // enable multiline tooltips (break at CR/LF and for very long one-line strings)
569 SendMessage(pnmh
->hwndFrom
, TTM_SETMAXTIPWIDTH
, 0, 400);
571 pdi
->lpszText
= entry
._tipText
.str();
579 void NotifyArea::CancelModes()
581 PostMessage(HWND_BROADCAST
, WM_CANCELMODE
, 0, 0);
583 for(NotifyIconSet::const_iterator it
=_sorted_icons
.begin(); it
!=_sorted_icons
.end(); ++it
)
584 PostMessage(it
->_hWnd
, WM_CANCELMODE
, 0, 0);
587 LRESULT
NotifyArea::ProcessTrayNotification(int notify_code
, NOTIFYICONDATA
* pnid
)
589 switch(notify_code
) {
592 if ((int)pnid
->uID
>= 0) { ///@todo This is a fix for Windows Task Manager.
593 NotifyInfo
& entry
= _icon_map
[pnid
];
596 if (entry
._idx
== -1)
597 entry
._idx
= ++_next_idx
;
598 /* equivalent code using iterator::find();
599 NotifyIconMap::iterator found = _icon_map.find(pnid);
602 if (found == _icon_map.end()) {
603 pentry = &_icon_map[pnid];
604 pentry->_idx = ++_next_idx;
606 pentry = &found->second;
608 NotifyInfo& entry = *pentry;
610 bool changes
= entry
.modify(pnid
);
612 #if NOTIFYICON_VERSION>=3 // as of 21.08.2003 missing in MinGW headers
613 if (DetermineHideState(entry
) && entry
._mode
==NIM_HIDE
) {
614 entry
._dwState
|= NIS_HIDDEN
;
620 UpdateIcons(); ///@todo call only if really changes occurred
627 NotifyIconMap::iterator found
= _icon_map
.find(pnid
);
629 if (found
!= _icon_map
.end()) {
630 if (found
->second
._hIcon
)
631 DestroyIcon(found
->second
._hIcon
);
632 _icon_map
.erase(found
);
638 #if NOTIFYICON_VERSION>=3 // as of 21.08.2003 missing in MinGW headers
640 SetForegroundWindow(_hwnd
);
644 NotifyIconMap::iterator found
= _icon_map
.find(pnid
);
646 if (found
!= _icon_map
.end()) {
647 found
->second
._version
= pnid
->UNION_MEMBER(uVersion
);
657 void NotifyArea::UpdateIcons()
659 _sorted_icons
.clear();
661 // sort icon infos by display index
662 for(NotifyIconMap::const_iterator it
=_icon_map
.begin(); it
!=_icon_map
.end(); ++it
) {
663 const NotifyInfo
& entry
= it
->second
;
665 #ifdef NIF_STATE // as of 21.08.2003 missing in MinGW headers
666 if (_show_hidden
|| !(entry
._dwState
& NIS_HIDDEN
))
668 _sorted_icons
.insert(entry
);
671 // sync tooltip areas to current icon number
672 if (_sorted_icons
.size() != _last_icon_count
) {
673 RECT rect
= {NOTIFYICON_X
, NOTIFYICON_Y
, NOTIFYICON_X
+NOTIFYICON_SIZE
, NOTIFYICON_Y
+NOTIFYICON_SIZE
};
678 _tooltip
.add(_hwnd
, tt_idx
++, rect
);
680 rect
.left
+= NOTIFYICON_DIST
;
681 rect
.right
+= NOTIFYICON_DIST
;
684 size_t icon_cnt
= _sorted_icons
.size();
685 while(tt_idx
< icon_cnt
) {
686 _tooltip
.add(_hwnd
, tt_idx
++, rect
);
688 rect
.left
+= NOTIFYICON_DIST
;
689 rect
.right
+= NOTIFYICON_DIST
;
692 while(tt_idx
< _last_icon_count
)
693 _tooltip
.remove(_hwnd
, tt_idx
++);
695 _last_icon_count
= _sorted_icons
.size();
698 SendMessage(GetParent(_hwnd
), PM_RESIZE_CHILDREN
, 0, 0);
700 InvalidateRect(_hwnd
, NULL
, FALSE
); // refresh icon display
704 #ifndef _NO_ALPHABLEND
706 #pragma comment(lib, "msimg32") // for AlphaBlend()
710 void NotifyArea::Paint()
712 BufferedPaintCanvas
canvas(_hwnd
);
714 // first fill with the background color
715 FillRect(canvas
, &canvas
.rcPaint
, GetSysColorBrush(COLOR_BTNFACE
));
718 int x
= NOTIFYICON_X
;
719 int y
= NOTIFYICON_Y
;
722 static SmallIcon
leftArrowIcon(IDI_NOTIFY_L
);
723 static SmallIcon
rightArrowIcon(IDI_NOTIFY_R
);
725 DrawIconEx(canvas
, x
, y
, _show_hidden
?rightArrowIcon
:leftArrowIcon
, NOTIFYICON_SIZE
, NOTIFYICON_SIZE
, 0, 0, DI_NORMAL
);
726 x
+= NOTIFYICON_DIST
;
729 #ifndef _NO_ALPHABLEND
731 SelectedBitmap
bmp(mem_dc
, CreateCompatibleBitmap(canvas
, NOTIFYICON_SIZE
, NOTIFYICON_SIZE
));
732 RECT rect
= {0, 0, NOTIFYICON_SIZE
, NOTIFYICON_SIZE
};
733 BLENDFUNCTION blend
= {AC_SRC_OVER
, 0, 128, 0}; // 50 % visible
736 for(NotifyIconSet::const_iterator it
=_sorted_icons
.begin(); it
!=_sorted_icons
.end(); ++it
) {
737 #ifndef _NO_ALPHABLEND
738 if (it
->_dwState
& NIS_HIDDEN
) {
739 FillRect(mem_dc
, &rect
, GetSysColorBrush(COLOR_BTNFACE
));
740 DrawIconEx(mem_dc
, 0, 0, it
->_hIcon
, NOTIFYICON_SIZE
, NOTIFYICON_SIZE
, 0, 0, DI_NORMAL
);
741 AlphaBlend(canvas
, x
, y
, NOTIFYICON_SIZE
, NOTIFYICON_SIZE
, mem_dc
, 0, 0, NOTIFYICON_SIZE
, NOTIFYICON_SIZE
, blend
);
744 DrawIconEx(canvas
, x
, y
, it
->_hIcon
, NOTIFYICON_SIZE
, NOTIFYICON_SIZE
, 0, 0, DI_NORMAL
);
746 x
+= NOTIFYICON_DIST
;
750 void NotifyArea::Refresh(bool update
)
752 // Look for task icons without valid owner window.
753 // This is an extended feature missing in MS Windows.
754 for(NotifyIconSet::const_iterator it
=_sorted_icons
.begin(); it
!=_sorted_icons
.end(); ++it
) {
755 const NotifyInfo
& entry
= *it
;
757 if (!IsWindow(entry
._hWnd
))
758 if (_icon_map
.erase(entry
)) // delete icons without valid owner window
762 DWORD now
= GetTickCount();
764 // handle icon hiding
765 for(NotifyIconMap::iterator it
=_icon_map
.begin(); it
!=_icon_map
.end(); ++it
) {
766 NotifyInfo
& entry
= it
->second
;
768 DetermineHideState(entry
);
770 switch(entry
._mode
) {
772 if (!(entry
._dwState
& NIS_HIDDEN
)) {
773 entry
._dwState
|= NIS_HIDDEN
;
779 if (entry
._dwState
&NIS_HIDDEN
) {
780 entry
._dwState
&= ~NIS_HIDDEN
;
786 // automatically hide icons after long periods of inactivity
788 if (!(entry
._dwState
& NIS_HIDDEN
))
789 if (now
-entry
._lastChange
> ICON_AUTOHIDE_SECONDS
*1000) {
790 entry
._dwState
|= NIS_HIDDEN
;
801 /// search for a icon at a given client coordinate position
802 NotifyIconSet::iterator
NotifyArea::IconHitTest(const POINT
& pos
)
804 if (pos
.y
<NOTIFYICON_Y
|| pos
.y
>=NOTIFYICON_Y
+NOTIFYICON_SIZE
)
805 return _sorted_icons
.end();
807 NotifyIconSet::iterator it
= _sorted_icons
.begin();
809 int x
= NOTIFYICON_X
;
812 x
+= NOTIFYICON_DIST
;
814 for(; it
!=_sorted_icons
.end(); ++it
) {
815 //NotifyInfo& entry = const_cast<NotifyInfo&>(*it); // Why does GCC 3.3 need this additional const_cast ?!
817 if (pos
.x
>=x
&& pos
.x
<x
+NOTIFYICON_SIZE
)
820 x
+= NOTIFYICON_DIST
;
827 void NotifyIconConfig::create_name()
829 _name
= FmtString(TEXT("'%s' - '%s' - '%s'"), _tipText
.c_str(), _windowTitle
.c_str(), _modulePath
.c_str());
833 #if NOTIFYICON_VERSION>=3 // as of 21.08.2003 missing in MinGW headers
835 bool NotifyIconConfig::match(const NotifyIconConfig
& props
) const
837 if (!_tipText
.empty() && !props
._tipText
.empty())
838 if (props
._tipText
== _tipText
)
841 if (!_windowTitle
.empty() && !props
._windowTitle
.empty())
842 if (_tcsstr(props
._windowTitle
, _windowTitle
))
845 if (!_modulePath
.empty() && !props
._modulePath
.empty())
846 if (!_tcsicmp(props
._modulePath
, _modulePath
))
852 bool NotifyArea::DetermineHideState(NotifyInfo
& entry
)
854 if (entry
._modulePath
.empty()) {
855 const String
& modulePath
= _window_modules
[entry
._hWnd
];
857 // request module path for new windows (We will get an asynchronous answer by a WM_COPYDATA message.)
858 if (!modulePath
.empty())
859 entry
._modulePath
= modulePath
;
861 _hook
.GetModulePath(entry
._hWnd
, _hwnd
);
864 for(NotifyIconCfgList::const_iterator it
=_cfg
.begin(); it
!=_cfg
.end(); ++it
) {
865 const NotifyIconConfig
& cfg
= *it
;
867 if (cfg
.match(entry
)) {
868 entry
._mode
= cfg
._mode
;
879 String
string_from_mode(NOTIFYICONMODE mode
)
883 return ResString(IDS_NOTIFY_SHOW
);
886 return ResString(IDS_NOTIFY_HIDE
);
888 default: //case NIM_AUTO
889 return ResString(IDS_NOTIFY_AUTOHIDE
);
894 TrayNotifyDlg::TrayNotifyDlg(HWND hwnd
)
896 _tree_ctrl(GetDlgItem(hwnd
, IDC_NOTIFY_ICONS
)),
897 _himl(ImageList_Create(GetSystemMetrics(SM_CXSMICON
), GetSystemMetrics(SM_CYSMICON
), ILC_COLOR24
, 3, 0)),
898 _pNotifyArea(static_cast<NotifyArea
*>(Window::get_window((HWND
)SendMessage(g_Globals
._hwndDesktopBar
, PM_GET_NOTIFYAREA
, 0, 0))))
903 // save original icon states and configuration data
904 for(NotifyIconMap::const_iterator it
=_pNotifyArea
->_icon_map
.begin(); it
!=_pNotifyArea
->_icon_map
.end(); ++it
)
905 _icon_states_org
[it
->first
] = IconStatePair(it
->second
._mode
, it
->second
._dwState
);
907 _cfg_org
= _pNotifyArea
->_cfg
;
908 _show_hidden_org
= _pNotifyArea
->_show_hidden
;
911 SetWindowIcon(hwnd
, IDI_REACTOS
);
913 _haccel
= LoadAccelerators(g_Globals
._hInstance
, MAKEINTRESOURCE(IDA_TRAYNOTIFY
));
916 WindowCanvas
canvas(_hwnd
);
917 HBRUSH hbkgnd
= GetStockBrush(WHITE_BRUSH
);
919 ImageList_AddAlphaIcon(_himl
, SmallIcon(IDI_DOT
), hbkgnd
, canvas
);
920 ImageList_AddAlphaIcon(_himl
, SmallIcon(IDI_DOT_TRANS
), hbkgnd
, canvas
);
921 ImageList_AddAlphaIcon(_himl
, SmallIcon(IDI_DOT_RED
), hbkgnd
, canvas
);
924 (void)TreeView_SetImageList(_tree_ctrl
, _himl
, TVSIL_NORMAL
);
926 _resize_mgr
.Add(IDC_NOTIFY_ICONS
, RESIZE
);
927 _resize_mgr
.Add(IDC_LABEL1
, MOVE_Y
);
928 _resize_mgr
.Add(IDC_NOTIFY_TOOLTIP
, RESIZE_X
|MOVE_Y
);
929 _resize_mgr
.Add(IDC_LABEL2
, MOVE_Y
);
930 _resize_mgr
.Add(IDC_NOTIFY_TITLE
, RESIZE_X
|MOVE_Y
);
931 _resize_mgr
.Add(IDC_LABEL3
, MOVE_Y
);
932 _resize_mgr
.Add(IDC_NOTIFY_MODULE
, RESIZE_X
|MOVE_Y
);
934 _resize_mgr
.Add(IDC_LABEL4
, MOVE_Y
);
935 _resize_mgr
.Add(IDC_NOTIFY_SHOW
, MOVE_Y
);
936 _resize_mgr
.Add(IDC_NOTIFY_HIDE
, MOVE_Y
);
937 _resize_mgr
.Add(IDC_NOTIFY_AUTOHIDE
,MOVE_Y
);
939 _resize_mgr
.Add(IDC_PICTURE
, MOVE
);
940 _resize_mgr
.Add(ID_SHOW_HIDDEN_ICONS
,MOVE_Y
);
942 _resize_mgr
.Add(IDC_LABEL6
, MOVE_Y
);
943 _resize_mgr
.Add(IDC_LAST_CHANGE
, MOVE_Y
);
945 _resize_mgr
.Add(IDOK
, MOVE
);
946 _resize_mgr
.Add(IDCANCEL
, MOVE
);
948 _resize_mgr
.Resize(+150, +200);
952 SetTimer(_hwnd
, 0, 3000, NULL
);
953 register_pretranslate(hwnd
);
956 TrayNotifyDlg::~TrayNotifyDlg()
959 unregister_pretranslate(_hwnd
);
960 ImageList_Destroy(_himl
);
963 void TrayNotifyDlg::Refresh()
965 ///@todo refresh incrementally
967 HiddenWindow
hide(_tree_ctrl
);
969 TreeView_DeleteAllItems(_tree_ctrl
);
974 tvi
.hInsertAfter
= TVI_LAST
;
976 TV_ITEM
& tv
= tvi
.item
;
977 tv
.mask
= TVIF_TEXT
|TVIF_IMAGE
|TVIF_SELECTEDIMAGE
;
979 ResString
str_cur(IDS_ITEMS_CUR
);
980 tv
.pszText
= str_cur
.str();
981 tv
.iSelectedImage
= tv
.iImage
= 0; // IDI_DOT
982 _hitemCurrent
= TreeView_InsertItem(_tree_ctrl
, &tvi
);
984 ResString
str_conf(IDS_ITEMS_CONFIGURED
);
985 tv
.pszText
= str_conf
.str();
986 tv
.iSelectedImage
= tv
.iImage
= 2; // IDI_DOT_RED
987 _hitemConfig
= TreeView_InsertItem(_tree_ctrl
, &tvi
);
989 tvi
.hParent
= _hitemCurrent
;
991 ResString
str_visible(IDS_ITEMS_VISIBLE
);
992 tv
.pszText
= str_visible
.str();
993 tv
.iSelectedImage
= tv
.iImage
= 0; // IDI_DOT
994 _hitemCurrent_visible
= TreeView_InsertItem(_tree_ctrl
, &tvi
);
996 ResString
str_hidden(IDS_ITEMS_HIDDEN
);
997 tv
.pszText
= str_hidden
.str();
998 tv
.iSelectedImage
= tv
.iImage
= 1; // IDI_DOT_TRANS
999 _hitemCurrent_hidden
= TreeView_InsertItem(_tree_ctrl
, &tvi
);
1004 tv
.mask
|= TVIF_PARAM
;
1006 WindowCanvas
canvas(_hwnd
);
1008 // insert current (visible and hidden) items
1009 for(NotifyIconMap::const_iterator it
=_pNotifyArea
->_icon_map
.begin(); it
!=_pNotifyArea
->_icon_map
.end(); ++it
) {
1010 const NotifyInfo
& entry
= it
->second
;
1012 InsertItem(entry
._dwState
&NIS_HIDDEN
? _hitemCurrent_hidden
: _hitemCurrent_visible
, TVI_LAST
, entry
, canvas
);
1015 // insert configured items in tree view
1016 const NotifyIconCfgList
& cfg
= _pNotifyArea
->_cfg
;
1017 for(NotifyIconCfgList::const_iterator it
=cfg
.begin(); it
!=cfg
.end(); ++it
) {
1018 const NotifyIconConfig
& cfg_entry
= *it
;
1022 if (!cfg_entry
._modulePath
.empty()) {
1023 if ((int)ExtractIconEx(cfg_entry
._modulePath
, 0, NULL
, &hicon
, 1) <= 0)
1029 if (SHGetFileInfo(cfg_entry
._modulePath
, 0, &sfi
, sizeof(sfi
), SHGFI_ICON
|SHGFI_SMALLICON
))
1034 InsertItem(_hitemConfig
, TVI_SORT
, cfg_entry
, canvas
, hicon
, cfg_entry
._mode
);
1040 CheckDlgButton(_hwnd
, ID_SHOW_HIDDEN_ICONS
, _pNotifyArea
->_show_hidden
? BST_CHECKED
: BST_UNCHECKED
);
1043 TreeView_Expand(_tree_ctrl
, _hitemCurrent_visible
, TVE_EXPAND
);
1044 TreeView_Expand(_tree_ctrl
, _hitemCurrent_hidden
, TVE_EXPAND
);
1045 TreeView_Expand(_tree_ctrl
, _hitemCurrent
, TVE_EXPAND
);
1046 TreeView_Expand(_tree_ctrl
, _hitemConfig
, TVE_EXPAND
);
1048 TreeView_EnsureVisible(_tree_ctrl
, _hitemCurrent_visible
);
1051 void TrayNotifyDlg::InsertItem(HTREEITEM hparent
, HTREEITEM after
, const NotifyInfo
& entry
, HDC hdc
)
1053 InsertItem(hparent
, after
, entry
, hdc
, entry
._hIcon
, entry
._mode
);
1056 void TrayNotifyDlg::InsertItem(HTREEITEM hparent
, HTREEITEM after
, const NotifyIconDlgInfo
& entry
,
1057 HDC hdc
, HICON hicon
, NOTIFYICONMODE mode
)
1059 int idx
= _info
.size() + 1;
1062 String mode_str
= string_from_mode(mode
);
1065 case NIM_SHOW
: mode_str
= ResString(IDS_NOTIFY_SHOW
); break;
1066 case NIM_HIDE
: mode_str
= ResString(IDS_NOTIFY_HIDE
); break;
1067 case NIM_AUTO
: mode_str
= ResString(IDS_NOTIFY_AUTOHIDE
);
1070 FmtString
txt(TEXT("%s - %s [%s]"), entry
._tipText
.c_str(), entry
._windowTitle
.c_str(), mode_str
.c_str());
1072 TV_INSERTSTRUCT tvi
;
1074 tvi
.hParent
= hparent
;
1075 tvi
.hInsertAfter
= after
;
1077 TV_ITEM
& tv
= tvi
.item
;
1078 tv
.mask
= TVIF_TEXT
|TVIF_IMAGE
|TVIF_SELECTEDIMAGE
|TVIF_PARAM
;
1080 tv
.lParam
= (LPARAM
)idx
;
1081 tv
.pszText
= txt
.str();
1082 tv
.iSelectedImage
= tv
.iImage
= ImageList_AddAlphaIcon(_himl
, hicon
, GetStockBrush(WHITE_BRUSH
), hdc
);
1083 (void)TreeView_InsertItem(_tree_ctrl
, &tvi
);
1086 LRESULT
TrayNotifyDlg::WndProc(UINT nmsg
, WPARAM wparam
, LPARAM lparam
)
1089 case PM_TRANSLATE_MSG
: {
1090 MSG
* pmsg
= (MSG
*) lparam
;
1092 if (TranslateAccelerator(_hwnd
, _haccel
, pmsg
))
1102 return super::WndProc(nmsg
, wparam
, lparam
);
1108 int TrayNotifyDlg::Command(int id
, int code
)
1110 if (code
== BN_CLICKED
) {
1116 case IDC_NOTIFY_SHOW
:
1117 SetIconMode(NIM_SHOW
);
1120 case IDC_NOTIFY_HIDE
:
1121 SetIconMode(NIM_HIDE
);
1124 case IDC_NOTIFY_AUTOHIDE
:
1125 SetIconMode(NIM_AUTO
);
1128 case ID_SHOW_HIDDEN_ICONS
:
1130 SendMessage(*_pNotifyArea
, WM_COMMAND
, MAKEWPARAM(id
,code
), 0);
1134 EndDialog(_hwnd
, id
);
1140 // restore original icon states and configuration data
1141 _pNotifyArea
->_cfg
= _cfg_org
;
1142 _pNotifyArea
->_show_hidden
= _show_hidden_org
;
1144 for(IconStateMap::const_iterator it
=_icon_states_org
.begin(); it
!=_icon_states_org
.end(); ++it
) {
1145 NotifyInfo
& info
= _pNotifyArea
->_icon_map
[it
->first
];
1147 info
._mode
= it
->second
.first
;
1148 info
._dwState
= it
->second
.second
;
1151 SendMessage(*_pNotifyArea
, PM_REFRESH
, 0, 0);
1154 EndDialog(_hwnd
, id
);
1164 int TrayNotifyDlg::Notify(int id
, NMHDR
* pnmh
)
1166 switch(pnmh
->code
) {
1167 case TVN_SELCHANGED
: {
1168 NMTREEVIEW
* pnmtv
= (NMTREEVIEW
*)pnmh
;
1169 int idx
= pnmtv
->itemNew
.lParam
;
1172 RefreshProperties(_info
[idx
]);
1173 _selectedItem
= pnmtv
->itemNew
.hItem
;
1176 SetDlgItemText(_hwnd, IDC_NOTIFY_TOOLTIP, NULL);
1177 SetDlgItemText(_hwnd, IDC_NOTIFY_TITLE, NULL);
1178 SetDlgItemText(_hwnd, IDC_NOTIFY_MODULE, NULL);
1180 CheckRadioButton(_hwnd
, IDC_NOTIFY_SHOW
, IDC_NOTIFY_AUTOHIDE
, 0);
1188 void TrayNotifyDlg::RefreshProperties(const NotifyIconDlgInfo
& entry
)
1190 SetDlgItemText(_hwnd
, IDC_NOTIFY_TOOLTIP
, entry
._tipText
);
1191 SetDlgItemText(_hwnd
, IDC_NOTIFY_TITLE
, entry
._windowTitle
);
1192 SetDlgItemText(_hwnd
, IDC_NOTIFY_MODULE
, entry
._modulePath
);
1194 CheckRadioButton(_hwnd
, IDC_NOTIFY_SHOW
, IDC_NOTIFY_AUTOHIDE
, IDC_NOTIFY_SHOW
+entry
._mode
);
1197 if (entry
._lastChange
)
1198 change_str
.printf(TEXT("before %d s"), (GetTickCount()-entry
._lastChange
+500)/1000);
1199 SetDlgItemText(_hwnd
, IDC_LAST_CHANGE
, change_str
);
1201 HICON hicon
= 0; //get_window_icon_big(entry._hWnd, false);
1203 // If we could not find an icon associated with the owner window, try to load one from the owning module.
1204 if (!hicon
&& !entry
._modulePath
.empty()) {
1205 hicon
= ExtractIcon(g_Globals
._hInstance
, entry
._modulePath
, 0);
1210 if (SHGetFileInfo(entry
._modulePath
, 0, &sfi
, sizeof(sfi
), SHGFI_ICON
|SHGFI_LARGEICON
))
1216 SendMessage(GetDlgItem(_hwnd
, IDC_PICTURE
), STM_SETICON
, (LPARAM
)hicon
, 0);
1219 SendMessage(GetDlgItem(_hwnd
, IDC_PICTURE
), STM_SETICON
, 0, 0);
1222 void TrayNotifyDlg::SetIconMode(NOTIFYICONMODE mode
)
1224 int idx
= TreeView_GetItemData(_tree_ctrl
, _selectedItem
);
1229 NotifyIconConfig
& entry
= _info
[idx
];
1231 if (entry
._mode
!= mode
) {
1234 // trigger refresh in notify area and this dialog
1236 SendMessage(*_pNotifyArea
, PM_REFRESH
, 0, 0);
1242 NotifyIconCfgList
& cfg
= _pNotifyArea
->_cfg
;
1243 for(NotifyIconCfgList::iterator it
=cfg
.begin(); it
!=cfg
.end(); ++it
) {
1244 NotifyIconConfig
& cfg_entry
= *it
;
1246 if (cfg_entry
.match(entry
)) {
1247 cfg_entry
._mode
= mode
;
1254 // insert new configuration entry
1255 NotifyIconConfig cfg_entry
= entry
;
1257 cfg_entry
._mode
= mode
;
1259 _pNotifyArea
->_cfg
.push_back(cfg_entry
);
1264 ///@todo select treeview item at new position in tree view -> refresh HTREEITEM in _selectedItem
1268 ClockWindow::ClockWindow(HWND hwnd
)
1272 *_time
= TEXT('\0');
1275 _tooltip
.add(_hwnd
, _hwnd
);
1278 HWND
ClockWindow::Create(HWND hwndParent
)
1280 static BtnWindowClass
wcClock(CLASSNAME_CLOCKWINDOW
, CS_DBLCLKS
);
1282 ClientRect
clnt(hwndParent
);
1284 WindowCanvas
canvas(hwndParent
);
1285 FontSelection
font(canvas
, GetStockFont(DEFAULT_GUI_FONT
));
1287 RECT rect
= {0, 0, 0, 0};
1290 if (!GetTimeFormat(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, NULL
, NULL
, buffer
, sizeof(buffer
)/sizeof(TCHAR
)))
1291 _tcscpy(buffer
, TEXT("00:00"));
1293 DrawText(canvas
, buffer
, -1, &rect
, DT_SINGLELINE
|DT_NOPREFIX
|DT_CALCRECT
);
1294 int clockwindowWidth
= rect
.right
-rect
.left
+ 4;
1296 return Window::Create(WINDOW_CREATOR(ClockWindow
), 0,
1297 wcClock
, NULL
, WS_CHILD
|WS_VISIBLE
,
1298 clnt
.right
-(clockwindowWidth
), 1, clockwindowWidth
, clnt
.bottom
-2, hwndParent
);
1301 LRESULT
ClockWindow::WndProc(UINT nmsg
, WPARAM wparam
, LPARAM lparam
)
1308 case WM_LBUTTONDBLCLK
:
1309 launch_cpanel(_hwnd
, TEXT("timedate.cpl"));
1313 return super::WndProc(nmsg
, wparam
, lparam
);
1319 int ClockWindow::Notify(int id
, NMHDR
* pnmh
)
1321 if (pnmh
->code
== TTN_GETDISPINFO
) {
1322 LPNMTTDISPINFO pdi
= (LPNMTTDISPINFO
)pnmh
;
1327 GetLocalTime(&systime
);
1329 if (GetDateFormat(LOCALE_USER_DEFAULT
, DATE_LONGDATE
, &systime
, NULL
, buffer
, 64))
1330 _tcscpy(pdi
->szText
, buffer
);
1332 pdi
->szText
[0] = '\0';
1338 void ClockWindow::TimerTick()
1341 InvalidateRect(_hwnd
, NULL
, TRUE
); // refresh displayed time
1344 bool ClockWindow::FormatTime()
1348 if (GetTimeFormat(LOCALE_USER_DEFAULT
, TIME_NOSECONDS
, NULL
, NULL
, buffer
, sizeof(buffer
)/sizeof(TCHAR
)))
1349 if (_tcscmp(buffer
, _time
)) {
1350 _tcscpy(_time
, buffer
);
1351 return true; // The text to display has changed.
1354 return false; // no change
1357 void ClockWindow::Paint()
1359 PaintCanvas
canvas(_hwnd
);
1361 FillRect(canvas
, &canvas
.rcPaint
, GetSysColorBrush(COLOR_BTNFACE
));
1363 BkMode
bkmode(canvas
, TRANSPARENT
);
1364 FontSelection
font(canvas
, GetStockFont(ANSI_VAR_FONT
));
1366 DrawText(canvas
, _time
, -1, ClientRect(_hwnd
), DT_SINGLELINE
|DT_VCENTER
|DT_NOPREFIX
);