/*******************************************************************************
 * Copyright (c) 2000, 2021 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.swt.widgets;


import java.util.*;

import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.*;
import org.eclipse.swt.internal.win32.*;

/**
 * Instances of this class support the layout of selectable
 * tool bar items.
 * <p>
 * The item children that may be added to instances of this class
 * must be of type <code>ToolItem</code>.
 * </p><p>
 * Note that although this class is a subclass of <code>Composite</code>,
 * it does not make sense to add <code>Control</code> children to it,
 * or set a layout on it.
 * </p>
 * <dl>
 * <dt><b>Styles:</b></dt>
 * <dd>FLAT, WRAP, RIGHT, HORIZONTAL, VERTICAL, SHADOW_OUT</dd>
 * <dt><b>Events:</b></dt>
 * <dd>(none)</dd>
 * </dl>
 * <p>
 * Note: Only one of the styles HORIZONTAL and VERTICAL may be specified.
 * </p><p>
 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
 * </p>
 *
 * @see <a href="https://eclipse.dev/eclipse/swt/snippets/#toolbar">ToolBar, ToolItem snippets</a>
 * @see <a href="https://eclipse.dev/eclipse/swt/examples.html">SWT Example: ControlExample</a>
 * @see <a href="https://eclipse.dev/eclipse/swt/">Sample code and further information</a>
 * @noextend This class is not intended to be subclassed by clients.
 */
public class ToolBar extends Composite {
	int lastFocusId, lastArrowId, lastHotId, _width, _height, _count = -1, _wHint = -1, _hHint = -1;
	long currentToolItemToolTip;
	ToolItem [] items;
	ToolItem [] tabItemList;
	boolean ignoreResize, ignoreMouse;
	ImageList imageList, disabledImageList, hotImageList;
	static final long ToolBarProc;
	static final TCHAR ToolBarClass = new TCHAR (0, OS.TOOLBARCLASSNAME, true);
	static {
		WNDCLASS lpWndClass = new WNDCLASS ();
		OS.GetClassInfo (0, ToolBarClass, lpWndClass);
		ToolBarProc = lpWndClass.lpfnWndProc;
	}

	/*
	* From the Windows SDK for TB_SETBUTTONSIZE:
	*
	*   "If an application does not explicitly
	*	set the button size, the size defaults
	*	to 24 by 22 pixels".
	*/
	static final int DEFAULT_WIDTH = 24;
	static final int DEFAULT_HEIGHT = 22;

/**
 * Constructs a new instance of this class given its parent
 * and a style value describing its behavior and appearance.
 * <p>
 * The style value is either one of the style constants defined in
 * class <code>SWT</code> which is applicable to instances of this
 * class, or must be built by <em>bitwise OR</em>'ing together
 * (that is, using the <code>int</code> "|" operator) two or more
 * of those <code>SWT</code> style constants. The class description
 * lists the style constants that are applicable to the class.
 * Style bits are also inherited from superclasses.
 * </p>
 *
 * @param parent a composite control which will be the parent of the new instance (cannot be null)
 * @param style the style of control to construct
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
 *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
 * </ul>
 *
 * @see SWT#FLAT
 * @see SWT#WRAP
 * @see SWT#RIGHT
 * @see SWT#HORIZONTAL
 * @see SWT#SHADOW_OUT
 * @see SWT#VERTICAL
 * @see Widget#checkSubclass()
 * @see Widget#getStyle()
 */
public ToolBar (Composite parent, int style) {
	super (parent, checkStyle (style));
	/*
	* Ensure that either of HORIZONTAL or VERTICAL is set.
	* NOTE: HORIZONTAL and VERTICAL have the same values
	* as H_SCROLL and V_SCROLL so it is necessary to first
	* clear these bits to avoid scroll bars and then reset
	* the bits using the original style supplied by the
	* programmer.
	*
	* NOTE: The CCS_VERT style cannot be applied when the
	* widget is created because of this conflict.
	*/
	if ((style & SWT.VERTICAL) != 0) {
		this.style |= SWT.VERTICAL;
		int bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
		/*
		* Feature in Windows.  When a tool bar has the style
		* TBSTYLE_LIST and has a drop down item, Window leaves
		* too much padding around the button.  This affects
		* every button in the tool bar and makes the preferred
		* height too big.  The fix is to set the TBSTYLE_LIST
		* when the tool bar contains both text and images.
		*
		* NOTE: Tool bars with CCS_VERT must have TBSTYLE_LIST
		* set before any item is added or the tool bar does
		* not lay out properly.  The work around does not run
		* in this case.
		*/
		if (OS.IsAppThemed ()) {
			if ((style & SWT.RIGHT) != 0) bits |= OS.TBSTYLE_LIST;
		}
		OS.SetWindowLong (handle, OS.GWL_STYLE, bits | OS.CCS_VERT);
	} else {
		this.style |= SWT.HORIZONTAL;
	}
}

@Override
long callWindowProc (long hwnd, int msg, long wParam, long lParam) {
	if (handle == 0) return 0;
	/*
	* Bug in Windows.  For some reason, during the processing
	* of WM_SYSCHAR, the tool bar window proc does not call the
	* default window proc causing mnemonics for the menu bar
	* to be ignored.  The fix is to always call the default
	* window proc for WM_SYSCHAR.
	*/
	if (msg == OS.WM_SYSCHAR) {
		return OS.DefWindowProc (hwnd, msg, wParam, lParam);
	}
	return OS.CallWindowProc (ToolBarProc, hwnd, msg, wParam, lParam);
}

static int checkStyle (int style) {
	/*
	* On Windows, only flat tool bars can be traversed.
	*/
	if ((style & SWT.FLAT) == 0) style |= SWT.NO_FOCUS;

	/*
	* A vertical tool bar cannot wrap because TB_SETROWS
	* fails when the toolbar has TBSTYLE_WRAPABLE.
	*/
	if ((style & SWT.VERTICAL) != 0) style &= ~SWT.WRAP;

	/*
	* Even though it is legal to create this widget
	* with scroll bars, they serve no useful purpose
	* because they do not automatically scroll the
	* widget's client area.  The fix is to clear
	* the SWT style.
	*/
	return style & ~(SWT.H_SCROLL | SWT.V_SCROLL);
}

@Override
void checkBuffered () {
	super.checkBuffered ();
	style |= SWT.DOUBLE_BUFFERED;
}

@Override
protected void checkSubclass () {
	if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS);
}

@Override
public void layout (boolean changed) {
	checkWidget ();
	clearSizeCache(changed);
	super.layout(changed);
}

void clearSizeCache(boolean changed) {
	// If changed, discard the cached layout information
	if (changed) {
		_count = _wHint = _hHint = -1;
	}
}

@Override
Point computeSizeInPixels (Point hintInPoints, int zoom, boolean changed) {
	Point hintInPixels = Win32DPIUtils.pointToPixelAsSufficientlyLargeSize(hintInPoints, zoom);
	int count = (int)OS.SendMessage (handle, OS.TB_BUTTONCOUNT, 0, 0);
	if (count == this._count && hintInPixels.x == this._wHint && hintInPixels.y == this._hHint) {
		// Return already cached values calculated previously
		return new Point (_width, _height);
	}
	this._count = count;
	this._wHint = hintInPixels.x;
	this._hHint = hintInPixels.y;
	int width = 0, height = 0;
	if ((style & SWT.VERTICAL) != 0) {
		RECT rect = new RECT ();
		TBBUTTON lpButton = new TBBUTTON ();
		for (int i=0; i<count; i++) {
			OS.SendMessage (handle, OS.TB_GETITEMRECT, i, rect);
			height = Math.max (height, rect.bottom);
			OS.SendMessage (handle, OS.TB_GETBUTTON, i, lpButton);
			if ((lpButton.fsStyle & OS.BTNS_SEP) != 0) {
				TBBUTTONINFO info = new TBBUTTONINFO ();
				info.cbSize = TBBUTTONINFO.sizeof;
				info.dwMask = OS.TBIF_SIZE;
				OS.SendMessage (handle, OS.TB_GETBUTTONINFO, lpButton.idCommand, info);
				width = Math.max (width, info.cx);
			} else {
				width = Math.max (width, rect.right);
			}
		}
	} else {
		RECT oldRect = new RECT ();
		OS.GetWindowRect (handle, oldRect);
		int oldWidth = oldRect.right - oldRect.left;
		int oldHeight = oldRect.bottom - oldRect.top;
		int border = getBorderWidthInPixels ();
		int newWidth = hintInPoints.x == SWT.DEFAULT ? 0x3FFF : hintInPixels.x + border * 2;
		int newHeight = hintInPoints.y == SWT.DEFAULT ? 0x3FFF : hintInPixels.y + border * 2;
		boolean redraw = getDrawing () && OS.IsWindowVisible (handle);
		ignoreResize = true;
		if (redraw) OS.UpdateWindow (handle);
		int flags = OS.SWP_NOACTIVATE | OS.SWP_NOMOVE | OS.SWP_NOREDRAW | OS.SWP_NOZORDER;
		OS.SetWindowPos (handle, 0, 0, 0, newWidth, newHeight, flags);
		if (count != 0) {
			RECT rect = new RECT ();
			OS.SendMessage (handle, OS.TB_GETITEMRECT, count - 1, rect);
			width = Math.max (width, rect.right);
			height = Math.max (height, rect.bottom);
		}
		OS.SetWindowPos (handle, 0, 0, 0, oldWidth, oldHeight, flags);
		if (redraw) OS.ValidateRect (handle, null);
		ignoreResize = false;
	}

	/*
	* From the Windows SDK for TB_SETBUTTONSIZE:
	*
	*   "If an application does not explicitly
	*	set the button size, the size defaults
	*	to 24 by 22 pixels".
	*/
	if (width == 0) width = DEFAULT_WIDTH;
	if (height == 0) height = DEFAULT_HEIGHT;
	if (hintInPoints.x != SWT.DEFAULT) width = hintInPixels.x;
	if (hintInPoints.y != SWT.DEFAULT) height = hintInPixels.y;
	Rectangle trim = computeTrimInPixels (0, 0, width, height);
	width = trim.width;  height = trim.height;
	/*
	 * Cache this size information for possible re-use as this method gets called
	 * too many times, for more details see performance Bug 574641
	 */
	this._width = width;
	this._height = height;
	return new Point (width, height);
}

