001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (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.gwt.client.ui.input;
029
030import org.opencms.gwt.client.ui.CmsPushButton;
031import org.opencms.gwt.client.ui.CmsScrollPanel;
032import org.opencms.gwt.client.ui.I_CmsButton;
033import org.opencms.gwt.client.ui.I_CmsButton.ButtonStyle;
034import org.opencms.gwt.client.ui.I_CmsButton.Size;
035import org.opencms.gwt.client.ui.I_CmsTruncable;
036import org.opencms.gwt.client.ui.css.I_CmsInputCss;
037import org.opencms.gwt.client.ui.css.I_CmsInputLayoutBundle;
038import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle;
039import org.opencms.gwt.client.util.CmsDomUtil;
040import org.opencms.gwt.client.util.CmsStyleVariable;
041
042import java.util.HashMap;
043import java.util.Map;
044
045import com.google.gwt.core.client.GWT;
046import com.google.gwt.core.client.Scheduler;
047import com.google.gwt.core.client.Scheduler.ScheduledCommand;
048import com.google.gwt.dom.client.Element;
049import com.google.gwt.event.dom.client.ClickEvent;
050import com.google.gwt.event.dom.client.ClickHandler;
051import com.google.gwt.event.dom.client.FocusEvent;
052import com.google.gwt.event.dom.client.FocusHandler;
053import com.google.gwt.event.dom.client.HasFocusHandlers;
054import com.google.gwt.event.dom.client.MouseOutEvent;
055import com.google.gwt.event.dom.client.MouseOutHandler;
056import com.google.gwt.event.dom.client.MouseOverEvent;
057import com.google.gwt.event.dom.client.MouseOverHandler;
058import com.google.gwt.event.dom.client.MouseWheelEvent;
059import com.google.gwt.event.dom.client.MouseWheelHandler;
060import com.google.gwt.event.logical.shared.CloseEvent;
061import com.google.gwt.event.logical.shared.CloseHandler;
062import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
063import com.google.gwt.event.logical.shared.ResizeEvent;
064import com.google.gwt.event.logical.shared.ResizeHandler;
065import com.google.gwt.event.logical.shared.ValueChangeEvent;
066import com.google.gwt.event.logical.shared.ValueChangeHandler;
067import com.google.gwt.event.shared.HandlerRegistration;
068import com.google.gwt.event.shared.SimpleEventBus;
069import com.google.gwt.uibinder.client.UiBinder;
070import com.google.gwt.uibinder.client.UiField;
071import com.google.gwt.uibinder.client.UiHandler;
072import com.google.gwt.user.client.Event;
073import com.google.gwt.user.client.Window;
074import com.google.gwt.user.client.ui.Composite;
075import com.google.gwt.user.client.ui.FlowPanel;
076import com.google.gwt.user.client.ui.FocusPanel;
077import com.google.gwt.user.client.ui.Panel;
078import com.google.gwt.user.client.ui.PopupPanel;
079import com.google.gwt.user.client.ui.RootPanel;
080import com.google.gwt.user.client.ui.Widget;
081
082import elemental2.dom.CSSProperties.MaxHeightUnionType;
083import elemental2.dom.HTMLElement;
084import jsinterop.base.Js;
085
086/**
087 * Abstract superclass for select box widgets.<p>
088 *
089 * @param <OPTION> the widget type of the select options
090 *
091 * @since 8.0.0
092 *
093 */
094public abstract class A_CmsSelectBox<OPTION extends A_CmsSelectCell> extends Composite
095implements I_CmsFormWidget, HasValueChangeHandlers<String>, HasFocusHandlers, I_CmsTruncable {
096
097    /**
098     * The UI Binder interface for this widget.<p>
099     */
100    protected interface I_CmsSelectBoxUiBinder extends UiBinder<Panel, A_CmsSelectBox<?>> {
101        // binder interface
102    }
103
104    /** The layout bundle. */
105    protected static final I_CmsInputCss CSS = I_CmsInputLayoutBundle.INSTANCE.inputCss();
106
107    /** The UiBinder instance used for this widget. */
108    private static I_CmsSelectBoxUiBinder uiBinder = GWT.create(I_CmsSelectBoxUiBinder.class);
109
110    /** Error widget. */
111    @UiField
112    protected CmsErrorWidget m_error;
113
114    /** The event bus. */
115    protected SimpleEventBus m_eventBus;
116
117    /** Handler registration for mouse wheel handlers. */
118    protected HandlerRegistration m_mousewheelRegistration;
119
120    /** The open-close button. */
121    protected CmsPushButton m_openClose;
122
123    /** The opener widget. */
124    @UiField
125    protected FocusPanel m_opener;
126
127    /**  Container for the opener and error widget. */
128    @UiField
129    protected Panel m_panel;
130
131    /** The popup panel inside which the selector will be shown.<p> */
132    protected PopupPanel m_popup = new PopupPanel(true);
133
134    /** Style of the select box widget. */
135    protected final CmsStyleVariable m_selectBoxState;
136
137    /** The map of select options. */
138    protected Map<String, OPTION> m_selectCells = new HashMap<String, OPTION>();
139
140    /** The value of the currently selected option. */
141    protected String m_selectedValue;
142
143    /** The selector which contains the select options. */
144    protected Panel m_selector = new FlowPanel();
145
146    /** Style of the select box widget. */
147    protected final CmsStyleVariable m_selectorState;
148
149    /** Flag indicating whether this widget is enabled. */
150    private boolean m_enabled = true;
151
152    /** The value of the first select option. */
153    private String m_firstValue;
154
155    /** The maximum cell width. */
156    private int m_preferredPopupWidth;
157
158    /** The value to test the popup resize behaviour.*/
159    private boolean m_resizePopup = true;
160
161    /** The text metrics prefix. */
162    private String m_textMetricsPrefix;
163
164    /** The widget width for truncation. */
165    private int m_widgetWidth;
166
167    /** Handler registration for the window resize handler. */
168    private HandlerRegistration m_windowResizeHandlerReg;
169
170    /**
171     * Creates a new select box.<p>
172     */
173    public A_CmsSelectBox() {
174
175        m_eventBus = new SimpleEventBus();
176        m_panel = uiBinder.createAndBindUi(this);
177        initWidget(m_panel);
178        m_selectBoxState = new CmsStyleVariable(m_opener);
179        m_selectBoxState.setValue(I_CmsLayoutBundle.INSTANCE.generalCss().cornerAll());
180
181        m_selectorState = new CmsStyleVariable(m_popup);
182        m_selectorState.setValue(I_CmsLayoutBundle.INSTANCE.generalCss().cornerBottom());
183
184        m_opener.addStyleName(CSS.selectBoxSelected());
185        addHoverHandlers(m_opener);
186        addMainPanelHoverHandlers(m_panel);
187        m_openClose = new CmsPushButton(I_CmsButton.TRIANGLE_RIGHT, I_CmsButton.TRIANGLE_DOWN);
188        m_openClose.setButtonStyle(ButtonStyle.FONT_ICON, null);
189        m_openClose.setSize(Size.small);
190        m_openClose.addStyleName(CSS.selectIcon());
191        m_panel.add(m_openClose);
192        m_openClose.addClickHandler(new ClickHandler() {
193
194            /**
195             * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
196             */
197            public void onClick(ClickEvent event) {
198
199                if (m_popup.isShowing()) {
200                    close();
201                } else {
202                    open();
203                }
204            }
205        });
206        m_popup.setWidget(m_selector);
207        m_popup.addStyleName(CSS.selectorPopup());
208        m_popup.addAutoHidePartner(m_panel.getElement());
209
210        m_popup.addStyleName(CSS.selectBoxSelector());
211        m_popup.addStyleName(I_CmsLayoutBundle.INSTANCE.generalCss().opencms());
212        m_popup.addStyleName(I_CmsLayoutBundle.INSTANCE.generalCss().cornerBottom());
213        m_popup.addStyleName(I_CmsLayoutBundle.INSTANCE.generalCss().textMedium());
214        m_popup.addCloseHandler(new CloseHandler<PopupPanel>() {
215
216            /**
217             * @see CloseHandler#onClose(CloseEvent)
218             */
219            public void onClose(CloseEvent<PopupPanel> e) {
220
221                close();
222            }
223        });
224        initOpener();
225    }
226
227    /**
228     * @see com.google.gwt.event.dom.client.HasFocusHandlers#addFocusHandler(com.google.gwt.event.dom.client.FocusHandler)
229     */
230    public HandlerRegistration addFocusHandler(FocusHandler handler) {
231
232        return addDomHandler(handler, FocusEvent.getType());
233    }
234
235    /**
236     * Adds a new select option to the select box.<p>
237     *
238     * @param cell the widget representing the select option
239     */
240    public void addOption(OPTION cell) {
241
242        String value = cell.getValue();
243        boolean first = m_selectCells.isEmpty();
244        m_selectCells.put(value, cell);
245
246        m_selector.add(cell);
247        if (first) {
248            selectValue(value);
249            m_firstValue = value;
250        }
251        initSelectCell(cell);
252    }
253
254    /**
255     * @see com.google.gwt.event.logical.shared.HasValueChangeHandlers#addValueChangeHandler(com.google.gwt.event.logical.shared.ValueChangeHandler)
256     */
257    public HandlerRegistration addValueChangeHandler(final ValueChangeHandler<String> handler) {
258
259        return super.addHandler(handler, ValueChangeEvent.getType());
260    }
261
262    /**
263     * Adds a widget.<p>
264     *
265     * @param widget the widget to add
266     */
267    public void addWidget(Widget widget) {
268
269        m_panel.add(widget);
270    }
271
272    /**
273     * Returns whether the select options are being displayed below or above the widget.<p>
274     *
275     * @return <code>true</code> in case the select options are displayed above the widget
276     */
277    public boolean displayingAbove() {
278
279        int popupHeight = getPopupHeight();
280        // Calculate top position for the popup
281        int top = m_opener.getAbsoluteTop();
282
283        // Make sure scrolling is taken into account, since
284        // box.getAbsoluteTop() takes scrolling into account.
285        int windowTop = Window.getScrollTop();
286        int windowBottom = Window.getScrollTop() + Window.getClientHeight();
287
288        // Distance from the top edge of the window to the top edge of the
289        // text box
290        int distanceFromWindowTop = top - windowTop;
291
292        // Distance from the bottom edge of the window to the bottom edge of
293        // the text box
294        int distanceToWindowBottom = windowBottom - (top + m_opener.getOffsetHeight());
295
296        // If there is not enough space for the popup's height below the text
297        // box and there IS enough space for the popup's height above the text
298        // box, then then position the popup above the text box. However, if there
299        // is not enough space on either side, then stick with displaying the
300        // popup below the text box.
301        boolean displayAbove = (distanceFromWindowTop > distanceToWindowBottom)
302            && (distanceToWindowBottom < popupHeight);
303        return displayAbove;
304    }
305
306    /**
307     * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFieldType()
308     */
309    public FieldType getFieldType() {
310
311        return FieldType.STRING;
312    }
313
314    /**
315     * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFormValue()
316     */
317    public Object getFormValue() {
318
319        if (m_selectedValue == null) {
320            return "";
321        }
322        return m_selectedValue;
323    }
324
325    /**
326     * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#getFormValueAsString()
327     */
328    public String getFormValueAsString() {
329
330        return (String)getFormValue();
331    }
332
333    /**
334     * Returns the selector of this widget.<p>
335     *
336     * @return the selector of this widget
337     */
338    public Panel getSelectorPopup() {
339
340        return m_popup;
341    }
342
343    /**
344     * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#isEnabled()
345     */
346    public boolean isEnabled() {
347
348        return m_enabled;
349    }
350
351    /**
352     * @see com.google.gwt.user.client.ui.Composite#onBrowserEvent(com.google.gwt.user.client.Event)
353     */
354    @Override
355    public void onBrowserEvent(Event event) {
356
357        // Should not act on button if disabled.
358        if (!isEnabled()) {
359            return;
360        }
361        super.onBrowserEvent(event);
362    }
363
364    /**
365     * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#reset()
366     */
367    public void reset() {
368
369        close();
370        onValueSelect(m_firstValue);
371    }
372
373    /**
374     * Helper method to set the current selected option.<p>
375     *
376     * This method does not trigger the "value changed" event.<p>
377     *
378     * @param value the new value
379     */
380    public void selectValue(String value) {
381
382        if (m_selectCells.get(value) == null) {
383            return;
384        }
385
386        updateOpener(value);
387        if (m_textMetricsPrefix != null) {
388            truncate(m_textMetricsPrefix, m_widgetWidth);
389        }
390        m_selectedValue = value;
391        close();
392    }
393
394    /**
395     * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setEnabled(boolean)
396     */
397    public void setEnabled(boolean enabled) {
398
399        close();
400        m_enabled = enabled;
401        getElement().setPropertyBoolean("disabled", !enabled);
402        m_openClose.setEnabled(enabled);
403        if (enabled) {
404            removeStyleName(CSS.selectBoxDisabled());
405        } else {
406            addStyleName(CSS.selectBoxDisabled());
407        }
408    }
409
410    /**
411     * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setErrorMessage(java.lang.String)
412     */
413    public void setErrorMessage(String errorMessage) {
414
415        m_error.setText(errorMessage);
416    }
417
418    /**
419     * Sets the form value of this select box.<p>
420     *
421     * @param value the new value
422     */
423    public void setFormValue(Object value) {
424
425        setFormValue(value, false);
426    }
427
428    /**
429     * Sets the form value of this select box.<p>
430     *
431     * @param value the new value
432     * @param fireEvents true if change events should be fired
433     */
434    public void setFormValue(Object value, boolean fireEvents) {
435
436        if (value == null) {
437            value = "";
438        }
439        if (!"".equals(value) && !m_selectCells.containsKey(value)) {
440            OPTION option = createUnknownOption((String)value);
441            if (option != null) {
442                addOption(option);
443            }
444        }
445        if (value instanceof String) {
446            String strValue = (String)value;
447            onValueSelect(strValue, fireEvents);
448        }
449    }
450
451    /**
452     * @see org.opencms.gwt.client.ui.input.I_CmsFormWidget#setFormValueAsString(java.lang.String)
453     */
454    public void setFormValueAsString(String formValue) {
455
456        setFormValue(formValue);
457    }
458
459    /**
460     * Sets the behavior of the popup if the input is bigger than the selectbox itself.
461     *
462     * @param resize <code>true</code> to resize
463     */
464    public void setPopupResize(boolean resize) {
465
466        m_resizePopup = resize;
467    }
468
469    /**
470     * @see org.opencms.gwt.client.ui.I_CmsTruncable#truncate(java.lang.String, int)
471     */
472    public void truncate(String textMetricsPrefix, int widgetWidth) {
473
474        m_textMetricsPrefix = textMetricsPrefix;
475        m_widgetWidth = widgetWidth;
476        truncateOpener(textMetricsPrefix, widgetWidth);
477    }
478
479    /**
480     * Internal helper method for clearing the select options.<p>
481     */
482    protected void clearItems() {
483
484        m_selectCells.clear();
485        m_selector.clear();
486        m_selectedValue = null;
487    }
488
489    /**
490     * Internal method which is called when the selector is closed.<p>
491     */
492    protected void close() {
493
494        if (!m_enabled) {
495            return;
496        }
497        m_openClose.setDown(false);
498        m_popup.hide();
499        m_selectBoxState.setValue(I_CmsLayoutBundle.INSTANCE.generalCss().cornerAll());
500    }
501
502    /**
503     * Internal method to create a select option for an unknown value.<p>
504     *
505     * @param value the value for which to create the option
506     *
507     * @return the new option
508     */
509    protected abstract OPTION createUnknownOption(String value);
510
511    /**
512     * Handle clicks on the opener.<p>
513     *
514     * @param e the click event
515     */
516    @UiHandler("m_opener")
517    protected void doClickOpener(ClickEvent e) {
518
519        toggleOpen();
520    }
521
522    /**
523     * Returns the offset height of the popup panel, should also work when the popup is currently not showing.<p>
524     *
525     * @return the offset height
526     */
527    protected int getPopupHeight() {
528
529        if (m_popup.isShowing()) {
530            return m_popup.getOffsetHeight();
531        } else {
532            Element el = CmsDomUtil.clone(m_popup.getElement());
533            RootPanel.getBodyElement().appendChild(el);
534            el.getStyle().setProperty("position", "absolute");
535            int height = el.getOffsetHeight();
536            el.removeFromParent();
537            return height;
538        }
539    }
540
541    /**
542     * The implementation of this method should initialize the opener of the select box.<p>
543     */
544    protected abstract void initOpener();
545
546    /**
547     * Initializes the selector width.<p>
548     *
549     * @return the preferred popup width
550     */
551    protected int initPreferredPopupWidth() {
552
553        int width = m_opener.getOffsetWidth() - 2 /*border*/;
554        for (Widget widget : m_selector) {
555            if (widget instanceof A_CmsSelectCell) {
556                int cellWidth = ((A_CmsSelectCell)widget).getRequiredWidth();
557                if (cellWidth > width) {
558                    width = cellWidth;
559                }
560            }
561        }
562        return width;
563    }
564
565    /**
566     * @see com.google.gwt.user.client.ui.Composite#onDetach()
567     */
568    @Override
569    protected void onDetach() {
570
571        super.onDetach();
572        removeWindowResizeHandler();
573    }
574
575    /**
576     * Handles the focus event on the opener.<p>
577     *
578     * @param event the focus event
579     */
580    @UiHandler("m_opener")
581    protected void onFocus(FocusEvent event) {
582
583        CmsDomUtil.fireFocusEvent(this);
584    }
585
586    /**
587     * @see com.google.gwt.user.client.ui.Widget#onLoad()
588     */
589    @Override
590    protected void onLoad() {
591
592        removeWindowResizeHandler();
593        m_windowResizeHandlerReg = Window.addResizeHandler(new ResizeHandler() {
594
595            public void onResize(ResizeEvent event) {
596
597                close();
598            }
599        });
600
601        m_mousewheelRegistration = RootPanel.get().addDomHandler(new MouseWheelHandler() {
602
603            public void onMouseWheel(MouseWheelEvent event) {
604
605                Scheduler.get().scheduleDeferred(new ScheduledCommand() {
606
607                    public void execute() {
608
609                        positionPopup();
610                    }
611                });
612
613            }
614        }, MouseWheelEvent.getType());
615
616    }
617
618    /**
619     * @see com.google.gwt.user.client.ui.Widget#onUnload()
620     */
621    @Override
622    protected void onUnload() {
623
624        super.onUnload();
625        if (m_mousewheelRegistration != null) {
626            m_mousewheelRegistration.removeHandler();
627        }
628    }
629
630    /**
631     * This method is called when a value is selected.<p>
632     *
633     * @param value the selected value
634     */
635    protected void onValueSelect(String value) {
636
637        onValueSelect(value, true);
638    }
639
640    /**
641     * Internal handler method which is called when a new value is selected.<p>
642     *
643     * @param value the new value
644     * @param fireEvents true if change events should be fired
645     */
646    protected void onValueSelect(String value, boolean fireEvents) {
647
648        String oldValue = m_selectedValue;
649        selectValue(value);
650        if (fireEvents) {
651            if ((oldValue == null) || !oldValue.equals(value)) {
652                // fire value change only if the the value really changed
653                ValueChangeEvent.<String> fire(this, value);
654            }
655        }
656    }
657
658    /**
659     * Internal method which is called when the selector is opened.<p>
660     */
661    protected void open() {
662
663        if (!m_enabled) {
664            return;
665        }
666        m_openClose.setDown(true);
667        int selectorWidth;
668        // if the resize option is deactivated the popup should not be wider than the selectbox.
669        // Default its true.
670        if (!m_resizePopup) {
671            selectorWidth = m_opener.getOffsetWidth() - 2;
672        } else {
673            if (m_preferredPopupWidth == 0) {
674                m_preferredPopupWidth = initPreferredPopupWidth();
675            }
676            selectorWidth = m_preferredPopupWidth;
677            int windowWidth = Window.getClientWidth();
678            if (m_preferredPopupWidth > windowWidth) {
679                selectorWidth = windowWidth - 10;
680            }
681        }
682        m_popup.setWidth(selectorWidth + "px");
683        m_popup.setWidget(m_selector);
684        m_popup.show();
685
686        positionPopup();
687    }
688
689    /**
690     * Deinstalls the window resize handler.<p>
691     */
692    protected void removeWindowResizeHandler() {
693
694        if (m_windowResizeHandlerReg != null) {
695            m_windowResizeHandlerReg.removeHandler();
696            m_windowResizeHandlerReg = null;
697        }
698    }
699
700    /**
701     * Abstract method whose implementation should truncate the opener widget(s).<p>
702     *
703     * @param prefix the text metrics prefix
704     * @param width the widget width
705     */
706    protected abstract void truncateOpener(String prefix, int width);
707
708    /**
709     * The implementation of this method should update the opener when a new value is selected by the user.<p>
710     *
711     * @param newValue the value selected by the user
712     */
713    protected abstract void updateOpener(String newValue);
714
715    /**
716     * Positions the select popup.<p>
717     */
718    void positionPopup() {
719
720        if (m_popup.isShowing()) {
721            int width = m_popup.getOffsetWidth();
722
723            int openerHeight = CmsDomUtil.getCurrentStyleInt(m_opener.getElement(), CmsDomUtil.Style.height);
724            int popupHeight = getPopupHeight();
725            int dx = 0;
726            if (width > (m_opener.getOffsetWidth())) {
727                int spaceOnTheRight = (Window.getClientWidth() + Window.getScrollLeft())
728                    - m_opener.getAbsoluteLeft()
729                    - width;
730                dx = spaceOnTheRight < 0 ? spaceOnTheRight : 0;
731            }
732            // Calculate top position for the popup
733            int top = m_opener.getAbsoluteTop();
734
735            // Make sure scrolling is taken into account, since
736            // box.getAbsoluteTop() takes scrolling into account.
737            int windowTop = Window.getScrollTop();
738            int windowBottom = Window.getScrollTop() + Window.getClientHeight();
739
740            // Distance from the top edge of the window to the top edge of the
741            // text box
742            int distanceFromWindowTop = top - windowTop;
743
744            // Distance from the bottom edge of the window to the bottom edge of
745            // the text box
746            int distanceToWindowBottom = windowBottom - (top + m_opener.getOffsetHeight());
747
748            boolean displayAbove = displayingAbove();
749
750            // in case there is not enough space, add a scroll panel to the selector popup
751            if ((displayAbove && (distanceFromWindowTop < popupHeight))
752                || (!displayAbove && (distanceToWindowBottom < popupHeight))) {
753                setScrollingSelector((displayAbove ? distanceFromWindowTop : distanceToWindowBottom) - 10);
754                popupHeight = m_popup.getOffsetHeight();
755            }
756
757            if (displayAbove) {
758                // Position above the text box
759                CmsDomUtil.positionElement(m_popup.getElement(), m_panel.getElement(), dx, -(popupHeight - 2));
760                m_selectBoxState.setValue(I_CmsLayoutBundle.INSTANCE.generalCss().cornerBottom());
761                m_selectorState.setValue(I_CmsLayoutBundle.INSTANCE.generalCss().cornerTop());
762            } else {
763                CmsDomUtil.positionElement(m_popup.getElement(), m_panel.getElement(), dx, openerHeight - 1);
764                m_selectBoxState.setValue(I_CmsLayoutBundle.INSTANCE.generalCss().cornerTop());
765                m_selectorState.setValue(I_CmsLayoutBundle.INSTANCE.generalCss().cornerBottom());
766            }
767        }
768    }
769
770    /**
771     * Helper method for adding event handlers for a 'hover' effect to the opener.<p>
772     *
773     * @param panel the opener
774     */
775    private void addHoverHandlers(FocusPanel panel) {
776
777        final CmsStyleVariable hoverVar = new CmsStyleVariable(panel);
778        hoverVar.setValue(I_CmsLayoutBundle.INSTANCE.globalWidgetCss().openerNoHover());
779        panel.addMouseOverHandler(new MouseOverHandler() {
780
781            /**
782             * @see com.google.gwt.event.dom.client.MouseOverHandler#onMouseOver(com.google.gwt.event.dom.client.MouseOverEvent)
783             */
784            public void onMouseOver(MouseOverEvent event) {
785
786                hoverVar.setValue(I_CmsLayoutBundle.INSTANCE.globalWidgetCss().openerHover());
787            }
788        });
789
790        panel.addMouseOutHandler(new MouseOutHandler() {
791
792            /**
793             * @see com.google.gwt.event.dom.client.MouseOutHandler#onMouseOut(com.google.gwt.event.dom.client.MouseOutEvent)
794             */
795            public void onMouseOut(MouseOutEvent event) {
796
797                hoverVar.setValue(I_CmsLayoutBundle.INSTANCE.globalWidgetCss().openerNoHover());
798            }
799        });
800
801    }
802
803    /**
804     * Helper method for adding event handlers for a 'hover' effect to the main panel.<p>
805     *
806     * @param panel the main panel
807     */
808    private void addMainPanelHoverHandlers(Panel panel) {
809
810        final CmsStyleVariable hoverPanel = new CmsStyleVariable(panel);
811        hoverPanel.setValue(I_CmsLayoutBundle.INSTANCE.globalWidgetCss().openerNoHover());
812        panel.addDomHandler(new MouseOverHandler() {
813
814            public void onMouseOver(MouseOverEvent event) {
815
816                hoverPanel.setValue(I_CmsLayoutBundle.INSTANCE.globalWidgetCss().openerHover());
817
818            }
819        }, MouseOverEvent.getType());
820        panel.addDomHandler(new MouseOutHandler() {
821
822            public void onMouseOut(MouseOutEvent event) {
823
824                hoverPanel.setValue(I_CmsLayoutBundle.INSTANCE.globalWidgetCss().openerNoHover());
825
826            }
827        }, MouseOutEvent.getType());
828    }
829
830    /**
831     * Initializes the event handlers of a select cell.<p>
832     *
833     * @param cell the select cell whose event handlers should be initialized
834     */
835    private void initSelectCell(final A_CmsSelectCell cell) {
836
837        cell.registerDomHandler(new ClickHandler() {
838
839            /**
840             * @see com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event.dom.client.ClickEvent)
841             */
842            public void onClick(ClickEvent e) {
843
844                onValueSelect(cell.getValue());
845                cell.removeStyleName(CSS.selectHover());
846            }
847        }, ClickEvent.getType());
848
849        cell.registerDomHandler(new MouseOverHandler() {
850
851            /**
852             * @see com.google.gwt.event.dom.client.MouseOverHandler#onMouseOver(com.google.gwt.event.dom.client.MouseOverEvent)
853             */
854            public void onMouseOver(MouseOverEvent e) {
855
856                cell.addStyleName(CSS.selectHover());
857
858            }
859        }, MouseOverEvent.getType());
860
861        cell.registerDomHandler(new MouseOutHandler() {
862
863            /**
864             * @see com.google.gwt.event.dom.client.MouseOutHandler#onMouseOut(com.google.gwt.event.dom.client.MouseOutEvent)
865             */
866            public void onMouseOut(MouseOutEvent e) {
867
868                cell.removeStyleName(CSS.selectHover());
869            }
870        }, MouseOutEvent.getType());
871    }
872
873    /**
874     * Adds a scroll panel to the selector popup.<p>
875     *
876     * @param availableHeight the available popup height
877     */
878    private void setScrollingSelector(int availableHeight) {
879
880        CmsScrollPanel panel = GWT.create(CmsScrollPanel.class);
881        HTMLElement elem = Js.cast(panel.getElement());
882        elem.style.maxHeight = MaxHeightUnionType.of("" + availableHeight + "px");
883        panel.setWidget(m_selector);
884        m_popup.setWidget(panel);
885    }
886
887    /**
888     * Toggles the state of the selector popup between 'open' and 'closed'.<p>
889     */
890    private void toggleOpen() {
891
892        if (!m_enabled) {
893            return;
894        }
895        if (m_popup.isShowing()) {
896            close();
897        } else {
898            open();
899        }
900    }
901}