view dwt/widgets/Button.d @ 27:dfcc6c4c4317

Ported dwt.widgets.Button
author Jacob Carlborg <doob@me.com> <jacob.carlborg@gmail.com>
date Mon, 08 Sep 2008 18:15:39 +0200
parents e831403a80a9
children 7d135fe0caf2
line wrap: on
line source

/*******************************************************************************
 * Copyright (c) 2000, 2007 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     
 * Port to the D programming language:
 *     Jacob Carlborg <jacob.carlborg@gmail.com>
 *******************************************************************************/
module dwt.widgets.Button;


import dwt.DWT;
import dwt.DWTException;
import dwt.events.SelectionEvent;
import dwt.events.SelectionListener;
import dwt.graphics.Image;
import dwt.internal.cocoa.NSAttributedString;
import dwt.internal.cocoa.NSButton;
import dwt.internal.cocoa.NSButtonCell;
import dwt.internal.cocoa.NSColor;
import dwt.internal.cocoa.NSEvent;
import dwt.internal.cocoa.NSMutableDictionary;
import dwt.internal.cocoa.NSMutableParagraphStyle;
import dwt.internal.cocoa.NSRect;
import dwt.internal.cocoa.NSString;
import dwt.internal.cocoa.OS;
import dwt.internal.cocoa.SWTButton;

import dwt.dwthelper.utils;
import dwt.internal.cocoa.CGFloat;
import dwt.internal.cocoa.NSInteger;
import dwt.widgets.Composite;
import dwt.widgets.Decorations;
import dwt.widgets.ImageList;
import dwt.widgets.TypedListener;

/**
 * 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>UP, DOWN, LEFT, RIGHT, CENTER</dd>
 * <dt><b>Events:</b></dt>
 * <dd>Selection</dd>
 * </dl>
 * <p>
 * Note: Only one of the styles ARROW, CHECK, PUSH, RADIO, and TOGGLE 
 * may be specified.
 * </p><p>
 * Note: Only one of the styles LEFT, RIGHT, and CENTER may be specified.
 * </p><p>
 * Note: Only one of the styles UP, DOWN, LEFT, and RIGHT may be specified
 * when the ARROW style is specified.
 * </p><p>
 * IMPORTANT: This class is intended to be subclassed <em>only</em>
 * within the DWT implementation.
 * </p>
 */
public class Button : Control {
    String text = "";
    Image image;
    bool grayed;
    
/**
 * 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>DWT</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>DWT</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 DWTException <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 DWT#ARROW
 * @see DWT#CHECK
 * @see DWT#PUSH
 * @see DWT#RADIO
 * @see DWT#TOGGLE
 * @see DWT#FLAT
 * @see DWT#LEFT
 * @see DWT#RIGHT
 * @see DWT#CENTER
 * @see Widget#checkSubclass
 * @see Widget#getStyle
 */
public this (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 the user, 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 by the user.
 * <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 DWTException <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 is null) error (DWT.ERROR_NULL_ARGUMENT);
    TypedListener typedListener = new TypedListener(listener);
    addListener(DWT.Selection,typedListener);
    addListener(DWT.DefaultSelection,typedListener);
}

static int checkStyle (int style) {
    style = checkBits (style, DWT.PUSH, DWT.ARROW, DWT.CHECK, DWT.RADIO, DWT.TOGGLE, 0);
    if ((style & (DWT.PUSH | DWT.TOGGLE)) !is 0) {
        return checkBits (style, DWT.CENTER, DWT.LEFT, DWT.RIGHT, 0, 0, 0);
    }
    if ((style & (DWT.CHECK | DWT.RADIO)) !is 0) {
        return checkBits (style, DWT.LEFT, DWT.RIGHT, DWT.CENTER, 0, 0, 0);
    }
    if ((style & DWT.ARROW) !is 0) {
        style |= DWT.NO_FOCUS;
        return checkBits (style, DWT.UP, DWT.DOWN, DWT.LEFT, DWT.RIGHT, 0, 0);
    }
    return style;
}

void click () {
    postEvent (DWT.Selection);
}