@Override Rectangle computeTrimInPixels (int x, int y, int width, int height) {
	Rectangle trim = super.computeTrimInPixels (x, y, width, height);
	int bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
	if ((bits & OS.CCS_NODIVIDER) == 0) trim.height += 2;
	return trim;
}

@Override
Widget computeTabGroup () {
	ToolItem [] items = _getItems ();
	if (tabItemList == null) {
		int i = 0;
		while (i < items.length && items [i].control == null) i++;
		if (i == items.length) return super.computeTabGroup ();
	}
	int index = (int)OS.SendMessage (handle, OS.TB_GETHOTITEM, 0, 0);
	if (index == -1) index = lastHotId;
	while (index >= 0) {
		ToolItem item = items [index];
		if (item.isTabGroup ()) return item;
		index--;
	}
	return super.computeTabGroup ();
}

@Override
Widget [] computeTabList () {
	ToolItem [] items = _getItems ();
	if (tabItemList == null) {
		int i = 0;
		while (i < items.length && items [i].control == null) i++;
		if (i == items.length) return super.computeTabList ();
	}
	Widget result [] = {};
	if (!isTabGroup () || !isEnabled () || !isVisible ()) return result;
	ToolItem [] list = tabList != null ? _getTabItemList () : items;
	for (ToolItem child : list) {
		Widget  [] childList = child.computeTabList ();
		if (childList.length != 0) {
			Widget [] newResult = new Widget [result.length + childList.length];
			System.arraycopy (result, 0, newResult, 0, result.length);
			System.arraycopy (childList, 0, newResult, result.length, childList.length);
			result = newResult;
		}
	}
	if (result.length == 0) result = new Widget [] {this};
	return result;
}

@Override
void createHandle () {
	super.createHandle ();
	state &= ~CANVAS;

	/*
	* Feature in Windows.  When TBSTYLE_FLAT is used to create
	* a flat toolbar, for some reason TBSTYLE_TRANSPARENT is
	* also set.  This causes the toolbar to flicker when it is
	* moved or resized.  The fix is to clear TBSTYLE_TRANSPARENT.
	*
	* NOTE:  This work around is unnecessary on XP.  There is no
	* flickering and clearing the TBSTYLE_TRANSPARENT interferes
	* with the XP theme.
	*/
	if ((style & SWT.FLAT) != 0) {
		if (!OS.IsAppThemed ()) {
			int bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
			bits &= ~OS.TBSTYLE_TRANSPARENT;
			OS.SetWindowLong (handle, OS.GWL_STYLE, bits);
		}
	}

	/*
	* Feature in Windows.  Despite the fact that the
	* tool tip text contains \r\n, the tooltip will
	* not honour the new line unless TTM_SETMAXTIPWIDTH
	* is set.  The fix is to set TTM_SETMAXTIPWIDTH to
	* a large value.
	*/
	/*
	* These lines are intentionally commented.  The tool
	* bar currently sets this value to 300 so it is not
	* necessary to set TTM_SETMAXTIPWIDTH.
	*/
//	long hwndToolTip = OS.SendMessage (handle, OS.TB_GETTOOLTIPS, 0, 0);
//	OS.SendMessage (hwndToolTip, OS.TTM_SETMAXTIPWIDTH, 0, 0x7FFF);

	/*
	* Feature in Windows.  When the control is created,
	* it does not use the default system font.  A new HFONT
	* is created and destroyed when the control is destroyed.
	* This means that a program that queries the font from
	* this control, uses the font in another control and then
	* destroys this control will have the font unexpectedly
	* destroyed in the other control.  The fix is to assign
	* the font ourselves each time the control is created.
	* The control will not destroy a font that it did not
	* create.
	*/
	long hFont = OS.GetStockObject (OS.SYSTEM_FONT);
	OS.SendMessage (handle, OS.WM_SETFONT, hFont, 0);

	/* Set the button struct, bitmap and button sizes */
	OS.SendMessage (handle, OS.TB_BUTTONSTRUCTSIZE, TBBUTTON.sizeof, 0);
	OS.SendMessage (handle, OS.TB_SETBITMAPSIZE, 0, 0);
	OS.SendMessage (handle, OS.TB_SETBUTTONSIZE, 0, 0);

	/* Set the extended style bits */
	int bits = OS.TBSTYLE_EX_DRAWDDARROWS | OS.TBSTYLE_EX_MIXEDBUTTONS | OS.TBSTYLE_EX_DOUBLEBUFFER;
	OS.SendMessage (handle, OS.TB_SETEXTENDEDSTYLE, 0, bits);
}

void createItem (ToolItem item, int index) {
	int count = (int)OS.SendMessage (handle, OS.TB_BUTTONCOUNT, 0, 0);
	if (!(0 <= index && index <= count)) error (SWT.ERROR_INVALID_RANGE);
	int id = 0;
	while (id < items.length && items [id] != null) id++;
	if (id == items.length) {
		ToolItem [] newItems = new ToolItem [items.length + 4];
		System.arraycopy (items, 0, newItems, 0, items.length);
		items = newItems;
	}
	int bits = item.widgetStyle ();
	TBBUTTON lpButton = new TBBUTTON ();
	lpButton.idCommand = id;
	lpButton.fsStyle = (byte) bits;
	lpButton.fsState = (byte) OS.TBSTATE_ENABLED;

	/*
	* Bug in Windows.  Despite the fact that the image list
	* index has never been set for the item, Windows always
	* assumes that the image index for the item is valid.
	* When an item is inserted, the image index is zero.
	* Therefore, when the first image is inserted and is
	* assigned image index zero, every item draws with this
	* image.  The fix is to set the image index to none
	* when the item is created.  This is not necessary in
	* the case when the item has the BTNS_SEP style because
	* separators cannot show images.
	*/
	if ((bits & OS.BTNS_SEP) == 0) lpButton.iBitmap = OS.I_IMAGENONE;
	if (OS.SendMessage (handle, OS.TB_INSERTBUTTON, index, lpButton) == 0) {
		error (SWT.ERROR_ITEM_NOT_ADDED);
	}
	items [item.id = id] = item;
	if ((style & SWT.VERTICAL) != 0) setRowCount (count + 1);
	layoutItems ();
}

@Override
void createWidget () {
	super.createWidget ();
	items = new ToolItem [4];
	lastFocusId = lastArrowId = lastHotId = -1;
}

@Override
int applyThemeBackground () {
	return -1; /* No Change */
}

