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 	 // set metrics for the Taskbar toolbar to enable button spacing
137 	TBMETRICS metrics;
138 
139 	metrics.cbSize = sizeof(TBMETRICS);
140 	metrics.dwMask = TBMF_BARPAD | TBMF_BUTTONSPACING;
141 	metrics.cxBarPad = 0;
142 	metrics.cyBarPad = 0;
143 	metrics.cxButtonSpacing = 3;
144 	metrics.cyButtonSpacing = 3;
145 
146 	SendMessage(_htoolbar, TB_SETMETRICS, 0, (LPARAM)&metrics);
147 
148 	_next_id = IDC_FIRST_APP;
149 
150 	 // register the taskbar window as task manager window to make the following call to RegisterShellHookWindow working
151 	if (g_SetTaskmanWindow)
152 		(*g_SetTaskmanWindow)(_hwnd);
153 
154 	if (g_RegisterShellHookWindow) {
155 		LOG(TEXT("Using shell hooks for notification of shell events."));
156 
157 		(*g_RegisterShellHookWindow)(_hwnd);
158 	} else {
159 		LOG(TEXT("Shell hooks not available."));
160 
161 		SetTimer(_hwnd, 0, 200, NULL);
162 	}
163 
164 /* Alternatively we could use the RegisterShellHook() function in SHELL32, but this is not yet implemented in the WINE code.
165 	if (g_RegisterShellHook) {
166 		(*g_RegisterShellHook)(0, RSH_REGISTER);
167 
168 		if ((HIWORD(GetVersion())>>14) == W_VER_NT)
169 			(*g_RegisterShellHook)(_hwnd, RSH_REGISTER_TASKMAN);
170 		else
171 			(*g_RegisterShellHook)(_hwnd, RSH_REGISTER);
172 	}
173 */
174 	Refresh();
175 
176 	return 0;
177 }
178 
179 LRESULT TaskBar::WndProc(UINT nmsg, WPARAM wparam, LPARAM lparam)
180 {
181 	switch(nmsg) {
182 	  case WM_SIZE:
183 		SendMessage(_htoolbar, WM_SIZE, 0, 0);
184 		ResizeButtons();
185 		break;
186 
187 	  case WM_TIMER:
188 		Refresh();
189 		return 0;
190 
191 	  case WM_CONTEXTMENU: {
192 		Point pt(lparam);
193 		ScreenToClient(_htoolbar, &pt);
194 
195 		if ((HWND)wparam==_htoolbar && SendMessage(_htoolbar, TB_HITTEST, 0, (LPARAM)&pt)>=0)
196 			break;	// avoid displaying context menu for application button _and_ desktop bar at the same time
197 
198 		goto def;}
199 
200 	  case PM_GET_LAST_ACTIVE:
201 		return (LRESULT)(HWND)_last_foreground_wnd;
202 
203 	  case WM_SYSCOLORCHANGE:
204 		SendMessage(_htoolbar, WM_SYSCOLORCHANGE, 0, 0);
205 		break;
206 
207 	  default: def:
208 		if (nmsg == WM_SHELLHOOK) {
209 			switch(wparam) {
210 			  case HSHELL_WINDOWCREATED:
211 			  case HSHELL_WINDOWDESTROYED:
212 			  case HSHELL_WINDOWACTIVATED:
213 			  case HSHELL_REDRAW:
214 #ifdef HSHELL_FLASH
215 			  case HSHELL_FLASH:
216 #endif
217 #ifdef HSHELL_RUDEAPPACTIVATED
218 			  case HSHELL_RUDEAPPACTIVATED:
219 #endif
220 				Refresh();
221 				break;
222 			}
223 		} else {
224 			return super::WndProc(nmsg, wparam, lparam);
225 		}
226 	}
227 
228 	return 0;
229 }
230 
231 int TaskBar::Command(int id, int code)
232 {
233 	TaskBarMap::iterator found = _map.find_id(id);
234 
235 	if (found != _map.end()) {
236 		ActivateApp(found);
237 		return 0;
238 	}
239 
240 	return super::Command(id, code);
241 }
242 
243 int TaskBar::Notify(int id, NMHDR* pnmh)
244 {
245 	if (pnmh->hwndFrom == _htoolbar)
246 		switch(pnmh->code) {
247 		  case NM_RCLICK: {
248 			TBBUTTONINFO btninfo;
249 			TaskBarMap::iterator it;
250 			Point pt(GetMessagePos());
251 			ScreenToClient(_htoolbar, &pt);
252 
253 			btninfo.cbSize = sizeof(TBBUTTONINFO);
254 			btninfo.dwMask = TBIF_BYINDEX|TBIF_COMMAND;
255 
256 			int idx = SendMessage(_htoolbar, TB_HITTEST, 0, (LPARAM)&pt);
257 
258 			if (idx>=0 &&
259 				SendMessage(_htoolbar, TB_GETBUTTONINFO, idx, (LPARAM)&btninfo)!=-1 &&
260 				(it=_map.find_id(btninfo.idCommand))!=_map.end()) {
261 				//TaskBarEntry& entry = it->second;
262 
263 				ActivateApp(it, false, false);	// don't restore minimized windows on right button click
264 
265 				static DynamicFct<DWORD(STDAPICALLTYPE*)(RESTRICTIONS)> pSHRestricted(TEXT("SHELL32"), "SHRestricted");
266 
267 				if (pSHRestricted && !(*pSHRestricted)(REST_NOTRAYCONTEXTMENU))
268 					ShowAppSystemMenu(it);
269 			}
270 			break;}
271 
272 		  default:
273 			return super::Notify(id, pnmh);
274 		}
275 
276 	return 0;
277 }
278 
279 
280 void TaskBar::ActivateApp(TaskBarMap::iterator it, bool can_minimize, bool can_restore)
281 {
282 	HWND hwnd = it->first;
283 
284 	bool minimize_it = can_minimize && !IsIconic(hwnd) &&
285 						(hwnd==GetForegroundWindow() || hwnd==_last_foreground_wnd);
286 
287 	 // switch to selected application window
288 	if (can_restore && !minimize_it)
289 		if (IsIconic(hwnd))
290 			PostMessage(hwnd, WM_SYSCOMMAND, SC_RESTORE, 0);
291 
292 	 // In case minimize_it is true, we _have_ to switch to the app before
293 	 // posting SW_MINIMIZE to be compatible with some applications (e.g. "Sleipnir")
294 	SetForegroundWindow(hwnd);
295 
296 	if (minimize_it) {
297 		PostMessage(hwnd, WM_SYSCOMMAND, SC_MINIMIZE, 0);
298 		_last_foreground_wnd = 0;
299 	} else
300 		_last_foreground_wnd = hwnd;
301 
302 	Refresh();
303 }
304 
305 void TaskBar::ShowAppSystemMenu(TaskBarMap::iterator it)
306 {
307 	HMENU hmenu = GetSystemMenu(it->first, FALSE);
308 
309 	if (hmenu) {
310 		POINT pt;
311 
312 		GetCursorPos(&pt);
313 		int cmd = TrackPopupMenu(hmenu, TPM_LEFTBUTTON|TPM_RIGHTBUTTON|TPM_RETURNCMD, pt.x, pt.y, 0, _hwnd, NULL);
314 
315 		if (cmd) {
316 			ActivateApp(it, false, false);	// reactivate window after the context menu has closed
317 			PostMessage(it->first, WM_SYSCOMMAND, cmd, 0);
318 		}
319 	}
320 }
321 
322 
323 HICON get_window_icon_small(HWND hwnd)
324 {
325 	HICON hIcon = 0;
326 
327 	SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL2, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR)&hIcon);
328 
329 	if (!hIcon)
330 		SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR)&hIcon);
331 
332 	if (!hIcon)
333 		SendMessageTimeout(hwnd, WM_GETICON, ICON_BIG, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR)&hIcon);
334 
335 	if (!hIcon)
336 		hIcon = (HICON)GetClassLongPtr(hwnd, GCL_HICONSM);
337 
338 	if (!hIcon)
339 		hIcon = (HICON)GetClassLongPtr(hwnd, GCL_HICON);
340 
341 	if (!hIcon)
342 		SendMessageTimeout(hwnd, WM_QUERYDRAGICON, 0, 0, 0, 1000, (PDWORD_PTR)&hIcon);
343 
344 	return hIcon;
345 }
346 
347 HICON get_window_icon_big(HWND hwnd, bool allow_from_class)
348 {
349 	HICON hIcon = 0;
350 
351 	SendMessageTimeout(hwnd, WM_GETICON, ICON_BIG, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR)&hIcon);
352 
353 	if (!hIcon)
354 		SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL2, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR)&hIcon);
355 
356 	if (!hIcon)
357 		SendMessageTimeout(hwnd, WM_GETICON, ICON_SMALL, 0, SMTO_ABORTIFHUNG, 1000, (PDWORD_PTR)&hIcon);
358 
359 	if (allow_from_class) {
360 		if (!hIcon)
361 			hIcon = (HICON)GetClassLongPtr(hwnd, GCL_HICON);
362 
363 		if (!hIcon)
364 			hIcon = (HICON)GetClassLongPtr(hwnd, GCL_HICONSM);
365 	}
366 
367 	if (!hIcon)
368 		SendMessageTimeout(hwnd, WM_QUERYDRAGICON, 0, 0, 0, 1000, (PDWORD_PTR)&hIcon);
369 
370 	return hIcon;
371 }
372 
373  // fill task bar with buttons for enumerated top level windows
374 BOOL CALLBACK TaskBar::EnumWndProc(HWND hwnd, LPARAM lparam)
375 {
376 	TaskBar* pThis = (TaskBar*)lparam;
377 
378 	DWORD style = GetWindowStyle(hwnd);
379 	DWORD ex_style = GetWindowExStyle(hwnd);
380 
381 	if ((style&WS_VISIBLE) && !(ex_style&WS_EX_TOOLWINDOW) &&
382 		!GetParent(hwnd) && !GetWindow(hwnd,GW_OWNER)) {
383 		TCHAR title[BUFFER_LEN];
384 
385 		if (!GetWindowText(hwnd, title, BUFFER_LEN))
386 			title[0] = '\0';
387 
388 		TaskBarMap::iterator found = pThis->_map.find(hwnd);
389 		int last_id = 0;
390 
391 		if (found != pThis->_map.end()) {
392 			last_id = found->second._id;
393 
394 			if (!last_id)
395 				found->second._id = pThis->_next_id++;
396 		} else {
397 			HBITMAP hbmp;
398 			HICON hIcon = get_window_icon_small(hwnd);
399 			BOOL delete_icon = FALSE;
400 
401 			if (!hIcon) {
402 				hIcon = LoadIcon(0, IDI_APPLICATION);
403 				delete_icon = TRUE;
404 			}
405 
406 			if (hIcon) {
407 				hbmp = create_bitmap_from_icon(hIcon, GetSysColorBrush(COLOR_BTNFACE), WindowCanvas(pThis->_htoolbar));
408 				if (delete_icon)
409 					DestroyIcon(hIcon); // some icons can be freed, some not - so ignore any error return of DestroyIcon()
410 			} else
411 				hbmp = 0;
412 
413 			TBADDBITMAP ab = {0, (UINT_PTR)hbmp};
414 			int bmp_idx = SendMessage(pThis->_htoolbar, TB_ADDBITMAP, 1, (LPARAM)&ab);
415 
416 			TaskBarEntry entry;
417 
418 			entry._id = pThis->_next_id++;
419 			entry._hbmp = hbmp;
420 			entry._bmp_idx = bmp_idx;
421 			entry._title = title;
422 
423 			pThis->_map[hwnd] = entry;
424 			found = pThis->_map.find(hwnd);
425 		}
426 
427 		TBBUTTON btn = {-2/*I_IMAGENONE*/, 0, TBSTATE_ENABLED/*|TBSTATE_ELLIPSES*/, BTNS_BUTTON, {0, 0}, 0, 0};
428 		TaskBarEntry& entry = found->second;
429 
430 		++entry._used;
431 		btn.idCommand = entry._id;
432 
433 		HWND foreground = GetForegroundWindow();
434 		HWND foreground_owner = GetWindow(foreground, GW_OWNER);
435 
436 		if (hwnd==foreground || hwnd==foreground_owner) {
437 			btn.fsState |= TBSTATE_PRESSED|TBSTATE_CHECKED;
438 			pThis->_last_foreground_wnd = hwnd;
439 		}
440 
441 		if (!last_id) {
442 			 // create new toolbar buttons for new windows
443 			if (title[0])
444 				btn.iString = (INT_PTR)title;
445 
446 			btn.iBitmap = entry._bmp_idx;
447 			entry._btn_idx = SendMessage(pThis->_htoolbar, TB_BUTTONCOUNT, 0, 0);
448 
449 			SendMessage(pThis->_htoolbar, TB_INSERTBUTTON, entry._btn_idx, (LPARAM)&btn);
450 
451 			pThis->ResizeButtons();
452 		} else {
453 			 // refresh attributes of existing buttons
454 			if (btn.fsState != entry._fsState)
455 				SendMessage(pThis->_htoolbar, TB_SETSTATE, entry._id, MAKELONG(btn.fsState,0));
456 
457 			if (entry._title != title) {
458 				TBBUTTONINFO info;
459 
460 				info.cbSize = sizeof(TBBUTTONINFO);
461 				info.dwMask = TBIF_TEXT;
462 				info.pszText = title;
463 
464 				SendMessage(pThis->_htoolbar, TB_SETBUTTONINFO, entry._id, (LPARAM)&info);
465 
466 				entry._title = title;
467 			}
468 		}
469 
470 		entry._fsState = btn.fsState;
471 
472 #ifdef __REACTOS__	// now handled by activating the ARW_HIDE flag with SystemParametersInfo(SPI_SETMINIMIZEDMETRICS)
473 		 // move minimized windows out of sight
474 		if (IsIconic(hwnd)) {
475 			RECT rect;
476 
477 			GetWindowRect(hwnd, &rect);
478 
479 			if (rect.bottom > 0)
480 				SetWindowPos(hwnd, 0, -32000, -32000, 0, 0, SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
481 		}
482 #endif
483 	}
484 
485 	return TRUE;
486 }
487 
488 void TaskBar::Refresh()
489 {
490 	for(TaskBarMap::iterator it=_map.begin(); it!=_map.end(); ++it)
491 		it->second._used = 0;
492 
493 	EnumWindows(EnumWndProc, (LPARAM)this);
494 	//EnumDesktopWindows(GetThreadDesktop(GetCurrentThreadId()), EnumWndProc, (LPARAM)_htoolbar);
495 
496 	set<int> btn_idx_to_delete;
497 	set<HBITMAP> hbmp_to_delete;
498 
499 	for(TaskBarMap::iterator it=_map.begin(); it!=_map.end(); ++it) {
500 		TaskBarEntry& entry = it->second;
501 
502 		if (!entry._used && entry._id) {
503 			 // store button indexes to remove
504 			btn_idx_to_delete.insert(entry._btn_idx);
505 			hbmp_to_delete.insert(entry._hbmp);
506 			entry._id = 0;
507 		}
508 	}
509 
510 	if (!btn_idx_to_delete.empty()) {
511 		 // remove buttons from right to left
512 		for(set<int>::reverse_iterator it=btn_idx_to_delete.rbegin(); it!=btn_idx_to_delete.rend(); ++it) {
513 			int idx = *it;
514 
515 			if (!SendMessage(_htoolbar, TB_DELETEBUTTON, idx, 0))
516 				MessageBoxW(NULL, L"failed to delete button", NULL, MB_OK);
517 
518 
519 			for(TaskBarMap::iterator it2=_map.begin(); it2!=_map.end(); ++it2) {
520 				TaskBarEntry& entry = it2->second;
521 
522 				 // adjust button indexes
523 				if (entry._btn_idx > idx) {
524 					--entry._btn_idx;
525 #if 0
526 					--entry._bmp_idx;
527 
528 					TBBUTTONINFO info;
529 
530 					info.cbSize = sizeof(TBBUTTONINFO);
531 					info.dwMask = TBIF_IMAGE;
532 					info.iImage = entry._bmp_idx;
533 
534 					if (!SendMessage(_htoolbar, TB_SETBUTTONINFO, entry._id, (LPARAM)&info))
535 						MessageBoxW(NULL, L"failed to set button info", NULL, MB_OK);
536 #endif
537 				}
538 			}
539 
540 		}
541 
542 		for(set<HBITMAP>::iterator it=hbmp_to_delete.begin(); it!=hbmp_to_delete.end(); ++it) {
543 			HBITMAP hbmp = *it;
544 #if 0
545 			TBREPLACEBITMAP tbrepl = {0, (UINT_PTR)hbmp, 0, 0};
546 			SendMessage(_htoolbar, TB_REPLACEBITMAP, 0, (LPARAM)&tbrepl);
547 #endif
548 			DeleteObject(hbmp);
549 
550 			for(TaskBarMap::iterator it=_map.begin(); it!=_map.end(); ++it)
551 				if (it->second._hbmp == hbmp) {
552 					_map.erase(it);
553 					break;
554 				}
555 		}
556 
557 		ResizeButtons();
558 	}
559 }
560 
561 TaskBarMap::iterator TaskBarMap::find_id(int id)
562 {
563 	for(iterator it=begin(); it!=end(); ++it)
564 		if (it->second._id == id)
565 			return it;
566 
567 	return end();
568 }
569 
570 void TaskBar::ResizeButtons()
571 {
572 	int btns = _map.size();
573 
574 	if (btns > 0) {
575 		int bar_width = ClientRect(_hwnd).right;
576 		int btn_width = (bar_width / btns) - 3;
577 
578 		if (btn_width < TASKBUTTONWIDTH_MIN)
579 			btn_width = TASKBUTTONWIDTH_MIN;
580 		else if (btn_width > TASKBUTTONWIDTH_MAX)
581 			btn_width = TASKBUTTONWIDTH_MAX;
582 
583 		if (btn_width != _last_btn_width) {
584 			_last_btn_width = btn_width;
585 
586 			SendMessage(_htoolbar, TB_SETBUTTONWIDTH, 0, MAKELONG(btn_width,btn_width));
587 			SendMessage(_htoolbar, TB_AUTOSIZE, 0, 0);
588 		}
589 	}
590 }
591