blob: 03e6bc855b427c74f8df446f42a5450cab455e50 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 BSI Business Systems Integration AG.
* 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:
* BSI Business Systems Integration AG - initial API and implementation
******************************************************************************/
package org.eclipse.scout.rt.client.ui.form.fields.button;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.List;
import org.eclipse.scout.commons.ConfigurationUtility;
import org.eclipse.scout.commons.EventListenerList;
import org.eclipse.scout.commons.OptimisticLock;
import org.eclipse.scout.commons.annotations.ClassId;
import org.eclipse.scout.commons.annotations.ConfigOperation;
import org.eclipse.scout.commons.annotations.ConfigProperty;
import org.eclipse.scout.commons.annotations.Order;
import org.eclipse.scout.commons.exception.ProcessingException;
import org.eclipse.scout.commons.logger.IScoutLogger;
import org.eclipse.scout.commons.logger.ScoutLogManager;
import org.eclipse.scout.rt.client.services.common.icon.IIconProviderService;
import org.eclipse.scout.rt.client.ui.action.keystroke.IKeyStroke;
import org.eclipse.scout.rt.client.ui.action.menu.IMenu;
import org.eclipse.scout.rt.client.ui.action.menu.MenuUtility;
import org.eclipse.scout.rt.client.ui.form.fields.AbstractFormField;
import org.eclipse.scout.rt.client.ui.form.fields.radiobuttongroup.AbstractRadioButtonGroup;
import org.eclipse.scout.rt.shared.services.common.exceptionhandler.IExceptionHandlerService;
import org.eclipse.scout.service.SERVICES;
@ClassId("998788cf-df0f-480b-bd5a-5037805610c9")
public abstract class AbstractButton extends AbstractFormField implements IButton {
private static final IScoutLogger LOG = ScoutLogManager.getLogger(AbstractButton.class);
private final EventListenerList m_listenerList = new EventListenerList();
private int m_systemType;
private int m_displayStyle;
private boolean m_processButton;
private Object m_radioValue;
private IMenu[] m_menus;
private final IButtonUIFacade m_uiFacade;
private final OptimisticLock m_uiFacadeSetSelectedLock;
public AbstractButton() {
this(true);
}
public AbstractButton(boolean callInitializer) {
super(false);
m_uiFacade = new P_UIFacade();
m_uiFacadeSetSelectedLock = new OptimisticLock();
if (callInitializer) {
callInitializer();
}
}
/*
* Configuration
*/
/**
* Configures the system type of this button. See {@code IButton.SYSTEM_TYPE_* } constants for valid values.
* System buttons are buttons with pre-defined behavior (such as an 'Ok' button or a 'Cancel' button).
* <p>
* Subclasses can override this method. Default is {@code IButton.SYSTEM_TYPE_NONE}.
*
* @return the system type for a system button, or {@code IButton.SYSTEM_TYPE_NONE} for a non-system button
* @see IButton
*/
@ConfigProperty(ConfigProperty.BUTTON_SYSTEM_TYPE)
@Order(200)
protected int getConfiguredSystemType() {
return SYSTEM_TYPE_NONE;
}
/**
* Configures whether this button is a process button. Process buttons are typically displayed on a
* dedicated button bar at the bottom of a form. Non-process buttons can be placed anywhere on a form.
* <p>
* Subclasses can override this method. Default is {@code true}.
*
* @return {@code true} if this button is a process button, {@code false} otherwise
* @see IButton
*/
@ConfigProperty(ConfigProperty.BOOLEAN)
@Order(220)
protected boolean getConfiguredProcessButton() {
return true;
}
/**
* Configures the display style of this button. See {@code IButton.DISPLAY_STYLE_* } constants for valid values.
* <p>
* Subclasses can override this method. Default is {@code IButton.DISPLAY_STYLE_DEFAULT}.
*
* @return the display style of this button
* @see IButton
*/
@ConfigProperty(ConfigProperty.BUTTON_DISPLAY_STYLE)
@Order(210)
protected int getConfiguredDisplayStyle() {
return DISPLAY_STYLE_DEFAULT;
}
/**
* {@inheritDoc} Default for buttons is false because they usually should only take as much place as is needed to
* display the button label, but not necessarily more. See also {@link #getConfiguredGridUseUiWidth()}.
*/
@Override
protected boolean getConfiguredFillHorizontal() {
return false;
}
/**
* {@inheritDoc} Default for buttons is false because they usually should only take as much place as is needed to
* display the button label, but not necessarily more.
*/
@Override
protected boolean getConfiguredFillVertical() {
return false;
}
/**
* {@inheritDoc} Default for buttons is true because they usually should only take as much place as is needed to
* display the button label, but not necessarily more.
*/
@Override
protected boolean getConfiguredGridUseUiWidth() {
return true;
}
/**
* {@inheritDoc}
*/
@Override
protected boolean getConfiguredGridUseUiHeight() {
return false;
}
/**
* Configures the value represented by this radio button. This is the value that is
* returned if you query a radio button group for the current value and this button is the
* currently selected radio button.
* <p>
* Subclasses can override this method. Default is {@code null}.
*
* @return an {@code Object} representing the value of this radio button
* @see AbstractRadioButton
* @see AbstractRadioButtonGroup
*/
@ConfigProperty(ConfigProperty.OBJECT)
@Order(230)
protected Object getConfiguredRadioValue() {
return null;
}
/**
* Configures the icon for this button. The icon is displayed on the button itself. Depending on UI and look and feel,
* this button might support icon groups to represent different states of this button, such as enabled, disabled,
* mouse-over etc.
* <p>
* Subclasses can override this method. Default is {@code null}.
*
* @return the ID (name) of the icon
* @see IIconGroup
* @see IIconProviderService
*/
@ConfigProperty(ConfigProperty.ICON_ID)
@Order(190)
protected String getConfiguredIconId() {
return null;
}
/**
* Called whenever this button is clicked. This button is disabled and cannot be clicked again
* until this method returns.
* <p>
* Subclasses can override this method. The default does nothing.
*
* @throws ProcessingException
*/
@ConfigOperation
@Order(190)
protected void execClickAction() throws ProcessingException {
}
/**
* Called whenever the state of a toggle button or radio button changes.
* <p>
* Subclasses can override this method. The default does nothing.
*
* @param selected
* new state of the button
* @throws ProcessingException
*/
@ConfigOperation
@Order(200)
protected void execToggleAction(boolean selected) throws ProcessingException {
}
private Class<? extends IMenu>[] getConfiguredMenus() {
Class[] dca = ConfigurationUtility.getDeclaredPublicClasses(getClass());
Class[] filtered = ConfigurationUtility.filterClasses(dca, IMenu.class);
Class<IMenu>[] foca = ConfigurationUtility.sortFilteredClassesByOrderAnnotation(filtered, IMenu.class);
return ConfigurationUtility.removeReplacedClasses(foca);
}
@Override
public IKeyStroke[] getContributedKeyStrokes() {
return MenuUtility.getKeyStrokesFromMenus(getMenus());
}
@Override
protected void initConfig() {
super.initConfig();
setSystemType(getConfiguredSystemType());
setDisplayStyleInternal(getConfiguredDisplayStyle());
setProcessButton(getConfiguredProcessButton());
setIconId(getConfiguredIconId());
setRadioValue(getConfiguredRadioValue());
// menus
ArrayList<IMenu> menuList = new ArrayList<IMenu>();
Class<? extends IMenu>[] menuArray = getConfiguredMenus();
for (int i = 0; i < menuArray.length; i++) {
IMenu menu;
try {
menu = ConfigurationUtility.newInnerInstance(this, menuArray[i]);
menuList.add(menu);
}
catch (Throwable t) {
SERVICES.getService(IExceptionHandlerService.class).handleException(new ProcessingException("menu: " + menuArray[i].getName(), t));
}
}
try {
injectMenusInternal(menuList);
}
catch (Exception e) {
LOG.error("error occured while dynamically contributing menus.", e);
}
m_menus = menuList.toArray(new IMenu[0]);
}
/**
* Override this internal method only in order to make use of dynamic menus<br>
* Used to manage menu list and add/remove menus
*
* @param menuList
* live and mutable list of configured menus
*/
protected void injectMenusInternal(List<IMenu> menuList) {
}
/*
* Properties
*/
@Override
public String getIconId() {
return propertySupport.getPropertyString(PROP_ICON_ID);
}
@Override
public void setIconId(String iconId) {
propertySupport.setPropertyString(PROP_ICON_ID, iconId);
}
@Override
public Object getImage() {
return propertySupport.getProperty(PROP_IMAGE);
}
@Override
public void setImage(Object nativeImg) {
propertySupport.setProperty(PROP_IMAGE, nativeImg);
}
@Override
public int getSystemType() {
return m_systemType;
}
@Override
public void setSystemType(int systemType) {
m_systemType = systemType;
}
@Override
public boolean isProcessButton() {
return m_processButton;
}
@Override
public void setProcessButton(boolean on) {
m_processButton = on;
}
@Override
public boolean hasMenus() {
return m_menus.length > 0;
}
@Override
public IMenu[] getMenus() {
return m_menus;
}
@Override
public void doClick() throws ProcessingException {
if (isEnabled() && isVisible() && isEnabledProcessingButton()) {
try {
setEnabledProcessingButton(false);
fireButtonClicked();
execClickAction();
}
finally {
setEnabledProcessingButton(true);
}
}
}
/*
* Radio Buttons
*/
@Override
public Object getRadioValue() {
return m_radioValue;
}
@Override
public void setRadioValue(Object o) {
m_radioValue = o;
}
/**
* Toggle and Radio Buttons
*/
@Override
public boolean isSelected() {
return propertySupport.getPropertyBool(PROP_SELECTED);
}
/**
* Toggle and Radio Buttons
*/
@Override
public void setSelected(boolean b) {
boolean changed = propertySupport.setPropertyBool(PROP_SELECTED, b);
// single observer for config
if (changed) {
try {
execToggleAction(b);
}
catch (ProcessingException e) {
SERVICES.getService(IExceptionHandlerService.class).handleException(e);
}
}
}
@Override
public void disarm() {
fireDisarmButton();
}
@Override
public void requestPopup() {
fireRequestPopup();
}
@Override
public int getDisplayStyle() {
return m_displayStyle;
}
@Override
public void setDisplayStyleInternal(int i) {
m_displayStyle = i;
}
@Override
public IButtonUIFacade getUIFacade() {
return m_uiFacade;
}
/**
* Model Observer
*/
@Override
public void addButtonListener(ButtonListener listener) {
m_listenerList.add(ButtonListener.class, listener);
}
@Override
public void removeButtonListener(ButtonListener listener) {
m_listenerList.remove(ButtonListener.class, listener);
}
private void fireButtonClicked() {
fireButtonEvent(new ButtonEvent(this, ButtonEvent.TYPE_CLICKED));
}
private void fireDisarmButton() {
fireButtonEvent(new ButtonEvent(this, ButtonEvent.TYPE_DISARM));
}
private void fireRequestPopup() {
fireButtonEvent(new ButtonEvent(this, ButtonEvent.TYPE_REQUEST_POPUP));
}
private IMenu[] fireButtonPopup() {
ButtonEvent e = new ButtonEvent(this, ButtonEvent.TYPE_POPUP);
// single observer add our menus
IMenu[] menus = MenuUtility.filterValidMenusOnButton(this, getMenus(), true);
e.addPopupMenus(menus);
fireButtonEvent(e);
return e.getPopupMenus();
}
// main handler
private void fireButtonEvent(ButtonEvent e) {
EventListener[] listeners = m_listenerList.getListeners(ButtonListener.class);
if (listeners != null && listeners.length > 0) {
for (int i = 0; i < listeners.length; i++) {
((ButtonListener) listeners[i]).buttonChanged(e);
}
}
}
/**
* Default implementation for buttons
*/
private class P_UIFacade implements IButtonUIFacade {
@Override
public void fireButtonClickedFromUI() {
try {
if (isEnabled() && isVisible()) {
doClick();
}
}
catch (ProcessingException e) {
SERVICES.getService(IExceptionHandlerService.class).handleException(e);
}
}
@Override
public IMenu[] fireButtonPopupFromUI() {
return fireButtonPopup();
}
/**
* {@inheritDoc} Uses {@link MenuUtility#filterValidMenus} to check if there are valid menus. Does not execute the
* method <code>prepareAction</code> on the menu objects.
*/
@Override
public boolean hasValidMenusFromUI() {
IMenu[] menus = MenuUtility.filterValidMenusOnButton(AbstractButton.this, getMenus(), false);
return menus.length > 0;
}
/**
* Toggle and Radio Buttons
*/
@Override
public void setSelectedFromUI(boolean b) {
try {
/*
* Ticket 76711: added optimistic lock
*/
if (m_uiFacadeSetSelectedLock.acquire()) {
setSelected(b);
}
}
finally {
m_uiFacadeSetSelectedLock.release();
}
}
}
}