void destroyItem (ToolItem item) {
	TBBUTTONINFO info = new TBBUTTONINFO ();
	info.cbSize = TBBUTTONINFO.sizeof;
	info.dwMask = OS.TBIF_IMAGE | OS.TBIF_STYLE;
	int index = (int)OS.SendMessage (handle, OS.TB_GETBUTTONINFO, item.id, info);
	/*
	* Feature in Windows.  For some reason, a tool item that has
	* the style BTNS_SEP does not return I_IMAGENONE when queried
	* for an image index, despite the fact that no attempt has been
	* made to assign an image to the item.  As a result, operations
	* on an image list that use the wrong index cause random results.
	* The fix is to ensure that the tool item is not a separator
	* before using the image index.  Since separators cannot have
	* an image and one is never assigned, this is not a problem.
	*/
	if ((info.fsStyle & OS.BTNS_SEP) == 0 && info.iImage != OS.I_IMAGENONE) {
		if (imageList != null) imageList.put (info.iImage, null);
		if (hotImageList != null) hotImageList.put (info.iImage, null);
		if (disabledImageList != null) disabledImageList.put (info.iImage, null);
	}
	OS.SendMessage (handle, OS.TB_DELETEBUTTON, index, 0);
	if (item.id == lastFocusId) lastFocusId = -1;
	if (item.id == lastArrowId) lastArrowId = -1;
	if (item.id == lastHotId) lastHotId = -1;
	items [item.id] = null;
	item.id = -1;
	int count = (int)OS.SendMessage (handle, OS.TB_BUTTONCOUNT, 0, 0);
	if (count == 0) {
		if (imageList != null) {
			OS.SendMessage (handle, OS.TB_SETIMAGELIST, 0, 0);
			display.releaseToolImageList (imageList);
		}
		if (hotImageList != null) {
			OS.SendMessage (handle, OS.TB_SETHOTIMAGELIST, 0, 0);
			display.releaseToolHotImageList (hotImageList);
		}
		if (disabledImageList != null) {
			OS.SendMessage (handle, OS.TB_SETDISABLEDIMAGELIST, 0, 0);
			display.releaseToolDisabledImageList (disabledImageList);
		}
		imageList = hotImageList = disabledImageList = null;
		items = new ToolItem [4];
	}
	if ((style & SWT.VERTICAL) != 0) setRowCount (count - 1);
	layoutItems ();
}

@Override
void enableWidget (boolean enabled) {
	super.enableWidget (enabled);
	/*
	* Bug in Windows.  When a tool item with the style
	* BTNS_CHECK or BTNS_CHECKGROUP is selected and then
	* disabled, the item does not draw using the disabled
	* image.  The fix is to use the disabled image in all
	* image lists for the item.
	*
	* Feature in Windows.  When a tool bar is disabled,
	* the text draws disabled but the images do not.
	* The fix is to use the disabled image in all image
	* lists for all items.
	*/
	for (ToolItem item : items) {
		if (item != null) {
			if ((item.style & SWT.SEPARATOR) == 0) {
				item.updateImages (enabled && item.getEnabled ());
			}
		}
	}
}

ImageList getDisabledImageList () {
	return disabledImageList;
}

ImageList getHotImageList () {
	return hotImageList;
}

ImageList getImageList () {
	return imageList;
}

/**
 * Returns the item at the given, zero-relative index in the
 * receiver. Throws an exception if the index is out of range.
 *
 * @param index the index of the item to return
 * @return the item at the given index
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public ToolItem getItem (int index) {
	checkWidget ();
	int count = (int)OS.SendMessage (handle, OS.TB_BUTTONCOUNT, 0, 0);
	if (!(0 <= index && index < count)) error (SWT.ERROR_INVALID_RANGE);
	TBBUTTON lpButton = new TBBUTTON ();
	long result = OS.SendMessage (handle, OS.TB_GETBUTTON, index, lpButton);
	if (result == 0) error (SWT.ERROR_CANNOT_GET_ITEM);
	return items [lpButton.idCommand];
}

/**
 * Returns the item at the given point in the receiver
 * or null if no such item exists. The point is in the
 * coordinate system of the receiver.
 *
 * @param point the point used to locate the item
 * @return the item at the given point
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the point is null</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public ToolItem getItem (Point point) {
	checkWidget ();
	if (point == null) error (SWT.ERROR_NULL_ARGUMENT);
	return getItemInPixels(Win32DPIUtils.pointToPixelAsLocation(point, getAutoscalingZoom()));
}

ToolItem getItemInPixels (Point point) {
	for (ToolItem item : getItems ()) {
		Rectangle rect = item.getBoundsInPixels ();
		if (rect.contains (point)) return item;
	}
	return null;
}

/**
 * Returns the number of items contained in the receiver.
 *
 * @return the number of items
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public int getItemCount () {
	checkWidget ();
	return (int)OS.SendMessage (handle, OS.TB_BUTTONCOUNT, 0, 0);
}

/**
 * Returns an array of <code>ToolItem</code>s which are the items
 * in the receiver.
 * <p>
 * Note: This is not the actual structure used by the receiver
 * to maintain its list of items, so modifying the array will
 * not affect the receiver.
 * </p>
 *
 * @return the items in the receiver
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public ToolItem [] getItems () {
	checkWidget ();
	return _getItems ();
}

ToolItem [] _getItems () {
	int count = (int)OS.SendMessage (handle, OS.TB_BUTTONCOUNT, 0, 0);
	TBBUTTON lpButton = new TBBUTTON ();
	ToolItem [] result = new ToolItem [count];
	for (int i=0; i<count; i++) {
		OS.SendMessage (handle, OS.TB_GETBUTTON, i, lpButton);
		result [i] = items [lpButton.idCommand];
	}
	return result;
}

/**
 * Returns the number of rows in the receiver. When
 * the receiver has the <code>WRAP</code> style, the
 * number of rows can be greater than one.  Otherwise,
 * the number of rows is always one.
 *
 * @return the number of items
 *
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public int getRowCount () {
	checkWidget ();
	if ((style & SWT.VERTICAL) != 0) {
		return (int)OS.SendMessage (handle, OS.TB_BUTTONCOUNT, 0, 0);
	}
	return (int)OS.SendMessage (handle, OS.TB_GETROWS, 0, 0);
}

ToolItem [] _getTabItemList () {
	if (tabItemList == null) return tabItemList;
	int count = 0;
	for (ToolItem item : tabItemList) {
		if (!item.isDisposed ()) count++;
	}
	if (count == tabItemList.length) return tabItemList;
	ToolItem [] newList = new ToolItem [count];
	int index = 0;
	for (ToolItem item : tabItemList) {
		if (!item.isDisposed ()) {
			newList [index++] = item;
		}
	}
	tabItemList = newList;
	return tabItemList;
}

/**
 * Searches the receiver's list starting at the first item
 * (index 0) until an item is found that is equal to the
 * argument, and returns the index of that item. If no item
 * is found, returns -1.
 *
 * @param item the search item
 * @return the index of the item
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the tool item is null</li>
 *    <li>ERROR_INVALID_ARGUMENT - if the tool item has been disposed</li>
 * </ul>
 * @exception SWTException <ul>
 *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 * </ul>
 */
public int indexOf (ToolItem item) {
	checkWidget ();
	if (item == null) error (SWT.ERROR_NULL_ARGUMENT);
	if (item.isDisposed()) error(SWT.ERROR_INVALID_ARGUMENT);
	return (int)OS.SendMessage (handle, OS.TB_COMMANDTOINDEX, item.id, 0);
}

