blob: 82d85ea684996bcb536d9ce4771d156a9257f0b2 [file] [log] [blame]
package org.eclipse.swt.widgets;
/*
* (c) Copyright IBM Corp. 2000, 2001.
* All Rights Reserved
*/
import org.eclipse.swt.internal.*;
import org.eclipse.swt.internal.win32.*;
import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.events.*;
/**
* Instances of this class represent a selectable user interface object that
* issues notification when pressed and released.
* <dl>
* <dt><b>Styles:</b></dt>
* <dd>ARROW, CHECK, PUSH, RADIO, TOGGLE, FLAT</dd>
* <dd>LEFT, RIGHT, CENTER</dd>
* <dt><b>Events:</b></dt>
* <dd>Selection</dd>
* </dl>
* <p>
* IMPORTANT: This class is intended to be subclassed <em>only</em>
* within the SWT implementation.
* </p>
*/
public class Button extends Control {
Image image;
static final int ButtonProc;
static final byte [] ButtonClass = Converter.wcsToMbcs (0,"BUTTON\0");
static final int CheckWidth, CheckHeight;
static {
int hBitmap = OS.LoadBitmap (0, OS.OBM_CHECKBOXES);
if (hBitmap == 0) {
CheckWidth = OS.GetSystemMetrics (OS.SM_CXVSCROLL);
CheckHeight = OS.GetSystemMetrics (OS.SM_CYVSCROLL);
} else {
BITMAP bitmap = new BITMAP ();
OS.GetObject (hBitmap, BITMAP.sizeof, bitmap);
OS.DeleteObject (hBitmap);
CheckWidth = bitmap.bmWidth / 4;
CheckHeight = bitmap.bmHeight / 3;
}
WNDCLASSEX lpWndClass = new WNDCLASSEX ();
lpWndClass.cbSize = WNDCLASSEX.sizeof;
OS.GetClassInfoEx (0, ButtonClass, lpWndClass);
ButtonProc = lpWndClass.lpfnWndProc;
}
/**
* 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
* for all SWT widget classes should include a comment which
* describes the style constants which are applicable to the class.
* </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
* @see Widget#checkSubclass
* @see Widget#getStyle
*/
public Button (Composite parent, int style) {
super (parent, checkStyle (style));
}
/**
* Adds the listener to the collection of listeners who will
* be notified when the control is selected, by sending
* it one of the messages defined in the <code>SelectionListener</code>
* interface.
* <p>
* <code>widgetSelected</code> is called when the control is selected.
* <code>widgetDefaultSelected</code> is not called.
* </p>
*
* @param listener the listener which should be notified
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener 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>
*
* @see SelectionListener
* @see #removeSelectionListener
* @see SelectionEvent
*/
public void addSelectionListener (SelectionListener listener) {
checkWidget ();
if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
TypedListener typedListener = new TypedListener (listener);
addListener (SWT.Selection,typedListener);
addListener (SWT.DefaultSelection,typedListener);
}
int callWindowProc (int msg, int wParam, int lParam) {
if (handle == 0) return 0;
return OS.CallWindowProc (ButtonProc, handle, msg, wParam, lParam);
}
static int checkStyle (int style) {
style = checkBits (style, SWT.PUSH, SWT.ARROW, SWT.CHECK, SWT.RADIO, SWT.TOGGLE, 0);
if ((style & SWT.PUSH) != 0) {
return checkBits (style, SWT.CENTER, SWT.LEFT, SWT.RIGHT, 0, 0, 0);
}
if ((style & (SWT.CHECK | SWT.RADIO | SWT.TOGGLE)) != 0) {
return checkBits (style, SWT.LEFT, SWT.RIGHT, SWT.CENTER, 0, 0, 0);
}
if ((style & SWT.ARROW) != 0) {
return checkBits (style, SWT.UP, SWT.DOWN, SWT.LEFT, SWT.RIGHT, 0, 0);
}
return style;
}
void click () {
OS.SendMessage (handle, OS.BM_CLICK, 0, 0);
}
public Point computeSize (int wHint, int hHint, boolean changed) {
checkWidget ();
int border = getBorderWidth ();
int width = border * 2, height = border * 2;
if ((style & SWT.ARROW) != 0) {
if ((style & (SWT.UP | SWT.DOWN)) != 0) {
width += OS.GetSystemMetrics (OS.SM_CXVSCROLL);
height += OS.GetSystemMetrics (OS.SM_CYVSCROLL);
} else {
width += OS.GetSystemMetrics (OS.SM_CXHSCROLL);
height += OS.GetSystemMetrics (OS.SM_CYHSCROLL);
}
if (wHint != SWT.DEFAULT) width = wHint + (border * 2);
if (hHint != SWT.DEFAULT) height = hHint + (border * 2);
return new Point (width, height);
}
int extra = 0;
int bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
if ((bits & (OS.BS_BITMAP | OS.BS_ICON)) == 0) {
int oldFont = 0;
int hDC = OS.GetDC (handle);
int newFont = OS.SendMessage (handle, OS.WM_GETFONT, 0, 0);
if (newFont != 0) oldFont = OS.SelectObject (hDC, newFont);
TEXTMETRIC lptm = new TEXTMETRIC ();
OS.GetTextMetrics (hDC, lptm);
int length = OS.GetWindowTextLength (handle);
if (length == 0) {
height += lptm.tmHeight;
} else {
extra = Math.max (8, lptm.tmAveCharWidth);
byte [] buffer = new byte [length + 1];
OS.GetWindowText (handle, buffer, length + 1);
RECT rect = new RECT ();
int flags = OS.DT_CALCRECT | OS.DT_SINGLELINE;
OS.DrawText (hDC, buffer, length, rect, flags);
width += rect.right - rect.left;
height += rect.bottom - rect.top;
}
if (newFont != 0) OS.SelectObject (hDC, oldFont);
OS.ReleaseDC (handle, hDC);
} else {
if (image != null) {
extra = 8;
switch (image.type) {
case SWT.BITMAP: {
BITMAP bm = new BITMAP ();
int hImage = image.handle;
OS.GetObject (hImage, BITMAP.sizeof, bm);
width += bm.bmWidth; height += bm.bmHeight;
break;
}
case SWT.ICON: {
BITMAP bm = new BITMAP ();
ICONINFO info = new ICONINFO ();
int hImage = image.handle;
OS.GetIconInfo (hImage, info);
int hBitmap = info.hbmColor;
if (hBitmap == 0) hBitmap = info.hbmMask;
OS.GetObject (hBitmap, BITMAP.sizeof, bm);
if (hBitmap == info.hbmMask) bm.bmHeight /= 2;
if (info.hbmColor != 0) OS.DeleteObject (info.hbmColor);
if (info.hbmMask != 0) OS.DeleteObject (info.hbmMask);
width += bm.bmWidth; height += bm.bmHeight;
break;
}
}
}
}
if ((style & (SWT.CHECK | SWT.RADIO)) != 0) {
width += CheckWidth + extra;
height = Math.max (height, CheckHeight + 3);
}
if ((style & (SWT.PUSH | SWT.TOGGLE)) != 0) {
width += 10; height += 7;
}
if (wHint != SWT.DEFAULT) width = wHint + (border * 2);
if (hHint != SWT.DEFAULT) height = hHint + (border * 2);
return new Point (width, height);
}
int defaultForeground () {
return OS.GetSysColor (OS.COLOR_BTNTEXT);
}
/**
* Returns a value which describes the position of the
* text or image in the receiver. The value will be one of
* <code>LEFT</code>, <code>RIGHT</code> or <code>CENTER</code>
* unless the receiver is an <code>ARROW</code> button, in
* which case, the alignment will indicate the direction of
* the arrow (one of <code>LEFT</code>, <code>RIGHT</code>,
* <code>UP</code> or <code>DOWN</code>.
*
* @return the alignment
*
* @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 getAlignment () {
checkWidget ();
if ((style & SWT.ARROW) != 0) {
if ((style & SWT.UP) != 0) return SWT.UP;
if ((style & SWT.DOWN) != 0) return SWT.DOWN;
if ((style & SWT.LEFT) != 0) return SWT.LEFT;
if ((style & SWT.RIGHT) != 0) return SWT.RIGHT;
return SWT.UP;
}
if ((style & SWT.LEFT) != 0) return SWT.LEFT;
if ((style & SWT.CENTER) != 0) return SWT.CENTER;
if ((style & SWT.RIGHT) != 0) return SWT.RIGHT;
return SWT.LEFT;
}
boolean getDefault () {
if ((style & SWT.PUSH) == 0) return false;
int bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
return (bits & OS.BS_DEFPUSHBUTTON) != 0;
}
/**
* Returns the receiver's image if it has one, or null
* if it does not.
*
* @return the receiver's image
*
* @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 Image getImage () {
checkWidget ();
return image;
}
String getNameText () {
return getText ();
}
/**
* Returns <code>true</code> if the receiver is selected,
* and false otherwise.
* <p>
* When the receiver is of type <code>CHECK</code> or <code>RADIO</code>,
* it is selected when it is checked. When it is of type <code>TOGGLE</code>,
* it is selected when it is pushed.
*
* @return the selection state
*
* @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 boolean getSelection () {
checkWidget ();
if ((style & (SWT.CHECK | SWT.RADIO | SWT.TOGGLE)) == 0) return false;
int state = OS.SendMessage (handle, OS.BM_GETCHECK, 0, 0);
return (state & OS.BST_CHECKED) != 0;
}
/**
* Returns the receiver's text, which will be an empty
* string if it has never been set.
*
* @return the receiver's text
*
* @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 String getText () {
checkWidget ();
int length = OS.GetWindowTextLength (handle);
if (length == 0) return "";
byte [] buffer1 = new byte [length + 1];
OS.GetWindowText (handle, buffer1, buffer1.length);
char [] buffer2 = Converter.mbcsToWcs (0, buffer1);
return new String (buffer2, 0, buffer2.length - 1);
}
boolean mnemonicHit () {
if (!setFocus ()) return false;
/*
* Feature in Windows. When a radio button gets focus,
* it selects the button in WM_SETFOCUS. Therefore, it
* is not necessary to click the button or send events
* because this has already happened in WM_SETFOCUS.
*/
if ((style & SWT.RADIO) == 0) click ();
return true;
}
boolean mnemonicMatch (char key) {
char mnemonic = findMnemonic (getText ());
if (mnemonic == '\0') return false;
return Character.toUpperCase (key) == Character.toUpperCase (mnemonic);
}
void releaseWidget () {
super.releaseWidget ();
image = null;
}
/**
* Removes the listener from the collection of listeners who will
* be notified when the control is selected.
*
* @param listener the listener which should be notified
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener 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>
*
* @see SelectionListener
* @see #addSelectionListener
*/
public void removeSelectionListener (SelectionListener listener) {
checkWidget ();
if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
if (eventTable == null) return;
eventTable.unhook (SWT.Selection, listener);
eventTable.unhook (SWT.DefaultSelection,listener);
}
void selectRadio () {
Control [] children = parent._getChildren ();
for (int i=0; i<children.length; i++) {
Control child = children [i];
if (this != child && child instanceof Button) {
Button button = (Button) child;
if ((button.style & SWT.RADIO) != 0) {
if (button.getSelection ()) {
button.setSelection (false);
button.postEvent (SWT.Selection);
}
}
}
}
setSelection (true);
}
/**
* Controls how text, images and arrows will be displayed
* in the receiver. The argument should be one of
* <code>LEFT</code>, <code>RIGHT</code> or <code>CENTER</code>
* unless the receiver is an <code>ARROW</code> button, in
* which case, the argument indicates the direction of
* the arrow (one of <code>LEFT</code>, <code>RIGHT</code>,
* <code>UP</code> or <code>DOWN</code>.
*
* @param alignment the new alignment
*
* @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 void setAlignment (int alignment) {
checkWidget ();
if ((style & SWT.ARROW) != 0) {
if ((style & (SWT.UP | SWT.DOWN | SWT.LEFT | SWT.RIGHT)) == 0) return;
style &= ~(SWT.UP | SWT.DOWN | SWT.LEFT | SWT.RIGHT);
style |= alignment & (SWT.UP | SWT.DOWN | SWT.LEFT | SWT.RIGHT);
OS.InvalidateRect (handle, null, true);
return;
}
if ((alignment & (SWT.LEFT | SWT.RIGHT | SWT.CENTER)) == 0) return;
style &= ~(SWT.LEFT | SWT.RIGHT | SWT.CENTER);
style |= alignment & (SWT.LEFT | SWT.RIGHT | SWT.CENTER);
int bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
bits &= ~(OS.BS_LEFT | OS.BS_CENTER | OS.BS_RIGHT);
if ((style & SWT.LEFT) != 0) bits |= OS.BS_LEFT;
if ((style & SWT.CENTER) != 0) bits |= OS.BS_CENTER;
if ((style & SWT.RIGHT) != 0) bits |= OS.BS_RIGHT;
OS.SetWindowLong (handle, OS.GWL_STYLE, bits);
OS.InvalidateRect (handle, null, true);
}
void setDefault (boolean value) {
if ((style & SWT.PUSH) == 0) return;
int bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
bits &= ~OS.BS_DEFPUSHBUTTON;
if (value) bits |= OS.BS_DEFPUSHBUTTON;
OS.SendMessage (handle, OS.BM_SETSTYLE, bits, 1);
}
public boolean setFocus () {
if ((style & SWT.ARROW) != 0) return false;
if (!super.setFocus ()) return false;
menuShell ().setDefaultButton (this, false);
return true;
}
/**
* Sets the receiver's image to the argument, which may be
* null indicating that no image should be displayed.
*
* @param image the image to display on the receiver (may be null)
*
* @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 void setImage (Image image) {
checkWidget ();
int hImage = 0, imageBits = 0, fImageType = 0;
if (image != null) {
hImage = image.handle;
switch (image.type) {
case SWT.BITMAP:
imageBits = OS.BS_BITMAP;
fImageType = OS.IMAGE_BITMAP;
break;
case SWT.ICON:
imageBits = OS.BS_ICON;
fImageType = OS.IMAGE_ICON;
break;
default:
return;
}
}
this.image = image;
int newBits = OS.GetWindowLong (handle, OS.GWL_STYLE);
int oldBits = newBits;
newBits &= ~(OS.BS_BITMAP | OS.BS_ICON);
newBits |= imageBits;
if (newBits != oldBits) {
OS.SetWindowLong (handle, OS.GWL_STYLE, newBits);
}
OS.SendMessage (handle, OS.BM_SETIMAGE, fImageType, hImage);
}
boolean setRadioFocus () {
if ((style & SWT.RADIO) == 0 || !getSelection ()) return false;
return setFocus () || !isVisible ();
}
/**
* Sets the selection state of the receiver.
* <p>
* When the receiver is of type <code>CHECK</code> or <code>RADIO</code>,
* it is selected when it is checked. When it is of type <code>TOGGLE</code>,
* it is selected when it is pushed.
*
* @param selected the new selection state
*
* @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 void setSelection (boolean selected) {
checkWidget ();
if ((style & (SWT.CHECK | SWT.RADIO | SWT.TOGGLE)) == 0) return;
int bits = selected ? OS.BST_CHECKED : OS.BST_UNCHECKED;
OS.SendMessage (handle, OS.BM_SETCHECK, bits, 0);
/*
* Feature in Windows. When a radio button gets focus,
* it selects the button in WM_SETFOCUS. If the previous
* saved focus widget was a radio button, allowing the shell
* to automatically restore the focus to the previous radio
* button will unexpectedly check that button. The fix is
* to set the saved focus widget for the shell to be the
* radio button so that when focus is restored, the focus
* widget will be the new radio button.
*/
if (!selected) return;
if ((style & SWT.RADIO) == 0) return;
menuShell ().setSavedFocus (this);
}
/**
* Sets the receiver's text.
* <p>
* This method sets the button label. The label may include
* the mnemonic character but must not contain line delimiters.
* </p>
*
* @param string the new text
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the text 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 void setText (String string) {
checkWidget ();
if (string == null) error (SWT.ERROR_NULL_ARGUMENT);
int newBits = OS.GetWindowLong (handle, OS.GWL_STYLE);
int oldBits = newBits;
newBits &= ~(OS.BS_BITMAP | OS.BS_ICON);
if (newBits != oldBits) {
OS.SetWindowLong (handle, OS.GWL_STYLE, newBits);
}
byte [] buffer = Converter.wcsToMbcs (0, string, true);
OS.SetWindowText (handle, buffer);
}
int widgetStyle () {
int bits = super.widgetStyle () | OS.BS_NOTIFY;
if ((style & SWT.FLAT) != 0) bits |= OS.BS_FLAT;
if ((style & SWT.ARROW) != 0) return bits | OS.BS_OWNERDRAW;
if ((style & SWT.LEFT) != 0) bits |= OS.BS_LEFT;
if ((style & SWT.CENTER) != 0) bits |= OS.BS_CENTER;
if ((style & SWT.RIGHT) != 0) bits |= OS.BS_RIGHT;
if ((style & SWT.PUSH) != 0) return bits | OS.BS_PUSHBUTTON | OS.WS_TABSTOP;
if ((style & SWT.CHECK) != 0) return bits | OS.BS_CHECKBOX;
if ((style & SWT.RADIO) != 0) return bits | OS.BS_RADIOBUTTON;
if ((style & SWT.TOGGLE) != 0) return bits | OS.BS_PUSHLIKE | OS.BS_CHECKBOX;
return bits | OS.BS_PUSHBUTTON | OS.WS_TABSTOP;
}
byte [] windowClass () {
return ButtonClass;
}
int windowProc () {
return ButtonProc;
}
LRESULT WM_GETDLGCODE (int wParam, int lParam) {
LRESULT result = super.WM_GETDLGCODE (wParam, lParam);
if (result != null) return result;
if ((style & SWT.ARROW) != 0) {
return new LRESULT (OS.DLGC_STATIC);
}
return result;
}
LRESULT WM_SETFOCUS (int wParam, int lParam) {
LRESULT result = super.WM_SETFOCUS (wParam, lParam);
if (result != null) return result;
if ((style & SWT.RADIO) == 0) return result;
/*
* Feature in Windows. When Windows sets focus to
* a radio button, it sets the WM_TABSTOP style.
* This is undocumented and unwanted. The fix is
* to clear this style.
*/
int bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
int code = callWindowProc (OS.WM_SETFOCUS, wParam, lParam);
OS.SetWindowLong (handle, OS.GWL_STYLE, bits);
return new LRESULT (code);
}
LRESULT wmCommandChild (int wParam, int lParam) {
int code = wParam >> 16;
switch (code) {
case OS.BN_CLICKED:
case OS.BN_DOUBLECLICKED:
if ((style & (SWT.CHECK | SWT.TOGGLE)) != 0) {
setSelection (!getSelection ());
} else {
if ((style & SWT.RADIO) != 0) {
if ((parent.getStyle () & SWT.NO_RADIO_GROUP) == 0) {
selectRadio ();
} else {
setSelection (!getSelection ());
}
}
}
postEvent (SWT.Selection);
}
return super.wmCommandChild (wParam, lParam);
}
LRESULT wmDrawChild (int wParam, int lParam) {
if ((style & SWT.ARROW) == 0) return super.wmDrawChild (wParam, lParam);
DRAWITEMSTRUCT struct = new DRAWITEMSTRUCT ();
OS.MoveMemory (struct, lParam, DRAWITEMSTRUCT.sizeof);
int uState = OS.DFCS_SCROLLLEFT;
switch (style & (SWT.UP | SWT.DOWN | SWT.LEFT | SWT.RIGHT)) {
case SWT.UP: uState = OS.DFCS_SCROLLUP; break;
case SWT.DOWN: uState = OS.DFCS_SCROLLDOWN; break;
case SWT.LEFT: uState = OS.DFCS_SCROLLLEFT; break;
case SWT.RIGHT: uState = OS.DFCS_SCROLLRIGHT; break;
}
if ((style & SWT.FLAT) == SWT.FLAT) uState |= OS.DFCS_FLAT;
if (!OS.IsWindowEnabled (handle)) uState |= OS.DFCS_INACTIVE;
if ((struct.itemState & OS.ODS_SELECTED) != 0) uState |= OS.DFCS_PUSHED;
RECT rect = new RECT ();
OS.SetRect (rect, struct.left, struct.top, struct.right, struct.bottom);
OS.DrawFrameControl (struct.hDC, rect, OS.DFC_SCROLL, uState);
return null;
}
}