/*******************************************************************************
* Copyright (c) 2000, 2014 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 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.
*
* The item children that may be added to instances of this class
* must be of type ToolItem
.
*
* Note that although this class is a subclass of Composite
,
* it does not make sense to add Control
children to it,
* or set a layout on it.
*
*
* - Styles:
* - FLAT, WRAP, RIGHT, HORIZONTAL, VERTICAL, SHADOW_OUT
* - Events:
* - (none)
*
*
* Note: Only one of the styles HORIZONTAL and VERTICAL may be specified.
*
* IMPORTANT: This class is not intended to be subclassed.
*
*
* @see ToolBar, ToolItem snippets
* @see SWT Example: ControlExample
* @see Sample code and further information
* @noextend This class is not intended to be subclassed by clients.
*/
public class ToolBar extends Composite {
int lastFocusId, lastArrowId, lastHotId;
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.
*
* The style value is either one of the style constants defined in
* class SWT
which is applicable to instances of this
* class, or must be built by bitwise OR'ing together
* (that is, using the int
"|" operator) two or more
* of those SWT
style constants. The class description
* lists the style constants that are applicable to the class.
* Style bits are also inherited from superclasses.
*
*
* @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
* - ERROR_NULL_ARGUMENT - if the parent is null
*
* @exception SWTException
* - ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent
* - ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass
*
*
* @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 Point computeSizeInPixels (int wHint, int hHint, boolean changed) {
int width = 0, height = 0;
if ((style & SWT.VERTICAL) != 0) {
RECT rect = new RECT ();
TBBUTTON lpButton = new TBBUTTON ();
int count = (int)OS.SendMessage (handle, OS.TB_BUTTONCOUNT, 0, 0);
for (int i=0; i= 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 (int i=0; i
* ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)
*
* @exception SWTException
* - ERROR_WIDGET_DISPOSED - if the receiver has been disposed
* - ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
*
*/
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
* - ERROR_NULL_ARGUMENT - if the point is null
*
* @exception SWTException
* - ERROR_WIDGET_DISPOSED - if the receiver has been disposed
* - ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
*
*/
public ToolItem getItem (Point point) {
checkWidget ();
if (point == null) error (SWT.ERROR_NULL_ARGUMENT);
return getItemInPixels(DPIUtil.autoScaleUp(point));
}
ToolItem getItemInPixels (Point point) {
ToolItem [] items = getItems ();
for (int i=0; i
* ERROR_WIDGET_DISPOSED - if the receiver has been disposed
* ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
*
*/
public int getItemCount () {
checkWidget ();
return (int)OS.SendMessage (handle, OS.TB_BUTTONCOUNT, 0, 0);
}
/**
* Returns an array of ToolItem
s which are the items
* in the receiver.
*
* 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.
*
*
* @return the items in the receiver
*
* @exception SWTException
* - ERROR_WIDGET_DISPOSED - if the receiver has been disposed
* - ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
*
*/
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; iWRAP 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
* - ERROR_WIDGET_DISPOSED - if the receiver has been disposed
* - ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
*
*/
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 (int i=0; i
* ERROR_NULL_ARGUMENT - if the tool item is null
* ERROR_INVALID_ARGUMENT - if the tool item has been disposed
*
* @exception SWTException
* - ERROR_WIDGET_DISPOSED - if the receiver has been disposed
* - ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
*
*/
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 () {
/*
* 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 (int i=0; i 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 (int i=0; i 0) {
info.cx = item.cx;
OS.SendMessage (handle, OS.TB_SETBUTTONINFO, item.id, info);
}
}
}
}
for (int i=0; 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) {
/*
* 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 size = imageList.getImageSize ();
ImageList newImageList = display.getImageListToolBar (style & SWT.RIGHT_TO_LEFT, size.x, size.y);
ImageList newHotImageList = display.getImageListToolBarHot (style & SWT.RIGHT_TO_LEFT, size.x, size.y);
ImageList newDisabledImageList = display.getImageListToolBarDisabled (style & SWT.RIGHT_TO_LEFT, size.x, size.y);
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 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);
}
private boolean customDrawing() {
return hasCustomBackground() || (hasCustomForeground() && OS.IsWindowEnabled(handle));
}
@Override
LRESULT wmNotifyChild (NMHDR hdr, long wParam, long lParam) {
switch (hdr.code) {
case OS.TBN_DROPDOWN:
NMTOOLBAR lpnmtb = new NMTOOLBAR ();
OS.MoveMemory (lpnmtb, lParam, NMTOOLBAR.sizeof);
ToolItem 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);
event.setLocationInPixels(rect.left, rect.bottom);
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);
// 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: {
return new LRESULT (customDrawing() ? OS.CDRF_NOTIFYITEMDRAW : OS.CDRF_DODEFAULT);
}
case OS.CDDS_ITEMPREPAINT: {
if (customDrawing()) {
nmcd.clrBtnFace = getBackgroundPixel();
nmcd.clrText = getForegroundPixel();
OS.MoveMemory(lParam, nmcd, NMTBCUSTOMDRAW.sizeof);
return new LRESULT(OS.TBCDRF_USECDCOLORS);
}
return new LRESULT (OS.CDRF_DODEFAULT);
}
}
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);
}
}