void layoutItems () {
	clearSizeCache(true);
	/*
	* Feature in Windows.  When a tool bar has the style
	* TBSTYLE_LIST and has a drop down item, Window leaves
	* too much padding around the button.  This affects
	* every button in the tool bar and makes the preferred
	* height too big.  The fix is to set the TBSTYLE_LIST
	* when the tool bar contains both text and images.
	*
	* NOTE: Tool bars with CCS_VERT must have TBSTYLE_LIST
	* set before any item is added or the tool bar does
	* not lay out properly.  The work around does not run
	* in this case.
	*/
	if (OS.IsAppThemed ()) {
		if ((style & SWT.RIGHT) != 0 && (style & SWT.VERTICAL) == 0) {
			boolean hasText = false, hasImage = false;
			for (ToolItem item : items) {
				if (item != null) {
					if (!hasText) hasText = item.text.length () != 0;
					if (!hasImage) hasImage = item.image != null;
					if (hasText && hasImage) break;
				}
			}
			int oldBits = OS.GetWindowLong (handle, OS.GWL_STYLE), newBits = oldBits;
			if (hasText && hasImage) {
				newBits |= OS.TBSTYLE_LIST;
			} else {
				newBits &= ~OS.TBSTYLE_LIST;
			}
			if (newBits != oldBits) {
				setDropDownItems (false);
				OS.SetWindowLong (handle, OS.GWL_STYLE, newBits);
				/*
				* Feature in Windows.  For some reason, when the style
				* is changed to TBSTYLE_LIST, Windows does not lay out
				* the tool items.  The fix is to use WM_SETFONT to force
				* the tool bar to redraw and lay out.
				*/
				long hFont = OS.SendMessage (handle, OS.WM_GETFONT, 0, 0);
				OS.SendMessage (handle, OS.WM_SETFONT, hFont, 0);
				setDropDownItems (true);
			}
		}
	}

	/*
	 * Feature in Windows. When a tool bar is not flagged TBSTYLE_LIST already (SWT.RIGHT)
	 * and contains both text and images, but not text + image in a single item,
	 * Window leaves too much padding around the button.
	 * This affects every button in the tool bar and makes the preferred height too big.
	 * The fix is to set the TBSTYLE_LIST, so that Windows can condense the unused space.
	 */
	if (OS.IsAppThemed()) {
		if ((style & SWT.RIGHT) == 0 && (style & SWT.HORIZONTAL) != 0) {
			boolean hasText = false, hasImage = false, hasTextAndImageInSingleItem = false;
			for (ToolItem item : items) {
				if (item != null) {
					boolean itemHasText = false, itemHasImage = false;
					itemHasText = item.text.length() != 0;
					itemHasImage = item.image != null;
					if (!hasText) {
						hasText = itemHasText;
					}
					if (!hasImage) {
						hasImage = itemHasImage;
					}
					if (itemHasText && itemHasImage) {
						hasTextAndImageInSingleItem = true;
						break;
					}
				}
			}
			int oldBits = OS.GetWindowLong(handle, OS.GWL_STYLE), newBits = oldBits;
			if (hasText && hasImage && !hasTextAndImageInSingleItem) {
				newBits |= OS.TBSTYLE_LIST;
			} else {
				newBits &= ~OS.TBSTYLE_LIST;
			}
			if (newBits != oldBits) {
				setDropDownItems(false);
				OS.SetWindowLong(handle, OS.GWL_STYLE, newBits);
				/*
				 * Feature in Windows. For some reason, when the style is changed to
				 * TBSTYLE_LIST, Windows does not lay out the tool items. The fix is to use
				 * WM_SETFONT to force the tool bar to redraw and lay out.
				 */
				long hFont = OS.SendMessage(handle, OS.WM_GETFONT, 0, 0);
				OS.SendMessage(handle, OS.WM_SETFONT, hFont, 0);
				setDropDownItems(true);
			}
		}
	}

	if ((style & SWT.WRAP) != 0) {
		OS.SendMessage (handle, OS.TB_AUTOSIZE, 0, 0);
	}
	/*
	*  When the tool bar is vertical, make the width of each button
	*  be the width of the widest button in the tool bar.  Note that
	*  when the tool bar contains a drop down item, it needs to take
	*  into account extra padding.
	*/
	if ((style & SWT.VERTICAL) != 0) {
		int itemCount = (int)OS.SendMessage (handle, OS.TB_BUTTONCOUNT, 0, 0);
		if (itemCount > 1) {
			TBBUTTONINFO info = new TBBUTTONINFO ();
			info.cbSize = TBBUTTONINFO.sizeof;
			info.dwMask = OS.TBIF_SIZE;
			long size = OS.SendMessage (handle, OS.TB_GETBUTTONSIZE, 0, 0);
			info.cx = (short) OS.LOWORD (size);
			int index = 0, extraPadding = 0;
			while (index < items.length) {
				ToolItem item = items [index];
				if (item != null && (item.style & SWT.DROP_DOWN) != 0) {
					/*
					 * Specifying 1 pixel extra padding to avoid truncation
					 * of widest item in the tool-bar when a tool-bar has
					 * SWT.VERTICAL style and any of the items in the
					 * tool-bar has SWT.DROP_DOWN style, Refer bug#437206
					 */
					extraPadding = 1;
					break;
				}
				index++;
			}
			if (index < items.length) {
				long padding = OS.SendMessage (handle, OS.TB_GETPADDING, 0, 0);
				info.cx += OS.LOWORD (padding + extraPadding) * 2;
			}
			for (ToolItem item : items) {
				if (item != null && (item.style & SWT.SEPARATOR) == 0) {
					OS.SendMessage (handle, OS.TB_SETBUTTONINFO, item.id, info);
				}
			}
		}
	}

	/*
	* Feature on Windows. When SWT.WRAP or SWT.VERTICAL are set
	* the separator items with control are implemented using BTNS_BUTTON
	* instead of BTNS_SEP. When that is the case and TBSTYLE_LIST is
	* set, the layout of the ToolBar recalculates the width for all
	* BTNS_BUTTON based on the text and bitmap of the item.
	* This is not strictly wrong, but the user defined width for the
	* separators has to be respected if set.
	* The fix is to detect this case and reset the cx width for the item.
	*/
	if ((style & (SWT.WRAP | SWT.VERTICAL)) != 0) {
		int bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
		if ((bits & OS.TBSTYLE_LIST) != 0) {
			TBBUTTONINFO info = new TBBUTTONINFO ();
			info.cbSize = TBBUTTONINFO.sizeof;
			info.dwMask = OS.TBIF_SIZE;
			for (ToolItem item : items) {
				if (item != null && item.cx > 0) {
					info.cx = item.cx;
					OS.SendMessage (handle, OS.TB_SETBUTTONINFO, item.id, info);
				}
			}
		}
	}

	for (ToolItem item : items) {
		if (item != null) item.resizeControl ();
	}
}

@Override
boolean mnemonicHit (char ch) {
	int [] id = new int [1];
	if (OS.SendMessage (handle, OS.TB_MAPACCELERATOR, ch, id) == 0) {
		return false;
	}
	if ((style & SWT.FLAT) != 0 && !setTabGroupFocus ()) return false;
	int index = (int)OS.SendMessage (handle, OS.TB_COMMANDTOINDEX, id [0], 0);
	if (index == -1) return false;
	OS.SendMessage (handle, OS.TB_SETHOTITEM, index, 0);
	items [id [0]].click (false);
	return true;
}

@Override
boolean mnemonicMatch (char ch) {
	int [] id = new int [1];
	if (OS.SendMessage (handle, OS.TB_MAPACCELERATOR, ch, id) == 0) {
		return false;
	}
	/*
	* Feature in Windows.  TB_MAPACCELERATOR matches either the mnemonic
	* character or the first character in a tool item.  This behavior is
	* undocumented and unwanted.  The fix is to ensure that the tool item
	* contains a mnemonic when TB_MAPACCELERATOR returns true.
	*/
	int index = (int)OS.SendMessage (handle, OS.TB_COMMANDTOINDEX, id [0], 0);
	if (index == -1) return false;
	return findMnemonic (items [id [0]].text) != '\0';
}

@Override
void releaseChildren (boolean destroy) {
	if (items != null) {
		for (ToolItem item : items) {
			if (item != null && !item.isDisposed ()) {
				item.release (false);
			}
		}
		items = null;
	}
	super.releaseChildren (destroy);
}

@Override
void releaseWidget () {
	super.releaseWidget ();
	if (imageList != null) {
		OS.SendMessage (handle, OS.TB_SETIMAGELIST, 0, 0);
		display.releaseToolImageList (imageList);
	}
	if (hotImageList != null) {
		OS.SendMessage (handle, OS.TB_SETHOTIMAGELIST, 0, 0);
		display.releaseToolHotImageList (hotImageList);
	}
	if (disabledImageList != null) {
		OS.SendMessage (handle, OS.TB_SETDISABLEDIMAGELIST, 0, 0);
		display.releaseToolDisabledImageList (disabledImageList);
	}
	imageList = hotImageList = disabledImageList = null;
}

@Override
void removeControl (Control control) {
	super.removeControl (control);
	for (ToolItem item : items) {
		if (item != null && item.control == control) {
			item.setControl (null);
		}
	}
}

@Override
void reskinChildren (int flags) {
	if (items != null) {
		for (ToolItem item : items) {
			if (item != null) item.reskin (flags);
		}
	}
	super.reskinChildren (flags);
}

@Override
void setBackgroundImage (long hBitmap) {
	super.setBackgroundImage (hBitmap);
	setBackgroundTransparent (hBitmap != 0);
}

@Override
void setBackgroundPixel (int pixel) {
	super.setBackgroundPixel (pixel);
	setBackgroundTransparent (pixel != -1);
}

void setBackgroundTransparent (boolean transparent) {
	/*
	* Feature in Windows.  When TBSTYLE_TRANSPARENT is set
	* in a tool bar that is drawing a background, images in
	* the image list that include transparency information
	* do not draw correctly.  The fix is to clear and set
	* TBSTYLE_TRANSPARENT depending on the background color.
	*
	* NOTE:  This work around is unnecessary on XP.  The
	* TBSTYLE_TRANSPARENT style is never cleared on that
	* platform.
	*/
	if ((style & SWT.FLAT) != 0) {
		if (!OS.IsAppThemed ()) {
			int bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
			if (!transparent && findBackgroundControl () == null) {
				bits &= ~OS.TBSTYLE_TRANSPARENT;
			} else {
				bits |= OS.TBSTYLE_TRANSPARENT;
			}
			OS.SetWindowLong (handle, OS.GWL_STYLE, bits);
		}
	}
}

