001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (C) Alkacon Software (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.acacia.client.ui;
029
030import org.opencms.acacia.client.CmsButtonBarHandler;
031import org.opencms.acacia.client.CmsChoiceMenuEntryBean;
032import org.opencms.acacia.client.I_CmsWidgetService;
033import org.opencms.acacia.client.css.I_CmsLayoutBundle;
034
035import com.google.gwt.core.client.GWT;
036import com.google.gwt.dom.client.SpanElement;
037import com.google.gwt.event.dom.client.HasMouseOutHandlers;
038import com.google.gwt.event.dom.client.HasMouseOverHandlers;
039import com.google.gwt.event.dom.client.MouseOutEvent;
040import com.google.gwt.event.dom.client.MouseOutHandler;
041import com.google.gwt.event.dom.client.MouseOverEvent;
042import com.google.gwt.event.dom.client.MouseOverHandler;
043import com.google.gwt.event.shared.HandlerRegistration;
044import com.google.gwt.uibinder.client.UiBinder;
045import com.google.gwt.uibinder.client.UiField;
046import com.google.gwt.user.client.Window;
047import com.google.gwt.user.client.rpc.AsyncCallback;
048import com.google.gwt.user.client.ui.Composite;
049import com.google.gwt.user.client.ui.FlowPanel;
050import com.google.gwt.user.client.ui.HTMLPanel;
051import com.google.gwt.user.client.ui.Panel;
052import com.google.gwt.user.client.ui.Widget;
053
054/**
055 * The attribute choice widget.<p>
056 */
057public class CmsAttributeChoiceWidget extends Composite implements HasMouseOverHandlers, HasMouseOutHandlers {
058
059    /**
060     * The UI binder interface.<p>
061     */
062    interface I_AttributeChoiceWidgetUiBinder extends UiBinder<HTMLPanel, CmsAttributeChoiceWidget> {
063        // nothing to do
064    }
065
066    /** The UI binder instance. */
067    private static I_AttributeChoiceWidgetUiBinder uiBinder = GWT.create(I_AttributeChoiceWidgetUiBinder.class);
068
069    /** Counts the number of choices added. */
070    private int m_choiceCount = 0;
071
072    /** The button icon element. */
073    @UiField
074    SpanElement m_buttonIcon;
075
076    /** The choices panel. */
077    @UiField
078    FlowPanel m_choices;
079
080    /**
081     * Constructor.<p>
082     */
083    public CmsAttributeChoiceWidget() {
084
085        initWidget(uiBinder.createAndBindUi(this));
086        addMouseOutHandler(CmsButtonBarHandler.INSTANCE);
087        addMouseOverHandler(CmsButtonBarHandler.INSTANCE);
088        addStyleName(CmsButtonBarHandler.HOVERABLE_MARKER);
089    }
090
091    /**
092     * Adds a new choice entry.<p>
093     *
094     * @param widgetService the widget service to use for labels
095     * @param menuEntry the menu entry bean
096     * @param selectHandler the handler to use for selecting entries
097     */
098    public void addChoice(
099        I_CmsWidgetService widgetService,
100        CmsChoiceMenuEntryBean menuEntry,
101        AsyncCallback<CmsChoiceMenuEntryBean> selectHandler) {
102
103        Widget choice = new CmsChoiceMenuEntryWidget(
104            widgetService.getAttributeLabel(menuEntry.getPathComponent()),
105            widgetService.getAttributeHelp(menuEntry.getPathComponent()),
106            menuEntry,
107            selectHandler,
108            this,
109            null);
110        addChoice(choice);
111    }
112
113    /**
114     * Adds a choice to the widget.<p>
115     *
116     * @param choice the choice to add
117     */
118    public void addChoice(Widget choice) {
119
120        m_choices.add(choice);
121        m_choiceCount += 1;
122        if (m_choiceCount == 1) {
123            this.addStyleName(I_CmsLayoutBundle.INSTANCE.attributeChoice().singleChoice());
124        } else {
125            this.removeStyleName(I_CmsLayoutBundle.INSTANCE.attributeChoice().singleChoice());
126        }
127    }
128
129    /**
130     * @see com.google.gwt.event.dom.client.HasMouseOutHandlers#addMouseOutHandler(com.google.gwt.event.dom.client.MouseOutHandler)
131     */
132    public HandlerRegistration addMouseOutHandler(MouseOutHandler handler) {
133
134        return addDomHandler(handler, MouseOutEvent.getType());
135    }
136
137    /**
138     * @see com.google.gwt.event.dom.client.HasMouseOverHandlers#addMouseOverHandler(com.google.gwt.event.dom.client.MouseOverHandler)
139     */
140    public HandlerRegistration addMouseOverHandler(MouseOverHandler handler) {
141
142        return addDomHandler(handler, MouseOverEvent.getType());
143    }
144
145    /**
146     * Gets the panel into which submenus of this menu should be inserted.<p>
147     *
148     * @return the panel for submenus
149     */
150    public Panel getSubmenuPanel() {
151
152        return (Panel)getParent();
153    }
154
155    /**
156     * Hides the choice menu.<p>
157     */
158    public void hide() {
159
160        removeStyleName(I_CmsLayoutBundle.INSTANCE.attributeChoice().hovering());
161    }
162
163    /**
164     * Shows the choice menu.<p>
165     */
166    public void show() {
167
168        addStyleName(I_CmsLayoutBundle.INSTANCE.attributeChoice().hovering());
169        if (displayAbove()) {
170            addStyleName(I_CmsLayoutBundle.INSTANCE.attributeChoice().displayAbove());
171        } else {
172            removeStyleName(I_CmsLayoutBundle.INSTANCE.attributeChoice().displayAbove());
173        }
174    }
175
176    /**
177     * Evaluates if the choice select should be displayed above the button.<p>
178     *
179     * @return <code>true</code> if the choice select should be displayed above the button
180     */
181    private boolean displayAbove() {
182
183        int popupHeight = m_choices.getOffsetHeight();
184        // Calculate top position for the choice select
185        int top = m_buttonIcon.getAbsoluteTop();
186
187        // Make sure scrolling is taken into account, since
188        // box.getAbsoluteTop() takes scrolling into account.
189        int windowTop = Window.getScrollTop();
190        int windowBottom = Window.getScrollTop() + Window.getClientHeight();
191
192        // Distance from the top edge of the window to the top edge of the
193        // text box
194        int distanceFromWindowTop = top - windowTop;
195
196        // Distance from the bottom edge of the window to the bottom edge of
197        // the text box
198        int distanceToWindowBottom = windowBottom - (top + m_buttonIcon.getOffsetHeight());
199
200        // If there is not enough space for the popup's height below the button
201        // and there IS enough space for the popup's height above the button,
202        // then then position the popup above the button. However, if there
203        // is not enough space on either side, then stick with displaying the
204        // popup below the button.
205        boolean displayAbove = (distanceFromWindowTop > distanceToWindowBottom)
206            && (distanceToWindowBottom < popupHeight);
207        return displayAbove;
208    }
209}