NSAttributedString createString() {
    NSMutableDictionary dict = NSMutableDictionary.dictionaryWithCapacity(4);
    if (foreground !is null) {
        NSColor color = NSColor.colorWithDeviceRed(cast(CGFloat) foreground.handle[0], cast(CGFloat) foreground.handle[1], cast(CGFloat) foreground.handle[2], 1);
        dict.setObject(color, OS.NSForegroundColorAttributeName());
    }
    if (font !is null) {
        dict.setObject(font.handle, OS.NSFontAttributeName());
    }
    int alignment;
    if ((style & DWT.CENTER) !is 0) {
        alignment = OS.NSCenterTextAlignment;
    } else if ((style & DWT.LEFT) !is 0) {
        alignment = OS.NSLeftTextAlignment;
    } else {
        alignment = OS.NSRightTextAlignment;
    }
    NSMutableParagraphStyle pStyle = cast(NSMutableParagraphStyle)(new NSMutableParagraphStyle()).alloc().init();
    pStyle.autorelease();
    pStyle.setAlignment(alignment);
    dict.setObject(pStyle, OS.NSParagraphStyleAttributeName());
    char [] chars = new char [text.length ()];
    text.getChars (0, chars.length, chars, 0);
    NSUInteger length = fixMnemonic (chars);
    NSString str = NSString.stringWithCharacters(chars.toString16().ptr, length);
    NSAttributedString attribStr = (cast(NSAttributedString)(new NSAttributedString()).alloc()).initWithString_attributes_(str, dict);
    attribStr.autorelease();
    return attribStr;
}

void createHandle () {
    NSButton widget = cast(NSButton)(new SWTButton()).alloc();
    widget.initWithFrame(NSRect());
    int type = OS.NSMomentaryPushButton;
    if ((style & DWT.PUSH) !is 0) {
        widget.setBezelStyle(OS.NSRoundedBezelStyle);
    } else if ((style & DWT.CHECK) !is 0) {
        type = OS.NSSwitchButton;
        widget.setAllowsMixedState (true);
    } else if ((style & DWT.RADIO) !is 0) {
        type = OS.NSRadioButton;        
    } else if ((style & DWT.TOGGLE) !is 0) {
        type = OS.NSPushOnPushOffButton;
        widget.setBezelStyle(OS.NSRegularSquareBezelStyle);
    } else if ((style & DWT.ARROW) !is 0) {
        widget.setBezelStyle(OS.NSRoundedDisclosureBezelStyle);
    }
    widget.setButtonType(type);
    widget.setTitle(NSString.stringWith(""));
    widget.setImagePosition(OS.NSImageLeft);
    widget.setTarget(widget);
    widget.setAction(OS.sel_sendSelection);
    widget.setTag(jniRef);
    view = widget;  
    parent.contentView().addSubview_(widget);
    _setAlignment(style);
}

/**
 * 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 DWTException <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 & DWT.ARROW) !is 0) {
        if ((style & DWT.UP) !is 0) return DWT.UP;
        if ((style & DWT.DOWN) !is 0) return DWT.DOWN;
        if ((style & DWT.LEFT) !is 0) return DWT.LEFT;
        if ((style & DWT.RIGHT) !is 0) return DWT.RIGHT;
        return DWT.UP;
    }
    if ((style & DWT.LEFT) !is 0) return DWT.LEFT;
    if ((style & DWT.CENTER) !is 0) return DWT.CENTER;
    if ((style & DWT.RIGHT) !is 0) return DWT.RIGHT;
    return DWT.LEFT;
}

public bool getGrayed() {
    checkWidget ();
    if ((style &DWT.CHECK) !is 0) return false;
    return grayed;
}

/**
 * Returns the receiver's image if it has one, or null
 * if it does not.
 *
 * @return the receiver's image
 *
 * @exception DWTException <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 in. If the receiver is of any other type,
 * this method returns false.
 *
 * @return the selection state
 *
 * @exception DWTException <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 bool getSelection () {
    checkWidget ();
    if ((style & (DWT.CHECK | DWT.RADIO | DWT.TOGGLE)) is 0) return false;
    if ((style & DWT.CHECK) !is 0 && grayed) return (cast(NSButton)view).state() is OS.NSMixedState;
    return (cast(NSButton)view).state() is OS.NSOnState;
}

/**
 * Returns the receiver's text, which will be an empty
 * string if it has never been set or if the receiver is
 * an <code>ARROW</code> button.
 *
 * @return the receiver's text
 *
 * @exception DWTException <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 ();
    return text;
}

void releaseWidget () {
    super.releaseWidget ();
    image = null;
    text = null;
}

/**
 * Removes the listener from the collection of listeners who will
 * be notified when the control is selected by the user.
 *
 * @param listener the listener which should no longer be notified
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
 * </ul>
 * @exception DWTException <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 is null) error (DWT.ERROR_NULL_ARGUMENT);
    if (eventTable is null) return;
    eventTable.unhook(DWT.Selection, listener);
    eventTable.unhook(DWT.DefaultSelection,listener);
}

void selectRadio () {
    /*
    * This code is intentionally commented.  When two groups
    * of radio buttons with the same parent are separated by
    * another control, the correct behavior should be that
    * the two groups act independently.  This is consistent
    * with radio tool and menu items.  The commented code
    * implements this behavior.
    */