@Override
void setBoundsInPixels (int x, int y, int width, int height, int flags) {
	/*
	* Feature in Windows.  For some reason, when a tool bar is
	* repositioned more than once using DeferWindowPos () into
	* the same HDWP, the toolbar redraws more than once, defeating
	* the purpose of DeferWindowPos ().  The fix is to end the
	* deferred positioning before the next tool bar is added,
	* ensuring that only one tool bar position is deferred at
	* any given time.
	*/
	if (parent.lpwp != null) {
		if (getDrawing () && OS.IsWindowVisible (handle)) {
			parent.setResizeChildren (false);
			parent.setResizeChildren (true);
		}
	}
	super.setBoundsInPixels (x, y, width, height, flags);
}

@Override
void setDefaultFont () {
	super.setDefaultFont ();
	OS.SendMessage (handle, OS.TB_SETBITMAPSIZE, 0, 0);
	OS.SendMessage (handle, OS.TB_SETBUTTONSIZE, 0, 0);
}

void setDropDownItems (boolean set) {
	/*
	* Feature in Windows.  When the first button in a tool bar
	* is a drop down item, Window leaves too much padding around
	* the button.  This affects every button in the tool bar and
	* makes the preferred height too big.  The fix is clear the
	* BTNS_DROPDOWN before Windows lays out the tool bar and set
	* the bit afterwards.
	*
	* NOTE:  This work around only runs when the tool bar contains
	* only images.
	*/
	if (OS.IsAppThemed ()) {
		boolean hasText = false, hasImage = false;
		for (ToolItem item : items) {
			if (item != null) {
				if (!hasText) hasText = item.text.length () != 0;
				if (!hasImage) hasImage = item.image != null;
				if (hasText && hasImage) break;
			}
		}
		if (hasImage && !hasText) {
			for (ToolItem item : items) {
				if (item != null && (item.style & SWT.DROP_DOWN) != 0) {
					TBBUTTONINFO info = new TBBUTTONINFO ();
					info.cbSize = TBBUTTONINFO.sizeof;
					info.dwMask = OS.TBIF_STYLE;
					OS.SendMessage (handle, OS.TB_GETBUTTONINFO, item.id, info);
					if (set) {
						info.fsStyle |= OS.BTNS_DROPDOWN;
					} else {
						info.fsStyle &= ~OS.BTNS_DROPDOWN;
					}
					OS.SendMessage (handle, OS.TB_SETBUTTONINFO, item.id, info);
				}
			}
		}
	}
}

void setDisabledImageList (ImageList imageList) {
	long hImageList = 0;
	if ((disabledImageList = imageList) != null) {
		hImageList = OS.SendMessage(handle, OS.TB_GETDISABLEDIMAGELIST, 0, 0);
		long newImageList = disabledImageList.getHandle(getAutoscalingZoom());
		if (hImageList == newImageList) return;
		hImageList = newImageList;
	}
	setDropDownItems (false);
	OS.SendMessage (handle, OS.TB_SETDISABLEDIMAGELIST, 0, hImageList);
	setDropDownItems (true);
}

@Override
public void setFont (Font font) {
	checkWidget ();
	setDropDownItems (false);
	super.setFont (font);
	setDropDownItems (true);
	/*
	* Bug in Windows.  When WM_SETFONT is sent to a tool bar
	* that contains only separators, causes the bitmap and button
	* sizes to be set.  The fix is to reset these sizes after the font
	* has been changed when the tool bar contains only separators.
	*/
	int index = 0;
	int mask = SWT.PUSH | SWT.CHECK | SWT.RADIO | SWT.DROP_DOWN;
	while (index < items.length) {
		ToolItem item = items [index];
		if (item != null && (item.style & mask) != 0) break;
		index++;
	}
	if (index == items.length) {
		OS.SendMessage (handle, OS.TB_SETBITMAPSIZE, 0, 0);
		OS.SendMessage (handle, OS.TB_SETBUTTONSIZE, 0, 0);
	}
	layoutItems ();
}

void setHotImageList (ImageList imageList) {
	long hImageList = 0;
	if ((hotImageList = imageList) != null) {
		hImageList = OS.SendMessage(handle, OS.TB_GETHOTIMAGELIST, 0, 0);
		long newImageList = hotImageList.getHandle(getAutoscalingZoom());
		if (hImageList == newImageList) return;
		hImageList = newImageList;
	}
	setDropDownItems (false);
	OS.SendMessage (handle, OS.TB_SETHOTIMAGELIST, 0, hImageList);
	setDropDownItems (true);
}

void setImageList (ImageList imageList) {
	long hImageList = 0;
	if ((this.imageList = imageList) != null) {
		hImageList = OS.SendMessage(handle, OS.TB_GETIMAGELIST, 0, 0);
		long newImageList = imageList.getHandle(getAutoscalingZoom());
		if (hImageList == newImageList) return;
		hImageList = newImageList;
	}
	setDropDownItems (false);
	OS.SendMessage (handle, OS.TB_SETIMAGELIST, 0, hImageList);
	setDropDownItems (true);
}

@Override
public boolean setParent (Composite parent) {
	checkWidget ();
	if (!super.setParent (parent)) return false;
	long hwndParent = parent.handle;
	OS.SendMessage (handle, OS.TB_SETPARENT, hwndParent, 0);
	/*
	* Bug in Windows.  When a tool bar is reparented, the tooltip
	* control that is automatically created for the item is not
	* reparented to the new shell.  The fix is to move the tooltip
	* over using SetWindowLongPtr().  Note that for some reason,
	* SetParent() does not work.
	*/
	long hwndShell = parent.getShell ().handle;
	long hwndToolTip = OS.SendMessage (handle, OS.TB_GETTOOLTIPS, 0, 0);
	OS.SetWindowLongPtr (hwndToolTip, OS.GWLP_HWNDPARENT, hwndShell);
	return true;
}

@Override
public void setRedraw (boolean redraw) {
	checkWidget ();
	setDropDownItems (false);
	super.setRedraw (redraw);
	setDropDownItems (true);
}

void setRowCount (int count) {
	if ((style & SWT.VERTICAL) != 0) {
		/*
		* Feature in Windows.  When the TB_SETROWS is used to set the
		* number of rows in a tool bar, the tool bar is resized to show
		* the items.  This is unexpected.  The fix is to save and restore
		* the current size of the tool bar.
		*/
		RECT rect = new RECT ();
		OS.GetWindowRect (handle, rect);
		OS.MapWindowPoints (0, parent.handle, rect, 2);
		ignoreResize = true;
		/*
		* Feature in Windows.  When the last button in a tool bar has the
		* style BTNS_SEP and TB_SETROWS is used to set the number of rows
		* in the tool bar, depending on the number of buttons, the toolbar
		* will wrap items with the style BTNS_CHECK, even when the fLarger
		* flags is used to force the number of rows to be larger than the
		* number of items.  The fix is to set the number of rows to be two
		* larger than the actual number of rows in the tool bar.  When items
		* are being added, as long as the number of rows is at least one
		* item larger than the count, the tool bar is laid out properly.
		* When items are being removed, setting the number of rows to be
		* one more than the item count has no effect.  The number of rows
		* is already one more causing TB_SETROWS to do nothing.  Therefore,
		* choosing two instead of one as the row increment fixes both cases.
		*/
		count += 2;
		OS.SendMessage (handle, OS.TB_SETROWS, OS.MAKEWPARAM (count, 1), 0);
		int flags = OS.SWP_NOACTIVATE | OS.SWP_NOMOVE | OS.SWP_NOZORDER;
		OS.SetWindowPos (handle, 0, 0, 0, rect.right - rect.left, rect.bottom - rect.top, flags);
		ignoreResize = false;
	}
}

/*public*/ void setTabItemList (ToolItem [] tabList) {
	checkWidget ();
	if (tabList != null) {
		for (ToolItem item : tabList) {
			if (item == null) error (SWT.ERROR_INVALID_ARGUMENT);
			if (item.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT);
			if (item.parent != this) error (SWT.ERROR_INVALID_PARENT);
		}
		ToolItem [] newList = new ToolItem [tabList.length];
		System.arraycopy (tabList, 0, newList, 0, tabList.length);
		tabList = newList;
	}
	this.tabItemList = tabList;
}

@Override
boolean setTabItemFocus () {
	int index = 0;
	while (index < items.length) {
		ToolItem item = items [index];
		if (item != null && (item.style & SWT.SEPARATOR) == 0) {
			if (item.getEnabled ()) break;
		}
		index++;
	}
	if (index == items.length) return false;
	return super.setTabItemFocus ();
}

