d2c5144704da3042c796b9660192b934d7b7ffa3
[reactos.git] / base / shell / explorer / taskbar / taskbar.cpp
1 /*
2 * Copyright 2003, 2004, 2005 Martin Fuchs
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19
20 //
21 // Explorer clone
22 //
23 // taskbar.cpp
24 //
25 // Martin Fuchs, 16.08.2003
26 //
27
28
29 #include <precomp.h>
30
31 #include "taskbar.h"
32 #include "traynotify.h" // for NOTIFYAREA_WIDTH_DEF
33
34
35 DynamicFct<BOOL (WINAPI*)(HWND hwnd)> g_SetTaskmanWindow(TEXT("user32"), "SetTaskmanWindow");
36 DynamicFct<BOOL (WINAPI*)(HWND hwnd)> g_RegisterShellHookWindow(TEXT("user32"), "RegisterShellHookWindow");
37 DynamicFct<BOOL (WINAPI*)(HWND hwnd)> g_DeregisterShellHookWindow(TEXT("user32"), "DeregisterShellHookWindow");
38
39 /*
40 DynamicFct<BOOL (WINAPI*)(HWND hWnd, DWORD dwType)> g_RegisterShellHook(TEXT("shell32"), (LPCSTR)0xb5);
41
42 // constants for RegisterShellHook()
43 #define RSH_UNREGISTER 0
44 #define RSH_REGISTER 1
45 #define RSH_REGISTER_PROGMAN 2
46 #define RSH_REGISTER_TASKMAN 3
47 */
48
49
50 TaskBarEntry::TaskBarEntry()
51 {
52 _id = 0;
53 _hbmp = 0;
54 _bmp_idx = 0;
55 _used = 0;
56 _btn_idx = 0;
57 _fsState = 0;
58 }
59
60 TaskBarMap::~TaskBarMap()
61 {
62 while(!empty()) {
63 iterator it = begin();
64 DeleteBitmap(it->second._hbmp);
65 erase(it);
66 }
67 }
68
69
70 TaskBar::TaskBar(HWND hwnd)
71 : super(hwnd),
72 WM_SHELLHOOK(RegisterWindowMessage(WINMSG_SHELLHOOK))
73 {
74 _last_btn_width = 0;
75
76 _mmMetrics_org.cbSize = sizeof(MINIMIZEDMETRICS);
77
78 SystemParametersInfo(SPI_GETMINIMIZEDMETRICS, sizeof(_mmMetrics_org), &_mmMetrics_org, 0);
79
80 // configure the window manager to hide windows when they are minimized
81 // This is neccessary to enable shell hook messages.
82 if (!(_mmMetrics_org.iArrange & ARW_HIDE)) {
83 MINIMIZEDMETRICS _mmMetrics_new = _mmMetrics_org;
84
85 _mmMetrics_new.iArrange |= ARW_HIDE;
86
87 SystemParametersInfo(SPI_SETMINIMIZEDMETRICS, sizeof(_mmMetrics_new), &_mmMetrics_new, 0);
88 }
89 }
90
91 TaskBar::~TaskBar()
92 {
93 // if (g_RegisterShellHook)
94 // (*g_RegisterShellHook)(_hwnd, RSH_UNREGISTER);
95
96 if (g_DeregisterShellHookWindow)
97 (*g_DeregisterShellHookWindow)(_hwnd);
98 else
99 KillTimer(_hwnd, 0);
100
101 if (g_SetTaskmanWindow)
102 (*g_SetTaskmanWindow)(0);
103
104 SystemParametersInfo(SPI_GETMINIMIZEDMETRICS, sizeof(_mmMetrics_org), &_mmMetrics_org, 0);
105 }
106
107 HWND TaskBar::Create(HWND hwndParent)
108 {
109 ClientRect clnt(hwndParent);
110
111 int taskbar_pos = 80; // This start position will be adjusted in DesktopBar::Resize().
112
113 return Window::Create(WINDOW_CREATOR(TaskBar), 0,
114 BtnWindowClass(CLASSNAME_TASKBAR), TITLE_TASKBAR,
115 WS_CHILD|WS_VISIBLE | CCS_TOP|CCS_NODIVIDER|CCS_NORESIZE,
116 taskbar_pos, 0, clnt.right-taskbar_pos-(NOTIFYAREA_WIDTH_DEF+1), clnt.bottom, hwndParent);
117 }
118
119 LRESULT TaskBar::Init(LPCREATESTRUCT pcs)
120 {
121 if (super::Init(pcs))
122 return 1;
123
124 /* FIXME: There's an internal padding for non-flat toolbar. Get rid of it somehow. */
125 _htoolbar = CreateToolbarEx(_hwnd,
126 WS_CHILD|WS_VISIBLE|WS_CLIPSIBLINGS|WS_CLIPCHILDREN|
127 CCS_TOP|CCS_NODIVIDER|TBSTYLE_LIST|TBSTYLE_TOOLTIPS|TBSTYLE_WRAPABLE,//|TBSTYLE_AUTOSIZE
128 IDW_TASKTOOLBAR, 0, 0, 0, NULL, 0, 0, 0, 16, 16, sizeof(TBBUTTON));
129
130 SendMessage(_htoolbar, TB_SETBUTTONWIDTH, 0, MAKELONG(TASKBUTTONWIDTH_MAX,TASKBUTTONWIDTH_MAX));
131 //SendMessage(_htoolbar, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_MIXEDBUTTONS);
132 //SendMessage(_htoolbar, TB_SETDRAWTEXTFLAGS, DT_CENTER|DT_VCENTER, DT_CENTER|DT_VCENTER);
133 //SetWindowFont(_htoolbar, GetStockFont(ANSI_VAR_FONT), FALSE);
134 //SendMessage(_htoolbar, TB_SETPADDING, 0, MAKELPARAM(8,8));
135
136 #ifndef __MINGW32__ // TBMETRICS missing in MinGW (as of 20.09.2005)
137 // set metrics for the Taskbar toolbar to enable button spacing
138 TBMETRICS metrics;
139
140 metrics.cbSize = sizeof(TBMETRICS);
141 metrics.dwMask = TBMF_BARPAD | TBMF_BUTTONSPACING;
142 metrics.cxBarPad = 0;
143 metrics.cyBarPad = 0;
144 metrics.cxButtonSpacing = 3;
145 metrics.cyButtonSpacing = 3;
146
147 SendMessage(_htoolbar, TB_SETMETRICS, 0, (LPARAM)&metrics);
148 #endif
149
150 _next_id = IDC_FIRST_APP;
151
152 // register the taskbar window as task manager window to make the following call to RegisterShellHookWindow working
153 if (g_SetTaskmanWindow)
154 (*g_SetTaskmanWindow)(_hwnd);
155
156 if (g_RegisterShellHookWindow) {
157 LOG(TEXT("Using shell hooks for notification of shell events."));
158
159 (*g_RegisterShellHookWindow)(_hwnd);
160 } else {
161 LOG(TEXT("Shell hooks not available."));
162
163 SetTimer(_hwnd, 0, 200, NULL);
164 }
165
166 /* Alternatively we could use the RegisterShellHook() function in SHELL32, but this is not yet implemented in the WINE code.
167 if (g_RegisterShellHook) {
168 (*g_RegisterShellHook)(0, RSH_REGISTER);
169
170 if ((HIWORD(GetVersion())>>14) == W_VER_NT)
171 (*g_RegisterShellHook)(_hwnd, RSH_REGISTER_TASKMAN);
172 else
173 (*g_RegisterShellHook)(_hwnd, RSH_REGISTER);
174 }
175 */
176 Refresh();
177
178 return 0;
179 }
180
181 LRESULT TaskBar::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam)
182 {
183 switch(nmsg) {
184 case WM_SIZE:
185 SendMessage(_htoolbar, WM_SIZE, 0, 0);
186 ResizeButtons();
187 break;
188
189 case WM_TIMER:
190 Refresh();
191 return 0;
192
193 case WM_CONTEXTMENU: {
194 Point pt(lparam);
195 ScreenToClient(_htoolbar, &pt);
196
197 if ((HWND)wparam==_htoolbar && SendMessage(_htoolbar, TB_HITTEST, 0, (LPARAM)&pt)>=0)
198 break; // avoid displaying context menu for application button _and_ desktop bar at the same time
199
200 goto def;}
201
202 case PM_GET_LAST_ACTIVE:
203 return (LRESULT)(HWND)_last_foreground_wnd;
204
205 case WM_SYSCOLORCHANGE:
206 SendMessage(_htoolbar, WM_SYSCOLORCHANGE, 0, 0);
207 break;
208
209 default: def:
210 if (nmsg == WM_SHELLHOOK) {
211 switch(wparam) {
212 case HSHELL_WINDOWCREATED:
213 case HSHELL_WINDOWDESTROYED:
214 case HSHELL_WINDOWACTIVATED:
215 case HSHELL_REDRAW:
216 #ifdef HSHELL_FLASH
217 case HSHELL_FLASH:
218 #endif
219 #ifdef HSHELL_RUDEAPPACTIVATED
220 case HSHELL_RUDEAPPACTIVATED:
221 #endif
222 Refresh();
223 break;
224 }
225 } else {
226 return super::WndProc(nmsg, wparam, lparam);
227 }
228 }
229
230 return 0;
231 }
232
233 int TaskBar::Command(int id, int code)
234 {
235 TaskBarMap::iterator found = _map.find_id(id);
236
237 if (found != _map.end()) {
238 ActivateApp(found);
239 return 0;
240 }
241
242 return super::Command(id, code);
243 }
244
245 int TaskBar::Notify(int id, NMHDR* pnmh)
246 {
247 if (pnmh->hwndFrom == _htoolbar)
248 switch(pnmh->code) {
249 case NM_RCLICK: {
250 TBBUTTONINFO btninfo;
251 TaskBarMap::iterator it;
252 Point pt(GetMessagePos());
253 ScreenToClient(_htoolbar, &pt);
254
255 btninfo.cbSize = sizeof(TBBUTTONINFO);
256 btninfo.dwMask = TBIF_BYINDEX|TBIF_COMMAND;
257
258 int idx = SendMessage(_htoolbar, TB_HITTEST, 0, (LPARAM)&pt);
259
260 if (idx>=0 &&
261 SendMessage(_htoolbar, TB_GETBUTTONINFO, idx, (LPARAM)&btninfo)!=-1 &&
262 (it=_map.find_id(btninfo.idCommand))!=_map.end()) {
263 //TaskBarEntry& entry = it->second;
264
265 ActivateApp(it, false, false); // don't restore minimized windows on right button click
266
267 #ifndef __MINGW32__ // SHRestricted() missing in MinGW (as of 29.10.2003)
268 static DynamicFct<DWORD(STDAPICALLTYPE*)(RESTRICTIONS)> pSHRestricted(TEXT("SHELL32"), "SHRestricted");
269
270 if (pSHRestricted && !(*pSHRestricted)(REST_NOTRAYCONTEXTMENU))
271 #endif
272 ShowAppSystemMenu(it);
273 }
274 break;}
275
276 default:
277 return super::Notify(id, pnmh);
278 }
279
280 return 0;
281 }
282
283
284 void TaskBar::ActivateApp(TaskBarMap::iterator it, bool can_minimize, bool can_restore)
285 {
286 HWND hwnd = it->first;
287
288 bool minimize_it = can_minimize && !IsIconic(hwnd) &&
289 (hwnd==GetForegroundWindow() || hwnd==_last_foreground_wnd);
290
291 // switch to selected application window
292 if (can_restore && !minimize_it)
293 if (IsIconic(hwnd))
294 PostMessage(hwnd, WM_SYSCOMMAND, SC_RESTORE, 0);
295
296 // In case minimize_it is true, we _have_ to switch to the app before
297 // posting SW_MINIMIZE to be compatible with some applications (e.g. "Sleipnir")
298 SetForegroundWindow(hwnd);
299
300 if (minimize_it) {
301 PostMessage(hwnd, WM_SYSCOMMAND, SC_MINIMIZE, 0);
302 _last_foreground_wnd = 0;
303 } else
304 _last_foreground_wnd = hwnd;
305
306 Refresh();
307 }
308
309 void TaskBar::ShowAppSystemMenu(TaskBarMap::iterator it)
310 {
311 HMENU hmenu = GetSystemMenu(it->first, FALSE);
312
313 if (hmenu) {
314 POINT pt;
315
316 GetCursorPos(&pt);
317 int cmd = TrackPopupMenu(hmenu, TPM_LEFTBUTTON|TPM_RIGHTBUTTON|TPM_RETURNCMD, pt.x, pt.y, 0, _hwnd, NULL);
318
319 if (cmd) {
320 ActivateApp(it, false, false); // reactivate window after the context menu has closed
321 PostMessage(it->first, WM_SYSCOMMAND, cmd, 0);
322 }
323 }
324 }
325
326
327 HICON get_window_icon_small(HWND hwnd)
328 {
329 HICON hIcon = 0;
330
331 SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL2, 0, SMTO_ABORTIFHUNG, 1000, (LPDWORD)&hIcon);
332
333 if (!hIcon)
334 SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL, 0, SMTO_ABORTIFHUNG, 1000, (LPDWORD)&hIcon);
335
336 if (!hIcon)
337 SendMessageTimeout(hwnd, WM_GETICON, ICON_BIG, 0, SMTO_ABORTIFHUNG, 1000, (LPDWORD)&hIcon);
338
339 if (!hIcon)
340 hIcon = (HICON)GetClassLongPtr(hwnd, GCL_HICONSM);
341
342 if (!hIcon)
343 hIcon = (HICON)GetClassLongPtr(hwnd, GCL_HICON);
344
345 if (!hIcon)
346 SendMessageTimeout(hwnd, WM_QUERYDRAGICON, 0, 0, 0, 1000, (LPDWORD)&hIcon);
347
348 return hIcon;
349 }
350
351 HICON get_window_icon_big(HWND hwnd, bool allow_from_class)
352 {
353 HICON hIcon = 0;
354
355 SendMessageTimeout(hwnd, WM_GETICON, ICON_BIG, 0, SMTO_ABORTIFHUNG, 1000, (LPDWORD)&hIcon);
356
357 if (!hIcon)
358 SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL2, 0, SMTO_ABORTIFHUNG, 1000, (LPDWORD)&hIcon);
359
360 if (!hIcon)
361 SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL, 0, SMTO_ABORTIFHUNG, 1000, (LPDWORD)&hIcon);
362
363 if (allow_from_class) {
364 if (!hIcon)
365 hIcon = (HICON)GetClassLongPtr(hwnd, GCL_HICON);
366
367 if (!hIcon)
368 hIcon = (HICON)GetClassLongPtr(hwnd, GCL_HICONSM);
369 }
370
371 if (!hIcon)
372 SendMessageTimeout(hwnd, WM_QUERYDRAGICON, 0, 0, 0, 1000, (LPDWORD)&hIcon);
373
374 return hIcon;
375 }
376
377 // fill task bar with buttons for enumerated top level windows
378 BOOL CALLBACK TaskBar::EnumWndProc(HWND hwnd, LPARAM lparam)
379 {
380 TaskBar* pThis = (TaskBar*)lparam;
381
382 DWORD style = GetWindowStyle(hwnd);
383 DWORD ex_style = GetWindowExStyle(hwnd);
384
385 if ((style&WS_VISIBLE) && !(ex_style&WS_EX_TOOLWINDOW) &&
386 !GetParent(hwnd) && !GetWindow(hwnd,GW_OWNER)) {
387 TCHAR title[BUFFER_LEN];
388
389 if (!GetWindowText(hwnd, title, BUFFER_LEN))
390 title[0] = '\0';
391
392 TaskBarMap::iterator found = pThis->_map.find(hwnd);
393 int last_id = 0;
394
395 if (found != pThis->_map.end()) {
396 last_id = found->second._id;
397
398 if (!last_id)
399 found->second._id = pThis->_next_id++;
400 } else {
401 HBITMAP hbmp;
402 HICON hIcon = get_window_icon_small(hwnd);
403 BOOL delete_icon = FALSE;
404
405 if (!hIcon) {
406 hIcon = LoadIcon(0, IDI_APPLICATION);
407 delete_icon = TRUE;
408 }
409
410 if (hIcon) {
411 hbmp = create_bitmap_from_icon(hIcon, GetSysColorBrush(COLOR_BTNFACE), WindowCanvas(pThis->_htoolbar));
412 if (delete_icon)
413 DestroyIcon(hIcon); // some icons can be freed, some not - so ignore any error return of DestroyIcon()
414 } else
415 hbmp = 0;
416
417 TBADDBITMAP ab = {0, (UINT_PTR)hbmp};
418 int bmp_idx = SendMessage(pThis->_htoolbar, TB_ADDBITMAP, 1, (LPARAM)&ab);
419
420 TaskBarEntry entry;
421
422 entry._id = pThis->_next_id++;
423 entry._hbmp = hbmp;
424 entry._bmp_idx = bmp_idx;
425 entry._title = title;
426
427 pThis->_map[hwnd] = entry;
428 found = pThis->_map.find(hwnd);
429 }
430
431 TBBUTTON btn = {-2/*I_IMAGENONE*/, 0, TBSTATE_ENABLED/*|TBSTATE_ELLIPSES*/, BTNS_BUTTON, {0, 0}, 0, 0};
432 TaskBarEntry& entry = found->second;
433
434 ++entry._used;
435 btn.idCommand = entry._id;
436
437 HWND foreground = GetForegroundWindow();
438 HWND foreground_owner = GetWindow(foreground, GW_OWNER);
439
440 if (hwnd==foreground || hwnd==foreground_owner) {
441 btn.fsState |= TBSTATE_PRESSED|TBSTATE_CHECKED;
442 pThis->_last_foreground_wnd = hwnd;
443 }
444
445 if (!last_id) {
446 // create new toolbar buttons for new windows
447 if (title[0])
448 btn.iString = (INT_PTR)title;
449
450 btn.iBitmap = entry._bmp_idx;
451 entry._btn_idx = SendMessage(pThis->_htoolbar, TB_BUTTONCOUNT, 0, 0);
452
453 SendMessage(pThis->_htoolbar, TB_INSERTBUTTON, entry._btn_idx, (LPARAM)&btn);
454
455 pThis->ResizeButtons();
456 } else {
457 // refresh attributes of existing buttons
458 if (btn.fsState != entry._fsState)
459 SendMessage(pThis->_htoolbar, TB_SETSTATE, entry._id, MAKELONG(btn.fsState,0));
460
461 if (entry._title != title) {
462 TBBUTTONINFO info;
463
464 info.cbSize = sizeof(TBBUTTONINFO);
465 info.dwMask = TBIF_TEXT;
466 info.pszText = title;
467
468 SendMessage(pThis->_htoolbar, TB_SETBUTTONINFO, entry._id, (LPARAM)&info);
469
470 entry._title = title;
471 }
472 }
473
474 entry._fsState = btn.fsState;
475
476 #ifdef __REACTOS__ // now handled by activating the ARW_HIDE flag with SystemParametersInfo(SPI_SETMINIMIZEDMETRICS)
477 // move minimized windows out of sight
478 if (IsIconic(hwnd)) {
479 RECT rect;
480
481 GetWindowRect(hwnd, &rect);
482
483 if (rect.bottom > 0)
484 SetWindowPos(hwnd, 0, -32000, -32000, 0, 0, SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
485 }
486 #endif
487 }
488
489 return TRUE;
490 }
491
492 void TaskBar::Refresh()
493 {
494 for(TaskBarMap::iterator it=_map.begin(); it!=_map.end(); ++it)
495 it->second._used = 0;
496
497 EnumWindows(EnumWndProc, (LPARAM)this);
498 //EnumDesktopWindows(GetThreadDesktop(GetCurrentThreadId()), EnumWndProc, (LPARAM)_htoolbar);
499
500 set<int> btn_idx_to_delete;
501 set<HBITMAP> hbmp_to_delete;
502
503 for(TaskBarMap::iterator it=_map.begin(); it!=_map.end(); ++it) {
504 TaskBarEntry& entry = it->second;
505
506 if (!entry._used && entry._id) {
507 // store button indexes to remove
508 btn_idx_to_delete.insert(entry._btn_idx);
509 hbmp_to_delete.insert(entry._hbmp);
510 entry._id = 0;
511 }
512 }
513
514 if (!btn_idx_to_delete.empty()) {
515 // remove buttons from right to left
516 for(set<int>::reverse_iterator it=btn_idx_to_delete.rbegin(); it!=btn_idx_to_delete.rend(); ++it) {
517 int idx = *it;
518
519 SendMessage(_htoolbar, TB_DELETEBUTTON, idx, 0);
520
521 for(TaskBarMap::iterator it=_map.begin(); it!=_map.end(); ++it) {
522 TaskBarEntry& entry = it->second;
523
524 // adjust button indexes
525 if (entry._btn_idx > idx) {
526 --entry._btn_idx;
527 --entry._bmp_idx;
528
529 TBBUTTONINFO info;
530
531 info.cbSize = sizeof(TBBUTTONINFO);
532 info.dwMask = TBIF_IMAGE;
533 info.iImage = entry._bmp_idx;
534
535 SendMessage(_htoolbar, TB_SETBUTTONINFO, entry._id, (LPARAM)&info);
536 }
537 }
538 }
539
540 for(set<HBITMAP>::iterator it=hbmp_to_delete.begin(); it!=hbmp_to_delete.end(); ++it) {
541 HBITMAP hbmp = *it;
542
543 TBREPLACEBITMAP tbrepl = {0, (UINT_PTR)hbmp, 0, 0};
544 SendMessage(_htoolbar, TB_REPLACEBITMAP, 0, (LPARAM)&tbrepl);
545
546 DeleteObject(hbmp);
547
548 for(TaskBarMap::iterator it=_map.begin(); it!=_map.end(); ++it)
549 if (it->second._hbmp == hbmp) {
550 _map.erase(it);
551 break;
552 }
553 }
554
555 ResizeButtons();
556 }
557 }
558
559 TaskBarMap::iterator TaskBarMap::find_id(int id)
560 {
561 for(iterator it=begin(); it!=end(); ++it)
562 if (it->second._id == id)
563 return it;
564
565 return end();
566 }
567
568 void TaskBar::ResizeButtons()
569 {
570 int btns = _map.size();
571
572 if (btns > 0) {
573 int bar_width = ClientRect(_hwnd).right;
574 int btn_width = (bar_width / btns) - 3;
575
576 if (btn_width < TASKBUTTONWIDTH_MIN)
577 btn_width = TASKBUTTONWIDTH_MIN;
578 else if (btn_width > TASKBUTTONWIDTH_MAX)
579 btn_width = TASKBUTTONWIDTH_MAX;
580
581 if (btn_width != _last_btn_width) {
582 _last_btn_width = btn_width;
583
584 SendMessage(_htoolbar, TB_SETBUTTONWIDTH, 0, MAKELONG(btn_width,btn_width));
585 SendMessage(_htoolbar, TB_AUTOSIZE, 0, 0);
586 }
587 }
588 }