//  int index = 0;
//  Control [] children = parent._getChildren ();
//  while (index < children.length && children [index] !is this) index++;
//  int i = index - 1;
//  while (i >= 0 && children [i].setRadioSelection (false)) --i;
//  int j = index + 1;
//  while (j < children.length && children [j].setRadioSelection (false)) j++;
//  setSelection (true);
    Control [] children = parent._getChildren ();
    for (int i=0; i<children.length; i++) {
        Control child = children [i];
        if (this !is child) child.setRadioSelection (false);
    }
    setSelection (true);
}

void sendSelection () {
    if ((style & DWT.RADIO) !is 0) {
        if ((parent.getStyle () & DWT.NO_RADIO_GROUP) is 0) {
            selectRadio ();
        }
    }
    if ((style & DWT.CHECK) !is 0) {
        if (grayed && (cast(NSButton)view).state() is OS.NSOnState) {
            (cast(NSButton)view).setState(OS.NSOffState);
        }
        if (!grayed && (cast(NSButton)view).state() is OS.NSMixedState) {
            (cast(NSButton)view).setState(OS.NSOnState);
        }
    }
    postEvent (DWT.Selection);
}


/**
 * 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 DWTException <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 ();
    _setAlignment (alignment);
    redraw ();
}

void _setAlignment (int alignment) {
    if ((style & DWT.ARROW) !is 0) {
        if ((style & (DWT.UP | DWT.DOWN | DWT.LEFT | DWT.RIGHT)) is 0) return; 
        style &= ~(DWT.UP | DWT.DOWN | DWT.LEFT | DWT.RIGHT);
        style |= alignment & (DWT.UP | DWT.DOWN | DWT.LEFT | DWT.RIGHT);
//      int orientation = OS.kThemeDisclosureRight;
//      if ((style & DWT.UP) !is 0) orientation = OS.kThemeDisclosureDown;
//      if ((style & DWT.DOWN) !is 0) orientation = OS.kThemeDisclosureDown;
//      if ((style & DWT.LEFT) !is 0) orientation = OS.kThemeDisclosureLeft;
//      OS.SetControl32BitValue (handle, orientation);
        return;
    }
    if ((alignment & (DWT.LEFT | DWT.RIGHT | DWT.CENTER)) is 0) return;
    style &= ~(DWT.LEFT | DWT.RIGHT | DWT.CENTER);
    style |= alignment & (DWT.LEFT | DWT.RIGHT | DWT.CENTER);
    /* text is still null when this is called from createHandle() */
    if (text !is null) {
        (cast(NSButton)view).setAttributedTitle(createString());
    }
//  /* Alignment not honoured when image and text is visible */
//  bool bothVisible = text !is null && text.length () > 0 && image !is null;
//  if (bothVisible) {
//      if ((style & (DWT.RADIO | DWT.CHECK)) !is 0) alignment = DWT.LEFT;
//      if ((style & (DWT.PUSH | DWT.TOGGLE)) !is 0) alignment = DWT.CENTER;
//  }
//  int textAlignment = 0;
//  int graphicAlignment = 0;
//  if ((alignment & DWT.LEFT) !is 0) {
//      textAlignment = OS.kControlBevelButtonAlignTextFlushLeft;
//      graphicAlignment = OS.kControlBevelButtonAlignLeft;
//  }
//  if ((alignment & DWT.CENTER) !is 0) {
//      textAlignment = OS.kControlBevelButtonAlignTextCenter;
//      graphicAlignment = OS.kControlBevelButtonAlignCenter;
//  }
//  if ((alignment & DWT.RIGHT) !is 0) {
//      textAlignment = OS.kControlBevelButtonAlignTextFlushRight;
//      graphicAlignment = OS.kControlBevelButtonAlignRight;
//  }
//  OS.SetControlData (handle, OS.kControlEntireControl, OS.kControlBevelButtonTextAlignTag, 2, new short [] {cast(short)textAlignment});
//  OS.SetControlData (handle, OS.kControlEntireControl, OS.kControlBevelButtonGraphicAlignTag, 2, new short [] {cast(short)graphicAlignment});
//  if (bothVisible) {
//      OS.SetControlData (handle, OS.kControlEntireControl, OS.kControlBevelButtonTextPlaceTag, 2, new short [] {cast(short)OS.kControlBevelButtonPlaceToRightOfGraphic});
//  }
}