@Override
boolean updateTextDirection(int textDirection) {
	if (super.updateTextDirection(textDirection)) {
		ToolItem [] items = _getItems ();
		int i = items.length;
		while (i-- > 0) {
			items[i].updateTextDirection(style & SWT.FLIP_TEXT_DIRECTION);
		}
		return true;
	}
	return false;
}

@Override
String toolTipText (NMTTDISPINFO hdr) {
	if ((hdr.uFlags & OS.TTF_IDISHWND) != 0) {
		return null;
	}
	/*
	* Bug in Windows.  On Windows XP, when TB_SETHOTITEM is
	* used to set the hot item, the tool bar control attempts
	* to display the tool tip, even when the cursor is not in
	* the hot item.  The fix is to detect this case and fail to
	* provide the string, causing no tool tip to be displayed.
	*/
	if (!hasCursor ()) return ""; //$NON-NLS-1$
	int index = (int)hdr.idFrom;
	long hwndToolTip = OS.SendMessage (handle, OS.TB_GETTOOLTIPS, 0, 0);
	if (hwndToolTip == hdr.hwndFrom) {
		if (currentToolItemToolTip != hwndToolTip) {
			maybeEnableDarkSystemTheme(hdr.hwndFrom);
			currentToolItemToolTip = hdr.hwndFrom;
		}
		/*
		* Bug in Windows. For some reason the reading order
		* in NMTTDISPINFO is sometimes set incorrectly.  The
		* reading order seems to change every time the mouse
		* enters the control from the top edge.  The fix is
		* to explicitly set TTF_RTLREADING.
		*/
		int flags = SWT.RIGHT_TO_LEFT | SWT.FLIP_TEXT_DIRECTION;
		if ((style & flags) != 0 && (style & flags) != flags) {
			hdr.uFlags |= OS.TTF_RTLREADING;
		} else {
			hdr.uFlags &= ~OS.TTF_RTLREADING;
		}
		if (toolTipText != null) return ""; //$NON-NLS-1$
		if (0 <= index && index < items.length) {
			ToolItem item = items [index];
			if (item != null) {
				/*
				* Bug in Windows.  When the  arrow keys are used to change
				* the hot item, for some reason, Windows displays the tool
				* tip for the hot item in at (0, 0) on the screen rather
				* than next to the current hot item.  This fix is to disallow
				* tool tips while the user is traversing with the arrow keys.
				*/
				if (lastArrowId != -1) return "";
				return item.toolTipText;
			}
		}
	}
	return super.toolTipText (hdr);
}

@Override
void updateOrientation () {
	super.updateOrientation ();
	if (imageList != null) {
		Point sizeInPoints = imageList.getImageSize();
		ImageList newImageList = display.getImageListToolBar (style & SWT.RIGHT_TO_LEFT, sizeInPoints.x, sizeInPoints.y, getAutoscalingZoom());
		ImageList newHotImageList = display.getImageListToolBarHot (style & SWT.RIGHT_TO_LEFT, sizeInPoints.x, sizeInPoints.y, getAutoscalingZoom());
		ImageList newDisabledImageList = display.getImageListToolBarDisabled (style & SWT.RIGHT_TO_LEFT, sizeInPoints.x, sizeInPoints.y, getAutoscalingZoom());
		TBBUTTONINFO info = new TBBUTTONINFO ();
		info.cbSize = TBBUTTONINFO.sizeof;
		info.dwMask = OS.TBIF_IMAGE;
		int count = (int)OS.SendMessage (handle, OS.TB_BUTTONCOUNT, 0, 0);
		for (int i=0; i<count; i++) {
			ToolItem item = items [i];
			if ((item.style & SWT.SEPARATOR) != 0) continue;
			if (item.image == null) continue;
			OS.SendMessage (handle, OS.TB_GETBUTTONINFO, item.id, info);
			if (info.iImage != OS.I_IMAGENONE) {
				Image image = imageList.get(info.iImage);
				Image hot = hotImageList.get(info.iImage);
				Image disabled = disabledImageList.get(info.iImage);
				imageList.put(info.iImage, null);
				hotImageList.put(info.iImage, null);
				disabledImageList.put(info.iImage, null);
				info.iImage = newImageList.add(image);
				newHotImageList.add(hot);
				newDisabledImageList.add(disabled);
				OS.SendMessage (handle, OS.TB_SETBUTTONINFO, item.id, info);
			}
		}
		display.releaseToolImageList (imageList);
		display.releaseToolHotImageList (hotImageList);
		display.releaseToolDisabledImageList (disabledImageList);
		OS.SendMessage (handle, OS.TB_SETIMAGELIST, 0, newImageList.getHandle(getAutoscalingZoom()));
		OS.SendMessage (handle, OS.TB_SETHOTIMAGELIST, 0, newHotImageList.getHandle(getAutoscalingZoom()));
		OS.SendMessage (handle, OS.TB_SETDISABLEDIMAGELIST, 0, newDisabledImageList.getHandle(getAutoscalingZoom()));
		imageList = newImageList;
		hotImageList = newHotImageList;
		disabledImageList = newDisabledImageList;
		OS.InvalidateRect (handle, null, true);
	}
}

@Override
int widgetStyle () {
	int bits = super.widgetStyle () | OS.CCS_NORESIZE | OS.TBSTYLE_TOOLTIPS | OS.TBSTYLE_CUSTOMERASE;
	if (OS.IsAppThemed ()) bits |= OS.TBSTYLE_TRANSPARENT;
	if ((style & SWT.SHADOW_OUT) == 0) bits |= OS.CCS_NODIVIDER;
	if ((style & SWT.WRAP) != 0) bits |= OS.TBSTYLE_WRAPABLE;
	if ((style & SWT.FLAT) != 0) bits |= OS.TBSTYLE_FLAT;
	/*
	* Feature in Windows.  When a tool bar has the style
	* TBSTYLE_LIST and has a drop down item, Window leaves
	* too much padding around the button.  This affects
	* every button in the tool bar and makes the preferred
	* height too big.  The fix is to set the TBSTYLE_LIST
	* when the tool bar contains both text and images.
	*
	* NOTE: Tool bars with CCS_VERT must have TBSTYLE_LIST
	* set before any item is added or the tool bar does
	* not lay out properly.  The work around does not run
	* in this case.
	*/
	if (!OS.IsAppThemed ()) {
		if ((style & SWT.RIGHT) != 0) bits |= OS.TBSTYLE_LIST;
	}
	return bits;
}

@Override
TCHAR windowClass () {
	return ToolBarClass;
}

@Override
long windowProc () {
	return ToolBarProc;
}

@Override
LRESULT WM_CAPTURECHANGED (long wParam, long lParam) {
	LRESULT result = super.WM_CAPTURECHANGED (wParam, lParam);
	if (result != null) return result;
	/*
	* Bug in Windows.  When the tool bar loses capture while an
	* item is pressed, the item remains pressed.  The fix is
	* unpress all items using TB_SETSTATE and TBSTATE_PRESSED.
	*/
	for (ToolItem item : items) {
		if (item != null) {
			int fsState = (int)OS.SendMessage (handle, OS.TB_GETSTATE, item.id, 0);
			if ((fsState & OS.TBSTATE_PRESSED) != 0) {
				fsState &= ~OS.TBSTATE_PRESSED;
				OS.SendMessage (handle, OS.TB_SETSTATE, item.id, fsState);
			}
		}
	}
	return result;
}

@Override
LRESULT WM_CHAR (long wParam, long lParam) {
	LRESULT result = super.WM_CHAR (wParam, lParam);
	if (result != null) return result;
	switch ((int)wParam) {
		case ' ':
			int index = (int)OS.SendMessage (handle, OS.TB_GETHOTITEM, 0, 0);
			if (index != -1) {
				TBBUTTON lpButton = new TBBUTTON ();
				long code = OS.SendMessage (handle, OS.TB_GETBUTTON, index, lpButton);
				if (code != 0) {
					items [lpButton.idCommand].click (false);
					return LRESULT.ZERO;
				}
			}
	}
	return result;
}

@Override
LRESULT WM_COMMAND (long wParam, long lParam) {
	/*
	* Feature in Windows.  When the toolbar window
	* proc processes WM_COMMAND, it forwards this
	* message to its parent.  This is done so that
	* children of this control that send this message
	* type to their parent will notify not only
	* this control but also the parent of this control,
	* which is typically the application window and
	* the window that is looking for the message.
	* If the control did not forward the message,
	* applications would have to subclass the control
	* window to see the message. Because the control
	* window is subclassed by SWT, the message
	* is delivered twice, once by SWT and once when
	* the message is forwarded by the window proc.
	* The fix is to avoid calling the window proc
	* for this control.
	*/
	LRESULT result = super.WM_COMMAND (wParam, lParam);
	if (result != null) return result;
	return LRESULT.ZERO;
}

