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.form;
029
030import org.opencms.gwt.client.util.CmsJsUtil;
031
032import com.google.gwt.core.client.GWT;
033import com.google.gwt.dom.client.Element;
034import com.google.gwt.dom.client.EventTarget;
035import com.google.gwt.dom.client.NativeEvent;
036import com.google.gwt.dom.client.Style;
037import com.google.gwt.dom.client.Style.Unit;
038import com.google.gwt.dom.client.StyleInjector;
039import com.google.gwt.uibinder.client.UiBinder;
040import com.google.gwt.uibinder.client.UiField;
041import com.google.gwt.user.client.Event;
042import com.google.gwt.user.client.Event.NativePreviewEvent;
043import com.google.gwt.user.client.Event.NativePreviewHandler;
044import com.google.gwt.user.client.ui.Composite;
045import com.google.gwt.user.client.ui.HTML;
046import com.google.gwt.user.client.ui.Panel;
047import com.google.gwt.user.client.ui.RootPanel;
048import com.google.gwt.user.client.ui.Widget;
049
050/**
051 * Tooltip widget for element setting help texts.<p>
052 */
053public class CmsFieldTooltip extends Composite {
054
055    /**
056     * Data needed to create a tooltip.<p>
057     */
058    public static class Data {
059
060        /** The tooltip content. */
061        private String m_info;
062
063        /** True if the content is HTML, false if it is plain text. */
064        private boolean m_isHtml;
065
066        /** The icon for which the tooltip should be shown. */
067        private Panel m_reference;
068
069        /**
070         * Creates a new instance.<p>
071         * @param reference the icon for which the tooltip should be shown
072         * @param info the tooltip content
073         * @param isHtml true if the content is HTML
074         */
075        public Data(Panel reference, String info, boolean isHtml) {
076
077            m_info = info;
078            m_isHtml = isHtml;
079            m_reference = reference;
080        }
081
082        /**
083         * Gets the tooltip content.<p>
084         *
085         * @return the tooltip content
086         */
087        public String getInfo() {
088
089            return m_info;
090        }
091
092        /**
093         * Gets the icon for which the tooltip is intended.<p>
094         *
095         * @return the icon for which to display a tooltip
096         */
097        public Panel getReference() {
098
099            return m_reference;
100        }
101
102        /**
103         * Returns true if the tooltip content is HTML.<p>
104         *
105         * @return true if the tooltip content is HTML
106         */
107        public boolean isHtml() {
108
109            return m_isHtml;
110        }
111
112        /**
113         * @see java.lang.Object#toString()
114         */
115        @Override
116        public String toString() {
117
118            return m_info;
119        }
120    }
121
122    /**
123     * Event handler for managing tooltip visibility.<p>
124     */
125    public static class Handler implements NativePreviewHandler {
126
127        /** The currently active tooltip. */
128        private CmsFieldTooltip m_tooltip;
129
130        /**
131         * Creats a new instance.<p>
132         */
133        public Handler() {
134
135            Event.addNativePreviewHandler(this);
136        }
137
138        /**
139         * Called when the user clicks on the icon.<p>
140         *
141         * @param data the tooltip data
142         */
143        public void buttonClick(Data data) {
144
145            if (m_tooltip != null) {
146                m_tooltip.makePersistent();
147            }
148        }
149
150        /**
151         * Called if the mouse moves over the button with a tooltip.<p>
152         *
153         * @param data the tooltip data for a button
154         */
155        public void buttonHover(Data data) {
156
157            if (data != getData()) {
158                closeTooltip();
159                CmsFieldTooltip tooltip = new CmsFieldTooltip(data);
160                if (data.isHtml()) {
161                    tooltip.getLabel().setHTML(data.getInfo());
162                } else {
163                    tooltip.getLabel().setText(data.getInfo());
164                }
165                m_tooltip = tooltip;
166                RootPanel.get().add(tooltip);
167                position(tooltip.getElement(), data.getReference().getElement());
168            }
169        }
170
171        /**
172         * Called if the mouse leaves a button with a tooltip.<p>
173         *
174         * @param data the tooltip data for the button
175         */
176        public void buttonOut(Data data) {
177
178            closeTooltip(false);
179        }
180
181        /**
182         * Closes the tooltip.<p>
183         */
184        public void closeTooltip() {
185
186            closeTooltip(true);
187        }
188
189        /**
190         * Closes the active tooltip.<p>
191         *
192         * @param closePersistent true if a persistent tooltip should also be closed
193         */
194        public void closeTooltip(boolean closePersistent) {
195
196            if (m_tooltip != null) {
197
198                if (m_tooltip.isPersistent() && !closePersistent) {
199                    return;
200                }
201                if (CmsJsUtil.getAttributeString(CmsJsUtil.getWindow(), "cmsDisableCloseTooltip") == null) {
202                    m_tooltip.removeFromParent();
203                }
204                m_tooltip = null;
205            }
206        }
207
208        /**
209         * Gets the target element for a native event, or null if there is no target element.<p>
210         *
211         * @param nativeEvent the native event
212         * @return the target element, or null if there is no target element
213         */
214        public Element getTargetElement(NativeEvent nativeEvent) {
215
216            EventTarget target = nativeEvent.getEventTarget();
217            Element targetElement = null;
218            if (Element.is(target)) {
219                targetElement = Element.as(target);
220            }
221            return targetElement;
222        }
223
224        /**
225         * @see com.google.gwt.user.client.Event.NativePreviewHandler#onPreviewNativeEvent(com.google.gwt.user.client.Event.NativePreviewEvent)
226         */
227        public void onPreviewNativeEvent(NativePreviewEvent event) {
228
229            int eventType = event.getTypeInt();
230            NativeEvent nativeEvent = event.getNativeEvent();
231            switch (eventType) {
232                case Event.ONMOUSEWHEEL:
233                    closeTooltip();
234                    break;
235                case Event.ONMOUSEDOWN:
236                    if (tooltipContains(getTargetElement(nativeEvent))) {
237                        event.consume();
238                        return;
239                    } else {
240                        closeTooltip();
241                    }
242                    break;
243                case Event.ONCLICK:
244                    if (tooltipContains(getTargetElement(nativeEvent))) {
245                        event.consume();
246                        return;
247                    }
248                    break;
249                default: // do nothing
250                    break;
251
252            }
253        }
254
255        /**
256         * Checks if the tooltip contains a given element.<p>
257         *
258         * @param targetElement the element to check
259         * @return true if the tooltip contains an element
260         */
261        public boolean tooltipContains(Element targetElement) {
262
263            if ((targetElement != null) && (m_tooltip != null)) {
264                return m_tooltip.getElement().isOrHasChild(targetElement)
265                    || m_tooltip.getData().getReference().getElement().isOrHasChild(targetElement);
266            }
267            return false;
268        }
269
270        /**
271         * Gets the tooltip data for the active tooltip, or null if no tooltip is active.<p>
272         *
273         * @return the tooltip data
274         */
275        Data getData() {
276
277            if (m_tooltip == null) {
278                return null;
279            }
280            return m_tooltip.getData();
281        }
282    }
283
284    /** The ui binder interface for this widget. */
285    protected interface I_UiBinder extends UiBinder<Widget, CmsFieldTooltip> {
286        //UIBinder interface
287
288    }
289
290    /** The handler instance. */
291    private static Handler m_handler = new Handler();
292
293    /** The label with the help text. */
294    @UiField
295    protected HTML m_label;
296
297    /** The tooltip data. */
298    private Data m_data;
299
300    /** Flag indicating whether tooltip is persistent (i.e. not closed when the mouse cursor leaves the icon). */
301    private boolean m_persistent;
302
303    /**
304     * Creates a new instance.<p>
305     *
306     * @param data the tooltip data
307     */
308    public CmsFieldTooltip(Data data) {
309
310        I_UiBinder uiBinder = GWT.create(I_UiBinder.class);
311        initWidget(uiBinder.createAndBindUi(this));
312        // force synchronous injection of styles
313        StyleInjector.flush();
314        m_data = data;
315    }
316
317    /**
318     * Gets the handler instance.<p>
319     *
320     * @return the handler instance
321     */
322    public static Handler getHandler() {
323
324        return m_handler;
325    }
326
327    /**
328     * Positions the tooltip.<p>
329     *
330     *
331     * @param elem the tooltip element
332     * @param referenceElement the tooltip icon element
333     */
334    public static void position(Element elem, Element referenceElement) {
335
336        int dy = 25;
337        Style style = elem.getStyle();
338        style.setLeft(0, Unit.PX);
339        style.setTop(0, Unit.PX);
340        int myX = elem.getAbsoluteLeft();
341        int myY = elem.getAbsoluteTop();
342        int refX = referenceElement.getAbsoluteLeft();
343        int refY = referenceElement.getAbsoluteTop();
344        int refWidth = referenceElement.getOffsetWidth();
345        int newX = (refX - myX - ((2 * elem.getOffsetWidth()) / 3)) + (refWidth / 2);
346        int newY = (refY - myY) + dy;
347        style.setLeft(newX, Unit.PX);
348        style.setTop(newY, Unit.PX);
349    }
350
351    /**
352     * Gets the tooltip data.<p>
353     *
354     * @return the tooltip data
355     */
356    public Data getData() {
357
358        return m_data;
359    }
360
361    /**
362     * Gets the label for the help text.<p>
363     *
364     * @return the label for the help text
365     */
366    public HTML getLabel() {
367
368        return m_label;
369    }
370
371    /**
372     * Checks if the tooltip is persistent,  i.e. it can no longer be closed by leaving the button with the mouse cursor,
373     * but needs to be closed by clicking somewhere else.<p>
374     *
375     * @return true if the tooltip is persistent
376     */
377    public boolean isPersistent() {
378
379        return m_persistent;
380    }
381
382    /**
383     * Makes the tooltip persistent, i.e. it can no longer be closed by leaving the button with the mouse cursor,
384     * but needs to be closed by clicking somewhere else.<p>
385     */
386    public void makePersistent() {
387
388        m_persistent = true;
389    }
390
391}