void setBackground (float [] color) {
    NSColor nsColor;
    if (color is null) {
        return; // TODO set to OS default
    } else {
        nsColor = NSColor.colorWithDeviceRed(cast(CGFloat) color[0], cast(CGFloat) color[1], cast(CGFloat) color[2], 1);
    }
    NSButtonCell cell = new NSButtonCell((cast(NSButton)view).cell());
    cell.setBackgroundColor(nsColor);
}

void setDefault (bool value) {
    if ((style & DWT.PUSH) is 0) return;
//  int window = OS.GetControlOwner (handle);
//  OS.SetWindowDefaultButton (window, value ? handle : 0);
}

void setForeground (float [] color) {
    (cast(NSButton)view).setAttributedTitle(createString());
}

public void setGrayed(bool grayed) {
    checkWidget ();
    if ((style & DWT.CHECK) is 0) return;
    bool checked = getSelection ();
    this.grayed = grayed;
    if (checked) {
        if (grayed) {
            (cast(NSButton) view).setState (OS.NSMixedState);
        } else {
            (cast(NSButton) view).setState (OS.NSOnState);
        }
    }
}

/**
 * Sets the receiver's image to the argument, which may be
 * <code>null</code> indicating that no image should be displayed.
 * <p>
 * Note that a Button can display an image and text simultaneously
 * on Windows (starting with XP), GTK+ and OSX.  On other platforms,
 * a Button that has an image and text set into it will display the
 * image or text that was set most recently.
 * </p>
 * @param image the image to display on the receiver (may be <code>null</code>)
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_INVALID_ARGUMENT - if the image has been disposed</li>
 * </ul> 
 * @exception DWTException <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();
    if ((style & DWT.ARROW) !is 0) return;
    if (image !is null && image.isDisposed ()) {
        error (DWT.ERROR_INVALID_ARGUMENT);
    }
    this.image = image;
    (cast(NSButton)view).setImage(image.handle);
}

bool setRadioSelection (bool value){
    if ((style & DWT.RADIO) is 0) return false;
    if (getSelection () !is value) {
        setSelection (value);
        postEvent (DWT.Selection);
    }
    return true;
}

/**
 * Sets the selection state of the receiver, if it is of type <code>CHECK</code>, 
 * <code>RADIO</code>, or <code>TOGGLE</code>.
 *
 * <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 in.
 *
 * @param selected the new selection state
 *
 * @exception DWTException <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 (bool selected) {
    checkWidget();
    if ((style & (DWT.CHECK | DWT.RADIO | DWT.TOGGLE)) is 0) return;
    if (grayed) {
        (cast(NSButton)view).setState (selected ? OS.NSMixedState : OS.NSOffState);
    } else {
        (cast(NSButton)view).setState (selected ? OS.NSOnState : OS.NSOffState);
    }
}

/**
 * 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>
 * <p>
 * Mnemonics are indicated by an '&amp;' that causes the next
 * character to be the mnemonic.  When the user presses a
 * key sequence that matches the mnemonic, a selection
 * event occurs. On most platforms, the mnemonic appears
 * underlined but may be emphasized in a platform specific
 * manner.  The mnemonic indicator character '&amp;' can be
 * escaped by doubling it in the string, causing a single
 * '&amp;' to be displayed.
 * </p><p>
 * Note that a Button can display an image and text simultaneously
 * on Windows (starting with XP), GTK+ and OSX.  On other platforms,
 * a Button that has an image and text set into it will display the
 * image or text that was set most recently.
 * </p>
 * @param string the new text
 *
 * @exception IllegalArgumentException <ul>
 *    <li>ERROR_NULL_ARGUMENT - if the text is null</li>
 * </ul>
 * @exception DWTException <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 is null) error (DWT.ERROR_NULL_ARGUMENT);
    if ((style & DWT.ARROW) !is 0) return;
    text = string;
    (cast(NSButton)view).setAttributedTitle(createString());
}

int traversalCode (int key, NSEvent theEvent) {
    int code = super.traversalCode (key, theEvent);
    if ((style & DWT.RADIO) !is 0) code |= DWT.TRAVERSE_ARROW_NEXT | DWT.TRAVERSE_ARROW_PREVIOUS;
    return code;
}

}