@Override
LRESULT WM_GETDLGCODE (long wParam, long lParam) {
	LRESULT result = super.WM_GETDLGCODE (wParam, lParam);
	/*
	* Return DLGC_BUTTON so that mnemonics will be
	* processed without needing to press the ALT key
	* when the widget has focus.
	*/
	if (result != null) return result;
	return new LRESULT (OS.DLGC_BUTTON | OS.DLGC_WANTARROWS);
}

@Override
LRESULT WM_KEYDOWN (long wParam, long lParam) {
	LRESULT result = super.WM_KEYDOWN (wParam, lParam);
	if (result != null) return result;
	switch ((int)wParam) {
		case OS.VK_SPACE:
			/*
			* Ensure that the window proc does not process VK_SPACE
			* so that it can be handled in WM_CHAR.  This allows the
			* application the opportunity to cancel the operation.
			*/
			return LRESULT.ZERO;
	}
	return result;
}

@Override
LRESULT WM_KILLFOCUS (long wParam, long lParam) {
	int index = (int)OS.SendMessage (handle, OS.TB_GETHOTITEM, 0, 0);
	TBBUTTON lpButton = new TBBUTTON ();
	long code = OS.SendMessage (handle, OS.TB_GETBUTTON, index, lpButton);
	if (code != 0) lastFocusId = lpButton.idCommand;
	return super.WM_KILLFOCUS (wParam, lParam);
}

@Override
LRESULT WM_LBUTTONDOWN (long wParam, long lParam) {
	if (ignoreMouse) return null;
	return super.WM_LBUTTONDOWN (wParam, lParam);
}

@Override
LRESULT WM_LBUTTONUP (long wParam, long lParam) {
	if (ignoreMouse) return null;
	return super.WM_LBUTTONUP (wParam, lParam);
}

@Override
LRESULT WM_MOUSELEAVE (long wParam, long lParam) {
	LRESULT result = super.WM_MOUSELEAVE (wParam, lParam);
	if (result != null) return result;
	/*
	* Bug in Windows.  On XP, when a tooltip is
	* hidden due to a time out or mouse press,
	* the tooltip remains active although no
	* longer visible and won't show again until
	* another tooltip becomes active.  If there
	* is only one tooltip in the window,  it will
	* never show again.  The fix is to remove the
	* current tooltip and add it again every time
	* the mouse leaves the control.
	*/
	TOOLINFO lpti = new TOOLINFO ();
	lpti.cbSize = TOOLINFO.sizeof;
	long hwndToolTip = OS.SendMessage (handle, OS.TB_GETTOOLTIPS, 0, 0);
	if (OS.SendMessage (hwndToolTip, OS.TTM_GETCURRENTTOOL, 0, lpti) != 0) {
		if ((lpti.uFlags & OS.TTF_IDISHWND) == 0) {
			OS.SendMessage (hwndToolTip, OS.TTM_DELTOOL, 0, lpti);
			OS.SendMessage (hwndToolTip, OS.TTM_ADDTOOL, 0, lpti);
		}
	}
	return result;
}

@Override
LRESULT WM_MOUSEMOVE (long wParam, long lParam) {
	if (OS.GetMessagePos () != display.lastMouse) lastArrowId = -1;
	return super.WM_MOUSEMOVE (wParam, lParam);
}

@Override
LRESULT WM_NOTIFY (long wParam, long lParam) {
	/*
	* Feature in Windows.  When the toolbar window
	* proc processes WM_NOTIFY, it forwards this
	* message to its parent.  This is done so that
	* children of this control that send this message
	* type to their parent will notify not only
	* this control but also the parent of this control,
	* which is typically the application window and
	* the window that is looking for the message.
	* If the control did not forward the message,
	* applications would have to subclass the control
	* window to see the message. Because the control
	* window is subclassed by SWT, the message
	* is delivered twice, once by SWT and once when
	* the message is forwarded by the window proc.
	* The fix is to avoid calling the window proc
	* for this control.
	*/
	LRESULT result = super.WM_NOTIFY (wParam, lParam);
	if (result != null) return result;
	return LRESULT.ZERO;
}

@Override
LRESULT WM_SETFOCUS (long wParam, long lParam) {
	LRESULT result = super.WM_SETFOCUS (wParam, lParam);
	if (lastFocusId != -1 && handle == OS.GetFocus ()) {
		int index = (int)OS.SendMessage (handle, OS.TB_COMMANDTOINDEX, lastFocusId, 0);
		OS.SendMessage (handle, OS.TB_SETHOTITEM, index, 0);
	}
	return result;
}

@Override
LRESULT WM_SIZE (long wParam, long lParam) {
	if (ignoreResize) {
		long code = callWindowProc (handle, OS.WM_SIZE, wParam, lParam);
		if (code == 0) return LRESULT.ZERO;
		return new LRESULT (code);
	}
	LRESULT result = super.WM_SIZE (wParam, lParam);
	if (isDisposed ()) return result;
	/*
	* Bug in Windows.  The code in Windows that determines
	* when tool items should wrap seems to use the window
	* bounds rather than the client area.  Unfortunately,
	* tool bars with the style TBSTYLE_EX_HIDECLIPPEDBUTTONS
	* use the client area.  This means that buttons which
	* overlap the border are hidden before they are wrapped.
	* The fix is to compute TBSTYLE_EX_HIDECLIPPEDBUTTONS
	* and set it each time the tool bar is resized.
	*/
	if ((style & SWT.BORDER) != 0 && (style & SWT.WRAP) != 0) {
		RECT windowRect = new RECT ();
		OS.GetWindowRect (handle, windowRect);
		int index = 0, border = getBorderWidthInPixels () * 2;
		RECT rect = new RECT ();
		int count = (int)OS.SendMessage (handle, OS.TB_BUTTONCOUNT, 0, 0);
		while (index < count) {
			OS.SendMessage (handle, OS.TB_GETITEMRECT, index, rect);
			OS.MapWindowPoints (handle, 0, rect, 2);
			if (rect.right > windowRect.right - border * 2) break;
			index++;
		}
		int bits = (int)OS.SendMessage (handle, OS.TB_GETEXTENDEDSTYLE, 0, 0);
		if (index == count) {
			bits |= OS.TBSTYLE_EX_HIDECLIPPEDBUTTONS;
		} else {
			bits &= ~OS.TBSTYLE_EX_HIDECLIPPEDBUTTONS;
		}
		OS.SendMessage (handle, OS.TB_SETEXTENDEDSTYLE, 0, bits);
	}
	layoutItems ();
	return result;
}

@Override
LRESULT WM_WINDOWPOSCHANGING (long wParam, long lParam) {
	LRESULT result = super.WM_WINDOWPOSCHANGING (wParam, lParam);
	if (result != null) return result;
	if (ignoreResize) return result;
	/*
	* Bug in Windows.  When a flat tool bar is wrapped,
	* Windows draws a horizontal separator between the
	* rows.  The tool bar does not draw the first or
	* the last two pixels of this separator.  When the
	* toolbar is resized to be bigger, only the new
	* area is drawn and the last two pixels, which are
	* blank are drawn over by separator.  This leaves
	* garbage on the screen.  The fix is to damage the
	* pixels.
	*/
	if (!getDrawing ()) return result;
	if ((style & SWT.WRAP) == 0) return result;
	if (!OS.IsWindowVisible (handle)) return result;
	if (OS.SendMessage (handle, OS.TB_GETROWS, 0, 0) == 1) {
		return result;
	}
	WINDOWPOS lpwp = new WINDOWPOS ();
	OS.MoveMemory (lpwp, lParam, WINDOWPOS.sizeof);
	if ((lpwp.flags & (OS.SWP_NOSIZE | OS.SWP_NOREDRAW)) != 0) {
		return result;
	}
	RECT oldRect = new RECT ();
	OS.GetClientRect (handle, oldRect);
	RECT newRect = new RECT ();
	OS.SetRect (newRect, 0, 0, lpwp.cx, lpwp.cy);
	OS.SendMessage (handle, OS.WM_NCCALCSIZE, 0, newRect);
	int oldWidth = oldRect.right - oldRect.left;
	int newWidth = newRect.right - newRect.left;
	if (newWidth > oldWidth) {
		RECT rect = new RECT ();
		int newHeight = newRect.bottom - newRect.top;
		OS.SetRect (rect, oldWidth - 2, 0, oldWidth, newHeight);
		OS.InvalidateRect (handle, rect, false);
	}
	return result;
}

@Override
LRESULT wmCommandChild (long wParam, long lParam) {
	ToolItem child = items [OS.LOWORD (wParam)];
	if (child == null) return null;
	return child.wmCommandChild (wParam, lParam);
}

