blob: 63a2d1c7348198de3597efeb85c9bf108c699ff0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012 Laurent CARON
* 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:
* Laurent CARON (laurent.caron at gmail dot com) - Initial API and implementation
*******************************************************************************/
package org.mihalis.opal.calculator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
/**
* The CalculatorCombo class represents a selectable user interface object that
* combines a text field and a calculator buttons panel and issues notification
* when an the value is modified.
* <p>
* Note that although this class is a subclass of <code>Composite</code>, it
* does not make sense to add children to it, or set a layout on it.
* </p>
* <dl>
* <dt><b>Styles:</b>
* <dd>BORDER, FLAT</dd>
* <dt><b>Events:</b>
* <dd>Modify</dd>
* </dl>
*
* @see <a href="http://www.eclipse.org/swt/snippets/#ccombo">CCombo
* snippets</a>
*/
public class CalculatorCombo extends Composite {
/** The label. */
private Label label;
/** The arrow. */
private Button arrow;
/** The popup. */
private Shell popup;
/** The filter. */
private Listener listener, filter;
/** The has focus. */
private boolean hasFocus;
/** The key listener. */
private KeyListener keyListener;
/** The composite. */
private CalculatorButtonsComposite composite;
/**
* Constructs a new instance of this class given its parent.
*
* @param parent a widget which will be the parent of the new instance (cannot be null)
* @param style not used
*
* @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>
* </ul>
*/
public CalculatorCombo(final Composite parent, final int style) {
super(parent, style);
final GridLayout gridLayout = new GridLayout(2, false);
gridLayout.horizontalSpacing = gridLayout.verticalSpacing = gridLayout.marginWidth = gridLayout.marginHeight = 0;
this.setLayout(gridLayout);
this.label = new Label(this, SWT.BORDER | SWT.RIGHT);
this.label.setBackground(this.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
this.label.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false));
this.arrow = new Button(this, SWT.ARROW | SWT.DOWN);
this.arrow.setLayoutData(new GridData(GridData.FILL, GridData.FILL, false, false));
this.listener = new Listener() {
@Override
public void handleEvent(final Event event) {
if (CalculatorCombo.this.popup == event.widget) {
handlePopupEvent(event);
return;
}
if (CalculatorCombo.this.arrow == event.widget) {
handleButtonEvent(event);
return;
}
if (CalculatorCombo.this == event.widget) {
handleMultiChoiceEvent(event);
return;
}
if (getShell() == event.widget) {
getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
if (isDisposed()) {
return;
}
handleFocusEvent(SWT.FocusOut);
}
});
}
}
};
final int[] calculatorComboEvents = { SWT.Dispose, SWT.Move, SWT.Resize };
for (final int calculatorComboEvent : calculatorComboEvents) {
this.addListener(calculatorComboEvent, this.listener);
}
final int[] buttonEvents = { SWT.Selection, SWT.FocusIn };
for (final int buttonEvent : buttonEvents) {
this.arrow.addListener(buttonEvent, this.listener);
}
this.filter = new Listener() {
@Override
public void handleEvent(final Event event) {
final Shell shell = ((Control) event.widget).getShell();
if (shell == CalculatorCombo.this.getShell()) {
handleFocusEvent(SWT.FocusOut);
}
}
};
createPopupShell();
}
/**
* Handle a popup event.
*
* @param event event to handle
*/
private void handlePopupEvent(final Event event) {
switch (event.type) {
case SWT.Paint:
final Rectangle listRect = this.popup.getBounds();
final Color black = getDisplay().getSystemColor(SWT.COLOR_BLACK);
event.gc.setForeground(black);
event.gc.drawRectangle(0, 0, listRect.width - 1, listRect.height - 1);
break;
case SWT.Close:
event.doit = false;
hidePopupWindow(false);
break;
case SWT.Deactivate:
hidePopupWindow(false);
break;
case SWT.Dispose:
if (this.keyListener != null) {
this.label.removeKeyListener(this.keyListener);
}
break;
}
}
/**
* Hide popup window.
*
* @param drop the drop
*/
private void hidePopupWindow(final boolean drop) {
_displayHidePopupWindow(false);
}
/**
* _display hide popup window.
*
* @param show the show
*/
private void _displayHidePopupWindow(final boolean show) {
if (show == isPopupVisible()) {
return;
}
if (!show) {
this.popup.setVisible(false);
if (!isDisposed()) {
this.label.setFocus();
}
return;
}
if (getShell() != this.popup.getParent()) {
this.popup.dispose();
this.popup = null;
createPopupShell();
}
final Point textRect = this.label.toDisplay(this.label.getLocation().x, this.label.getLocation().y);
final int x = textRect.x;
final int y = textRect.y + this.label.getSize().y;
this.popup.setLocation(x, y);
this.popup.setVisible(true);
this.popup.setFocus();
}
/**
* Creates the popup shell.
*/
private void createPopupShell() {
this.popup = new Shell(getShell(), SWT.NO_TRIM | SWT.ON_TOP);
this.popup.setLayout(new GridLayout());
final int[] popupEvents = { SWT.Close, SWT.Paint, SWT.Deactivate, SWT.Dispose };
for (final int popupEvent : popupEvents) {
this.popup.addListener(popupEvent, this.listener);
}
this.composite = new CalculatorButtonsComposite(this.popup, SWT.NONE);
this.composite.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, true));
this.composite.setDisplayArea(this.label);
this.keyListener = this.composite.getKeyListener();
this.label.addKeyListener(this.keyListener);
this.popup.pack();
}
/**
* Handle button event.
*
* @param event the event
*/
private void handleButtonEvent(final Event event) {
switch (event.type) {
case SWT.FocusIn: {
handleFocusEvent(SWT.FocusIn);
break;
}
case SWT.Selection: {
_displayHidePopupWindow(!isPopupVisible());
break;
}
}
}
/**
* Handle focus event.
*
* @param eventType the event type
*/
private void handleFocusEvent(final int eventType) {
if (isDisposed()) {
return;
}
switch (eventType) {
case SWT.FocusIn: {
if (this.hasFocus) {
return;
}
this.hasFocus = true;
final Shell shell = getShell();
shell.removeListener(SWT.Deactivate, this.listener);
shell.addListener(SWT.Deactivate, this.listener);
final Display display = getDisplay();
display.removeFilter(SWT.FocusIn, this.filter);
display.addFilter(SWT.FocusIn, this.filter);
final Event e = new Event();
notifyListeners(SWT.FocusIn, e);
break;
}
case SWT.FocusOut: {
if (!this.hasFocus) {
return;
}
final Control focusControl = getDisplay().getFocusControl();
if (focusControl == this.arrow) {
return;
}
this.hasFocus = false;
final Shell shell = getShell();
shell.removeListener(SWT.Deactivate, this.listener);
final Display display = getDisplay();
display.removeFilter(SWT.FocusIn, this.filter);
final Event e = new Event();
notifyListeners(SWT.FocusOut, e);
break;
}
}
}
/**
* Checks if is popup visible.
*
* @return true, if is popup visible
*/
private boolean isPopupVisible() {
return !this.popup.isDisposed() && this.popup.getVisible();
}
/**
* Handle multi choice event.
*
* @param event the event
*/
private void handleMultiChoiceEvent(final Event event) {
switch (event.type) {
case SWT.Dispose:
if (this.popup != null && !this.popup.isDisposed()) {
this.popup.dispose();
}
final Shell shell = getShell();
shell.removeListener(SWT.Deactivate, this.listener);
final Display display = getDisplay();
display.removeFilter(SWT.FocusIn, this.filter);
this.popup = null;
this.arrow = null;
break;
case SWT.Move:
hidePopupWindow(false);
break;
case SWT.Resize:
if (isPopupVisible()) {
hidePopupWindow(false);
}
break;
}
}
/**
* Adds the listener to the collection of listeners who will be notified
* when the receiver's text is modified, by sending it one of the messages
* defined in the <code>ModifyListener</code> interface.
*
* @param listener the listener which should be notified
* @see ModifyListener
* @see #removeModifyListener
* @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>
*/
public void addModifyListener(final ModifyListener listener) {
checkWidget();
this.composite.addModifyListener(listener);
}
/**
* Compute size.
*
* @param wHint the w hint
* @param hHint the h hint
* @param changed the changed
* @return the point
* @see org.eclipse.swt.widgets.Composite#computeSize(int, int, boolean)
*/
@Override
public Point computeSize(final int wHint, final int hHint, final boolean changed) {
checkWidget();
int width = 0, height = 0;
final GC gc = new GC(this.label);
final int spacer = gc.stringExtent(" ").x;
final int textWidth = gc.stringExtent(this.label.getText()).x;
gc.dispose();
final Point textSize = this.label.computeSize(SWT.DEFAULT, SWT.DEFAULT, changed);
final Point arrowSize = this.arrow.computeSize(SWT.DEFAULT, SWT.DEFAULT, changed);
final int borderWidth = getBorderWidth();
height = Math.max(textSize.y, arrowSize.y);
width = textWidth + 2 * spacer + arrowSize.x + 2 * borderWidth;
if (wHint != SWT.DEFAULT) {
width = wHint;
}
if (hHint != SWT.DEFAULT) {
height = hHint;
}
return new Point(width + 2 * borderWidth, height + 2 * borderWidth);
}
/**
* Gets the value.
*
* @return the value of the combo
*/
public String getValue() {
checkWidget();
return this.label.getText();
}
/**
* Removes the listener from the collection of listeners who will be
* notified when the receiver's text is modified.
*
* @param listener the listener which should no longer be notified
* @see ModifyListener
* @see #addModifyListener
* @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>
*/
public void removeModifyListener(final ModifyListener listener) {
checkWidget();
this.composite.removeModifyListener(listener);
}
/**
* Sets the enabled.
*
* @param enabled the new enabled
* @see org.eclipse.swt.widgets.Control#setEnabled(boolean)
*/
@Override
public void setEnabled(final boolean enabled) {
checkWidget();
this.arrow.setEnabled(enabled);
this.label.setEnabled(enabled);
super.setEnabled(enabled);
}
/**
* Sets the tool tip text.
*
* @param txt the new tool tip text
* @see org.eclipse.swt.widgets.Control#setToolTipText(java.lang.String)
*/
@Override
public void setToolTipText(final String txt) {
checkWidget();
this.label.setToolTipText(txt);
}
/**
* Sets the value.
*
* @param value new value
* @throws NumberFormatException if <code>value</code> is not a valid float
* value
*/
public void setValue(final String value) {
checkWidget();
this.label.setText(value);
}
}