/* * This library is part of OpenCms - * the Open Source Content Management System * * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * For further information about Alkacon Software, please see the * company website: http://www.alkacon.com * * For further information about OpenCms, please see the * project website: http://www.opencms.org * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.opencms.gwt.client.ui.input; import org.opencms.gwt.client.ui.CmsPushButton; import org.opencms.gwt.client.ui.I_CmsButton.ButtonStyle; import org.opencms.gwt.client.ui.I_CmsTruncable; import org.opencms.gwt.client.ui.css.I_CmsImageBundle; import org.opencms.gwt.client.ui.css.I_CmsInputCss; import org.opencms.gwt.client.ui.css.I_CmsInputLayoutBundle; import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle; import org.opencms.gwt.client.util.CmsDomUtil; import org.opencms.gwt.client.util.CmsStyleVariable; import java.util.HashMap; import java.util.Map; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.MouseOutEvent; import com.google.gwt.event.dom.client.MouseOutHandler; import com.google.gwt.event.dom.client.MouseOverEvent; import com.google.gwt.event.dom.client.MouseOverHandler; import com.google.gwt.event.logical.shared.CloseEvent; import com.google.gwt.event.logical.shared.CloseHandler; import com.google.gwt.event.logical.shared.HasValueChangeHandlers; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.event.shared.SimpleEventBus; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.PopupPanel; import com.google.gwt.user.client.ui.Widget; /** * Abstract superclass for select box widgets.<p> * * @param <OPTION> the widget type of the select options * * @since 8.0.0 * */ public abstract class A_CmsSelectBox<OPTION extends A_CmsSelectCell> extends Composite implements I_CmsFormWidget, HasValueChangeHandlers<String>, I_CmsTruncable { /** * The UI Binder interface for this widget.<p> */ protected interface I_CmsSelectBoxUiBinder extends UiBinder<Panel, A_CmsSelectBox<?>> { // binder interface } /** The layout bundle. */ protected static final I_CmsInputCss CSS = I_CmsInputLayoutBundle.INSTANCE.inputCss(); /** Text metrics key. */ private static final String TM_OPTION = "Option"; /** The UiBinder instance used for this widget. */ private static I_CmsSelectBoxUiBinder uiBinder = GWT.create(I_CmsSelectBoxUiBinder.class); /** Error widget. */ @UiField protected CmsErrorWidget m_error; /** The event bus. */ protected SimpleEventBus m_eventBus; /** The open-close button. */ protected CmsPushButton m_openClose; /** The opener widget. */ @UiField protected FocusPanel m_opener; /** Container for the opener and error widget. */ @UiField protected Panel m_panel; /** The popup panel inside which the selector will be shown.<p> */ protected PopupPanel m_popup = new PopupPanel(true); /** Style of the select box widget. */ protected final CmsStyleVariable m_selectBoxState; /** The map of select options. */ protected Map<String, OPTION> m_selectCells = new HashMap<String, OPTION>(); /** The value of the currently selected option. */ protected String m_selectedValue; /** The selector which contains the select options. */ protected Panel m_selector = new FlowPanel(); /** Style of the select box widget. */ protected final CmsStyleVariable m_selectorState; /** Flag indicating whether this widget is enabled. */ private boolean m_enabled = true; /** The value of the first select option. */ private String m_firstValue; /** The text metrics prefix. */ private String m_textMetricsPrefix; /** The widget width for truncation. */ private int m_widgetWidth; /** * Creates a new select box.<p> */ public A_CmsSelectBox() { m_eventBus = new SimpleEventBus(); m_panel = uiBinder.createAndBindUi(this); initWidget(m_panel); m_selectBoxState = new CmsStyleVariable(m_opener); m_selectBoxState.setValue(I_CmsLayoutBundle.INSTANCE.generalCss().cornerAll()); m_selectorState = new CmsStyleVariable(m_selector); m_selectorState.setValue(I_CmsLayoutBundle.INSTANCE.generalCss().cornerBottom()); m_opener.addStyleName(CSS.selectBoxSelected()); addHoverHandlers(m_opener); m_openClose = new CmsPushButton( I_CmsImageBundle.INSTANCE.style().triangleRight(), I_CmsImageBundle.INSTANCE.style().triangleDown()); m_openClose.setButtonStyle(ButtonStyle.TRANSPARENT, null); m_openClose.addStyleName(CSS.selectIcon()); m_panel.add(m_openClose); m_openClose.addClickHandler(new ClickHandler() { /** * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent) */ public void onClick(ClickEvent event) { if (m_popup.isShowing()) { close(); } else { open(); } } }); m_popup.setWidget(m_selector); m_popup.addStyleName(CSS.selectorPopup()); m_popup.addAutoHidePartner(m_panel.getElement()); m_selector.setStyleName(CSS.selectBoxSelector()); m_selector.addStyleName(I_CmsLayoutBundle.INSTANCE.generalCss().cornerBottom()); m_selector.addStyleName(I_CmsLayoutBundle.INSTANCE.generalCss().textMedium()); m_popup.addCloseHandler(new CloseHandler<PopupPanel>() { /** * @see CloseHandler#onClose(CloseEvent) */ public void onClose(CloseEvent<PopupPanel> e) { close(); } }); initOpener(); } /** * Adds a new select option to the select box.<p> * * @param cell the widget representing the select option */ public void addOption(OPTION cell) { String value = cell.getValue(); boolean first = m_selectCells.isEmpty(); m_selectCells.put(value, cell); m_selector.add(cell); if (first) { selectValue(value); m_firstValue = value; } initSelectCell(cell); } /** * @see com.google.gwt.event.logical.shared.HasValueChangeHandlers#addValueChangeHandler(com.google.gwt.event.logical.shared.ValueChangeHandler) */ public HandlerRegistration addValueChangeHandler(final ValueChangeHandler<String> handler) { return super.addHandler(handler, ValueChangeEvent.getType()); } /** * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFieldType() */ public FieldType getFieldType() { return FieldType.STRING; } /** * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFormValue() */ public Object getFormValue() { if (m_selectedValue.equals("")) { return null; } return m_selectedValue; } /** * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFormValueAsString() */ public String getFormValueAsString() { return (String)getFormValue(); } /** * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#isEnabled() */ public boolean isEnabled() { return m_enabled; } /** * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#reset() */ public void reset() { close(); onValueSelect(m_firstValue); } /** * Helper method to set the current selected option.<p> * * This method does not trigger the "value changed" event.<p> * * @param value the new value */ public void selectValue(String value) { if (m_selectCells.get(value) == null) { return; } updateOpener(value); if (m_textMetricsPrefix != null) { truncate(m_textMetricsPrefix, m_widgetWidth); } m_selectedValue = value; close(); } /** * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setEnabled(boolean) */ public void setEnabled(boolean enabled) { close(); m_enabled = enabled; } /** * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setErrorMessage(java.lang.String) */ public void setErrorMessage(String errorMessage) { m_error.setText(errorMessage); } /** * Sets the form value of this select box.<p> * * @param value the new value */ public void setFormValue(Object value) { if (value == null) { value = ""; } if (!"".equals(value) && !m_selectCells.containsKey(value)) { OPTION option = createUnknownOption((String)value); if (option != null) { addOption(option); } } if (value instanceof String) { String strValue = (String)value; this.onValueSelect(strValue); } } /** * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setFormValueAsString(java.lang.String) */ public void setFormValueAsString(String formValue) { setFormValue(formValue); } /** * @see org.opencms.gwt.client.ui.I_CmsTruncable#truncate(java.lang.String, int) */ public void truncate(String textMetricsPrefix, int widgetWidth) { m_textMetricsPrefix = textMetricsPrefix; m_widgetWidth = widgetWidth; truncateOpener(textMetricsPrefix, widgetWidth); int labelWidth = widgetWidth - 2 - 5; // 2px border left/right + 5px left margin for (Widget widget : m_selector) { if (widget instanceof I_CmsTruncable) { ((I_CmsTruncable)widget).truncate(textMetricsPrefix + TM_OPTION, labelWidth); } } } /** * Internal helper method for clearing the select options.<p> */ protected void clearItems() { m_selectCells.clear(); m_selector.clear(); m_selectedValue = null; } /** * Internal method which is called when the selector is closed.<p> */ protected void close() { if (!m_enabled) { return; } m_openClose.setDown(false); m_popup.hide(); m_selectBoxState.setValue(I_CmsLayoutBundle.INSTANCE.generalCss().cornerAll()); } /** * Internal method to create a select option for an unknown value.<p> * * @param value the value for which to create the option * * @return the new option */ protected abstract OPTION createUnknownOption(String value); /** * Handle clicks on the opener.<p> * * @param e the click event */ @UiHandler("m_opener") protected void doClickOpener(ClickEvent e) { toggleOpen(); } /** * The implementation of this method should initialize the opener of the select box.<p> */ protected abstract void initOpener(); /** * Internal handler method which is called when a new value is selected.<p> * * @param value the new value */ protected void onValueSelect(String value) { selectValue(value); ValueChangeEvent.<String> fire(this, value); } /** * Internal method which is called when the selector is opened.<p> */ protected void open() { if (!m_enabled) { return; } m_openClose.setDown(true); int newWidth = m_opener.getOffsetWidth() - 2 /*border*/; m_popup.setWidth(newWidth + "px"); m_popup.show(); int panelTop = m_panel.getElement().getAbsoluteTop(); int openerHeight = CmsDomUtil.getCurrentStyleInt(m_opener.getElement(), CmsDomUtil.Style.height); int popupHeight = m_popup.getOffsetHeight(); if (((Window.getClientHeight() - (panelTop + openerHeight)) < popupHeight) && (panelTop > popupHeight)) { CmsDomUtil.positionElement(m_popup.getElement(), m_panel.getElement(), 0, -(popupHeight - 2)); m_selectBoxState.setValue(I_CmsLayoutBundle.INSTANCE.generalCss().cornerBottom()); m_selectorState.setValue(I_CmsLayoutBundle.INSTANCE.generalCss().cornerTop()); } else { CmsDomUtil.positionElement(m_popup.getElement(), m_panel.getElement(), 0, openerHeight); m_selectBoxState.setValue(I_CmsLayoutBundle.INSTANCE.generalCss().cornerTop()); m_selectorState.setValue(I_CmsLayoutBundle.INSTANCE.generalCss().cornerBottom()); } // m_selectBoxState.setValue(CSS.selectBoxOpen()); } /** * Abstract method whose implementation should truncate the opener widget(s).<p> * * @param prefix the text metrics prefix * @param width the widget width */ protected abstract void truncateOpener(String prefix, int width); /** * The implementation of this method should update the opener when a new value is selected by the user.<p> * * @param newValue the value selected by the user */ protected abstract void updateOpener(String newValue); /** * Helper method for adding event handlers for a 'hover' effect to the opener.<p> * * @param panel the opener */ private void addHoverHandlers(FocusPanel panel) { final CmsStyleVariable hoverVar = new CmsStyleVariable(panel); hoverVar.setValue(CSS.openerNoHover()); panel.addMouseOverHandler(new MouseOverHandler() { /** * @see com.google.gwt.event.dom.client.MouseOverHandler#onMouseOver(com.google.gwt.event.dom.client.MouseOverEvent) */ public void onMouseOver(MouseOverEvent event) { hoverVar.setValue(CSS.openerHover()); } }); panel.addMouseOutHandler(new MouseOutHandler() { /** * @see com.google.gwt.event.dom.client.MouseOutHandler#onMouseOut(com.google.gwt.event.dom.client.MouseOutEvent) */ public void onMouseOut(MouseOutEvent event) { hoverVar.setValue(CSS.openerNoHover()); } }); } /** * Initializes the event handlers of a select cell.<p> * * @param cell the select cell whose event handlers should be initialized */ private void initSelectCell(final A_CmsSelectCell cell) { cell.addStyleName(CSS.selectBoxCell()); cell.registerDomHandler(new ClickHandler() { /** * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent) */ public void onClick(ClickEvent e) { onValueSelect(cell.getValue()); cell.removeStyleName(CSS.selectHover()); } }, ClickEvent.getType()); cell.registerDomHandler(new MouseOverHandler() { /** * @see com.google.gwt.event.dom.client.MouseOverHandler#onMouseOver(com.google.gwt.event.dom.client.MouseOverEvent) */ public void onMouseOver(MouseOverEvent e) { cell.addStyleName(CSS.selectHover()); } }, MouseOverEvent.getType()); cell.registerDomHandler(new MouseOutHandler() { /** * @see com.google.gwt.event.dom.client.MouseOutHandler#onMouseOut(com.google.gwt.event.dom.client.MouseOutEvent) */ public void onMouseOut(MouseOutEvent e) { cell.removeStyleName(CSS.selectHover()); } }, MouseOutEvent.getType()); } /** * Toggles the state of the selector popup between 'open' and 'closed'.<p> */ private void toggleOpen() { if (!m_enabled) { return; } if (m_popup.isShowing()) { close(); } else { open(); } } }