int getForegroundPixel (ToolItem item) {
	if (item != null && item.foreground != -1) {
		return item.foreground;
	}
	return getForegroundPixel ();
}

int getBackgroundPixel (ToolItem item) {
	if (item != null && item.background != -1) {
		return item.background;
	}
	return getBackgroundPixel ();
}

@Override
LRESULT wmNotifyChild (NMHDR hdr, long wParam, long lParam) {
	ToolItem child;
	switch (hdr.code) {
		case OS.TBN_DROPDOWN:
			NMTOOLBAR lpnmtb = new NMTOOLBAR ();
			OS.MoveMemory (lpnmtb, lParam, NMTOOLBAR.sizeof);
			child = items [lpnmtb.iItem];
			if (child != null) {
				Event event = new Event ();
				event.detail = SWT.ARROW;
				int index = (int)OS.SendMessage (handle, OS.TB_COMMANDTOINDEX, lpnmtb.iItem, 0);
				RECT rect = new RECT ();
				OS.SendMessage (handle, OS.TB_GETITEMRECT, index, rect);
				int zoom = getAutoscalingZoom();
				event.setLocation(DPIUtil.pixelToPoint(rect.left, zoom), DPIUtil.pixelToPoint(rect.bottom, zoom));
				child.sendSelectionEvent (SWT.Selection, event, false);
			}
			break;
		case OS.NM_CUSTOMDRAW:
			/*
			* Bug in Windows.  For some reason, under the XP Silver
			* theme, tool bars continue to draw using the gray color
			* from the default Blue theme.  The fix is to draw the
			* background.
			*/
			NMTBCUSTOMDRAW nmcd = new NMTBCUSTOMDRAW ();
			OS.MoveMemory (nmcd, lParam, NMTBCUSTOMDRAW.sizeof);
			child = items [(int) nmcd.dwItemSpec];
//			if (drawCount != 0 || !OS.IsWindowVisible (handle)) {
//				if (OS.WindowFromDC (nmcd.hdc) == handle) break;
//			}
			switch (nmcd.dwDrawStage) {
				case OS.CDDS_PREERASE: {
					/*
					* Bug in Windows.  When a tool bar does not have the style
					* TBSTYLE_FLAT, the rectangle to be erased in CDDS_PREERASE
					* is empty.  The fix is to draw the whole client area.
					*/
					int bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
					if ((bits & OS.TBSTYLE_FLAT) == 0) {
						drawBackground (nmcd.hdc);
					} else {
						RECT rect = new RECT ();
						OS.SetRect (rect, nmcd.left, nmcd.top, nmcd.right, nmcd.bottom);
						drawBackground (nmcd.hdc, rect);
					}
					return new LRESULT (OS.CDRF_SKIPDEFAULT);
				}
				case OS.CDDS_PREPAINT: {
					long result = OS.CDRF_DODEFAULT;
					if (background != -1 || (foreground != -1 && OS.IsWindowEnabled (handle)) || (state & CUSTOM_DRAW_ITEM) != 0 || display.useDarkModeExplorerTheme) {
						result = OS.CDRF_NOTIFYITEMDRAW;
					}
					return new LRESULT (result);
				}
				case OS.CDDS_ITEMPREPAINT: {
					long result = OS.TBCDRF_USECDCOLORS;
					int bgPixel = getBackgroundPixel (child);
					nmcd.clrBtnFace = bgPixel;
					nmcd.clrBtnHighlight = getDifferentColor (bgPixel);
					nmcd.clrText = getForegroundPixel (child);
					OS.MoveMemory (lParam, nmcd, NMTBCUSTOMDRAW.sizeof);
					if (display.useDarkModeExplorerTheme && (nmcd.uItemState & (OS.CDIS_CHECKED | OS.CDIS_HOT)) != 0) {
						int pixel;
						if ((nmcd.uItemState & OS.CDIS_CHECKED) != 0) {
							pixel = getDifferentColor (bgPixel);
						} else {
							pixel = getSlightlyDifferentColor (bgPixel);
						}
						RECT rect = new RECT (nmcd.left, nmcd.top, nmcd.right, nmcd.bottom);
						OS.SetDCBrushColor (nmcd.hdc, pixel);
						OS.FillRect (nmcd.hdc, rect, OS.GetStockObject (OS.DC_BRUSH));
						result |= OS.TBCDRF_NOBACKGROUND;
					} else if (child != null && child.background != -1) {
						RECT rect = new RECT (nmcd.left, nmcd.top, nmcd.right, nmcd.bottom);
						OS.SetDCBrushColor (nmcd.hdc, child.background);
						OS.FillRect (nmcd.hdc, rect, OS.GetStockObject (OS.DC_BRUSH));
					}
					return new LRESULT (result);
				}
			}
			break;
		case OS.TBN_HOTITEMCHANGE:
			NMTBHOTITEM lpnmhi = new NMTBHOTITEM ();
			OS.MoveMemory (lpnmhi, lParam, NMTBHOTITEM.sizeof);
			switch (lpnmhi.dwFlags) {
				case OS.HICF_MOUSE: {
					/*
					* Bug in Windows.  When the tool bar has focus, a mouse is
					* in an item and hover help for that item is displayed and
					* then the arrow keys are used to change the hot item,
					* for some reason, Windows snaps the hot item back to the
					* one that is under the mouse.  The fix is to disallow
					* hot item changes when the user is traversing using the
					* arrow keys.
					*/
					if (lastArrowId != -1) return LRESULT.ONE;
					break;
				}
				case OS.HICF_ARROWKEYS:	{
					RECT client = new RECT ();
					OS.GetClientRect (handle, client);
					int index = (int)OS.SendMessage (handle, OS.TB_COMMANDTOINDEX, lpnmhi.idNew, 0);
					RECT rect = new RECT ();
					OS.SendMessage (handle, OS.TB_GETITEMRECT, index, rect);
					if (rect.right > client.right || rect.bottom > client.bottom) {
						return LRESULT.ONE;
					}
					lastArrowId = lpnmhi.idNew;
					break;
				}
				default:
					lastArrowId = -1;
			}
			if ((lpnmhi.dwFlags & OS.HICF_LEAVING) == 0) {
				lastHotId = lpnmhi.idNew;
			}
			break;
	}
	return super.wmNotifyChild (hdr, wParam, lParam);
}

@Override
void handleDPIChange(Event event, float scalingFactor) {
	super.handleDPIChange(event, scalingFactor);
	ToolItem[] toolItems = _getItems();
	var seperatorWidth = new int[toolItems.length];
	int itemCount = toolItems.length;

	if (itemCount == 0) {
		return;
	}

	record ToolItemData(ToolItem toolItem, TBBUTTON button) {
	}

	// Remove and re-add all button the let Windows resize the tool bar
	Stack<ToolItemData> buttondata = new Stack<>();
	for (int i = itemCount - 1; i >= 0; i--) {
		TBBUTTON lpButton = new TBBUTTON ();
		OS.SendMessage (handle, OS.TB_GETBUTTON, i, lpButton);
		ToolItem item = toolItems[i];
		if ((item.style & SWT.SEPARATOR) != 0 && item.getControl() != null) {
			// Take note of widths of separators with control, so they can be resized
			// at the end
			seperatorWidth[i] = item.getWidth();
		}
		item.notifyListeners(SWT.ZoomChanged, event);
		buttondata.push(new ToolItemData(item, lpButton));
		OS.SendMessage(handle, OS.TB_DELETEBUTTON, i, 0);
	}
	OS.SendMessage(handle, OS.TB_BUTTONSTRUCTSIZE, TBBUTTON.sizeof, 0);
	while (!buttondata.isEmpty()) {
		ToolItemData itemData = buttondata.pop();
		OS.SendMessage(handle, OS.TB_ADDBUTTONS, 1, itemData.button);
		ToolItem item = itemData.toolItem;
		if (item != null) {
			// The text is not retained correctly, so we need to reset it
			String text = item.getText();
			if (text != null) {
				item.setText("");
				item.setText(text);
			}
		}
	}
	for (int i = 0; i < itemCount; i++) {
		ToolItem item = toolItems[i];
		// If the separator is used with a control, we must reset the size to the cached value,
		// cause windows will treat the separator as normal separator and shrinks it accordingly
		if ((item.style & SWT.SEPARATOR) != 0 && item.getControl() != null) {
			item.setWidth(seperatorWidth[i]);
		}
	}

	// Refresh the image lists so the image list for the correct zoom is used
	setImageList(getImageList());
	setDisabledImageList(getDisabledImageList());
	setHotImageList(getHotImageList());
	OS.SendMessage(handle, OS.TB_AUTOSIZE, 0, 0);
	clearSizeCache(true);
}
}
