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.gwt.client.util.CmsClientStringUtil;
031import org.opencms.gwt.client.util.CmsDomUtil;
032import org.opencms.gwt.client.util.CmsPositionBean;
033
034import java.util.ArrayList;
035import java.util.HashMap;
036import java.util.List;
037import java.util.Map;
038
039import com.google.gwt.core.client.GWT;
040import com.google.gwt.dom.client.Element;
041import com.google.gwt.dom.client.Style;
042import com.google.gwt.dom.client.Style.Display;
043import com.google.gwt.dom.client.Style.Unit;
044import com.google.gwt.event.dom.client.ClickEvent;
045import com.google.gwt.event.dom.client.ClickHandler;
046import com.google.gwt.event.dom.client.HasClickHandlers;
047import com.google.gwt.event.shared.HandlerRegistration;
048import com.google.gwt.uibinder.client.UiBinder;
049import com.google.gwt.uibinder.client.UiField;
050import com.google.gwt.user.client.Window;
051import com.google.gwt.user.client.ui.Composite;
052import com.google.gwt.user.client.ui.FlowPanel;
053import com.google.gwt.user.client.ui.HTMLPanel;
054import com.google.gwt.user.client.ui.RootPanel;
055import com.google.gwt.user.client.ui.Widget;
056
057/**
058 * In-line edit overlay covering rest of the page.<p>
059 */
060public class CmsInlineEditOverlay extends Composite implements HasClickHandlers {
061
062    /** The ui binder. */
063    interface I_CmsInlineEditOverlayUiBinder extends UiBinder<HTMLPanel, CmsInlineEditOverlay> {
064        // nothing to do
065    }
066
067    /** The width rquired by the button bar. */
068    private static final int BUTTON_BAR_WIDTH = 28;
069
070    /** List of present overlays. */
071    private static List<CmsInlineEditOverlay> m_overlays = new ArrayList<CmsInlineEditOverlay>();
072
073    /** The ui binder instance. */
074    private static I_CmsInlineEditOverlayUiBinder uiBinder = GWT.create(I_CmsInlineEditOverlayUiBinder.class);
075
076    /** Bottom border. */
077    @UiField
078    protected Element m_borderBottom;
079
080    /** Left border. */
081    @UiField
082    protected Element m_borderLeft;
083
084    /** Right border. */
085    @UiField
086    protected Element m_borderRight;
087
088    /** Top border. */
089    @UiField
090    protected Element m_borderTop;
091
092    /** The button bar element. */
093    @UiField
094    protected Element m_buttonBar;
095
096    /** Edit overlay. */
097    @UiField
098    protected Element m_overlayBottom;
099
100    /** Edit overlay. */
101    @UiField
102    protected Element m_overlayLeft;
103
104    /** Edit overlay. */
105    @UiField
106    protected Element m_overlayRight;
107
108    /** Edit overlay. */
109    @UiField
110    protected Element m_overlayTop;
111
112    /** The edit button panel. */
113    @UiField
114    FlowPanel m_buttonPanel;
115
116    /** Style of border. */
117    private Style m_borderBottomStyle;
118
119    /** Style of border. */
120    private Style m_borderLeftStyle;
121
122    /** Style of border. */
123    private Style m_borderRightStyle;
124
125    /** Style of border. */
126    private Style m_borderTopStyle;
127
128    /** Map of attached edit buttons and their absolute top positions. */
129    private Map<CmsInlineEntityWidget, Integer> m_buttons;
130
131    /** The current overlay position. */
132    private CmsPositionBean m_currentPosition;
133
134    /** The element to surround with the overlay. */
135    private Element m_element;
136
137    /** Flag indicating this overlay has a button bar. */
138    private boolean m_hasButtonBar;
139
140    /** The main panel. */
141    private HTMLPanel m_main;
142
143    /** The overlay offset. */
144    private int m_offset = 3;
145
146    /** Style of overlay. */
147    private Style m_overlayBottomStyle;
148
149    /** Style of overlay. */
150    private Style m_overlayLeftStyle;
151
152    /** Style of overlay. */
153    private Style m_overlayRightStyle;
154
155    /** Style of overlay. */
156    private Style m_overlayTopStyle;
157
158    /**
159     * Constructor.<p>
160     *
161     * @param element the element to surround with the overlay
162     */
163    public CmsInlineEditOverlay(Element element) {
164
165        m_main = uiBinder.createAndBindUi(this);
166        initWidget(m_main);
167        m_element = element;
168        m_overlayLeftStyle = m_overlayLeft.getStyle();
169        m_overlayBottomStyle = m_overlayBottom.getStyle();
170        m_overlayRightStyle = m_overlayRight.getStyle();
171        m_overlayTopStyle = m_overlayTop.getStyle();
172        m_borderBottomStyle = m_borderBottom.getStyle();
173        m_borderLeftStyle = m_borderLeft.getStyle();
174        m_borderRightStyle = m_borderRight.getStyle();
175        m_borderTopStyle = m_borderTop.getStyle();
176        m_buttonBar.getStyle().setDisplay(Display.NONE);
177        m_buttonPanel.addDomHandler(new ClickHandler() {
178
179            public void onClick(ClickEvent event) {
180
181                // prevent the click event to propagated from the button panel to the main widget
182                event.stopPropagation();
183            }
184        }, ClickEvent.getType());
185        m_buttons = new HashMap<CmsInlineEntityWidget, Integer>();
186    }
187
188    /**
189     * Adds an overlay surrounding the given DOM element.<p>
190     *
191     * @param element the element
192     *
193     * @return the overlay widget
194     */
195    public static CmsInlineEditOverlay addOverlayForElement(Element element) {
196
197        CmsInlineEditOverlay overlay = new CmsInlineEditOverlay(element);
198        if (!m_overlays.isEmpty()) {
199            m_overlays.get(m_overlays.size() - 1).setVisible(false);
200        }
201        m_overlays.add(overlay);
202        RootPanel.get().add(overlay);
203        overlay.updatePosition();
204        overlay.checkZIndex();
205        return overlay;
206    }
207
208    /**
209     * Returns the root overlay if available.<p>
210     *
211     * @return the root overlay
212     */
213    public static CmsInlineEditOverlay getRootOverlay() {
214
215        return m_overlays.isEmpty() ? null : m_overlays.get(0);
216    }
217
218    /**
219     * Removes all present overlays.<p>
220     */
221    public static void removeAll() {
222
223        for (CmsInlineEditOverlay overlay : m_overlays) {
224            overlay.removeFromParent();
225        }
226        m_overlays.clear();
227    }
228
229    /**
230     * Removes the last overlay to display the previous or none.<p>
231     */
232    public static void removeLastOverlay() {
233
234        if (!m_overlays.isEmpty()) {
235            CmsInlineEditOverlay last = m_overlays.remove(m_overlays.size() - 1);
236            last.removeFromParent();
237        }
238        if (!m_overlays.isEmpty()) {
239            m_overlays.get(m_overlays.size() - 1).setVisible(true);
240        }
241    }
242
243    /**
244     * Updates the current overlay's position.<p>
245     */
246    public static void updateCurrentOverlayPosition() {
247
248        if (!m_overlays.isEmpty()) {
249            m_overlays.get(m_overlays.size() - 1).updatePosition();
250        }
251    }
252
253    /**
254     * Adds a button widget to the button panel.<p>
255     *
256     * @param widget the button widget
257     * @param absoluteTop the absolute top position
258     */
259    public void addButton(CmsInlineEntityWidget widget, int absoluteTop) {
260
261        setButtonBarVisible(true);
262        m_buttonPanel.add(widget);
263        setButtonPosition(widget, absoluteTop);
264    }
265
266    /**
267     * @see com.google.gwt.event.dom.client.HasClickHandlers#addClickHandler(com.google.gwt.event.dom.client.ClickHandler)
268     */
269    public HandlerRegistration addClickHandler(ClickHandler handler) {
270
271        return addDomHandler(handler, ClickEvent.getType());
272    }
273
274    /**
275     * Increases the overlay z-index if necessary.<p>
276     */
277    public void checkZIndex() {
278
279        int zIndex = 100000;
280        Element parent = m_element.getParentElement();
281        while (parent != null) {
282            int parentIndex = CmsDomUtil.getCurrentStyleInt(parent, CmsDomUtil.Style.zIndex);
283            if (parentIndex > zIndex) {
284                zIndex = parentIndex;
285            }
286            parent = parent.getParentElement();
287        }
288        if (zIndex > 100000) {
289            getElement().getStyle().setZIndex(zIndex);
290        }
291    }
292
293    /**
294     * Clears and hides the button panel.<p>
295     */
296    public void clearButtonPanel() {
297
298        m_buttonPanel.clear();
299        m_buttons.clear();
300        setButtonBarVisible(false);
301    }
302
303    /**
304     * Updates the position of the given button widget.<p>
305     *
306     * @param widget the button widget
307     * @param absoluteTop the top absolute top position
308     */
309    public void setButtonPosition(CmsInlineEntityWidget widget, int absoluteTop) {
310
311        if (m_buttonPanel.getWidgetIndex(widget) > -1) {
312            int buttonBarTop = CmsClientStringUtil.parseInt(m_buttonBar.getStyle().getTop());
313            if (absoluteTop < buttonBarTop) {
314                absoluteTop = buttonBarTop;
315            }
316            int positionTop = getAvailablePosition(widget, absoluteTop) - buttonBarTop;
317            widget.getElement().getStyle().setTop(positionTop, Unit.PX);
318            if (CmsClientStringUtil.parseInt(m_buttonBar.getStyle().getHeight()) < (positionTop + 20)) {
319                increaseOverlayHeight(positionTop + 20);
320            }
321        }
322    }
323
324    /**
325     * Sets the overlay offset.<p>
326     *
327     * @param offset the offset
328     */
329    public void setOffset(int offset) {
330
331        m_offset = offset;
332    }
333
334    /**
335     * @see com.google.gwt.user.client.ui.UIObject#setVisible(boolean)
336     */
337    @Override
338    public void setVisible(boolean visible) {
339
340        super.setVisible(visible);
341        if (!visible && m_hasButtonBar) {
342            for (Widget widget : m_buttonPanel) {
343                if (widget instanceof CmsInlineEntityWidget) {
344                    ((CmsInlineEntityWidget)widget).setContentHighlightingVisible(false);
345                }
346            }
347        }
348    }
349
350    /**
351     * Updates the overlay position.<p>
352     */
353    public void updatePosition() {
354
355        setPosition(CmsPositionBean.getBoundingClientRect(m_element));
356        for (Widget widget : m_buttonPanel) {
357            if (widget instanceof CmsInlineEntityWidget) {
358                ((CmsInlineEntityWidget)widget).positionWidget();
359            }
360        }
361    }
362
363    /**
364     * Returns the available absolute top position for the given button.<p>
365     *
366     * @param widget the button widget
367     * @param absoluteTop the proposed position
368     *
369     * @return the available position
370     */
371    private int getAvailablePosition(CmsInlineEntityWidget widget, int absoluteTop) {
372
373        m_buttons.remove(widget);
374        boolean positionBlocked = true;
375        while (positionBlocked) {
376            positionBlocked = false;
377            for (int pos : m_buttons.values()) {
378                if (((pos - 24) < absoluteTop) && (absoluteTop < (pos + 24))) {
379                    positionBlocked = true;
380                    absoluteTop = pos + 25;
381                    break;
382                }
383            }
384        }
385        m_buttons.put(widget, Integer.valueOf(absoluteTop));
386        return absoluteTop;
387    }
388
389    /**
390     * Increases the overlay height to make space for edit buttons.<p>
391     *
392     * @param height the height to set
393     */
394    private void increaseOverlayHeight(int height) {
395
396        if (m_currentPosition != null) {
397            m_currentPosition.setHeight(height);
398            setPosition(m_currentPosition);
399        }
400    }
401
402    /**
403     * Sets button bar visibility.<p>
404     *
405     * @param visible <code>true</code> to set the button bar visible
406     */
407    private void setButtonBarVisible(boolean visible) {
408
409        if (m_hasButtonBar != visible) {
410            m_hasButtonBar = visible;
411            if (m_hasButtonBar) {
412
413                m_buttonBar.getStyle().clearDisplay();
414                int width = CmsClientStringUtil.parseInt(m_borderTopStyle.getWidth()) + BUTTON_BAR_WIDTH;
415                m_borderTopStyle.setWidth(width, Unit.PX);
416                m_borderBottomStyle.setWidth(width, Unit.PX);
417                m_borderRightStyle.setLeft(
418                    CmsClientStringUtil.parseInt(m_borderRightStyle.getLeft()) + BUTTON_BAR_WIDTH,
419                    Unit.PX);
420            } else {
421                m_buttonBar.getStyle().setDisplay(Display.NONE);
422                int width = CmsClientStringUtil.parseInt(m_borderTopStyle.getWidth()) - BUTTON_BAR_WIDTH;
423                m_borderTopStyle.setWidth(width, Unit.PX);
424                m_borderBottomStyle.setWidth(width, Unit.PX);
425                m_borderRightStyle.setLeft(
426                    CmsClientStringUtil.parseInt(m_borderRightStyle.getLeft()) - BUTTON_BAR_WIDTH,
427                    Unit.PX);
428            }
429        }
430    }
431
432    /**
433     * Sets position and size of the overlay area.<p>
434     *
435     * @param position the position of highlighted area
436     */
437    private void setPosition(CmsPositionBean position) {
438
439        m_currentPosition = position;
440        setSelectPosition(position.getLeft(), position.getTop(), position.getHeight(), position.getWidth());
441    }
442
443    /**
444     * Sets position and size of the overlay area.<p>
445     *
446     * @param posX the new X position
447     * @param posY the new Y position
448     * @param height the new height
449     * @param width the new width
450     */
451    private void setSelectPosition(int posX, int posY, int height, int width) {
452
453        int useWidth = Window.getClientWidth();
454        int bodyWidth = RootPanel.getBodyElement().getClientWidth() + RootPanel.getBodyElement().getOffsetLeft();
455        if (bodyWidth > useWidth) {
456            useWidth = bodyWidth;
457        }
458        int useHeight = Window.getClientHeight();
459        int bodyHeight = RootPanel.getBodyElement().getClientHeight() + RootPanel.getBodyElement().getOffsetTop();
460        if (bodyHeight > useHeight) {
461            useHeight = bodyHeight;
462        }
463
464        m_overlayLeftStyle.setWidth(posX - m_offset, Unit.PX);
465        m_overlayLeftStyle.setHeight(useHeight, Unit.PX);
466
467        m_borderLeftStyle.setHeight(height + (4 * m_offset), Unit.PX);
468        m_borderLeftStyle.setTop(posY - (2 * m_offset), Unit.PX);
469        m_borderLeftStyle.setLeft(posX - (2 * m_offset), Unit.PX);
470
471        m_overlayTopStyle.setLeft(posX - m_offset, Unit.PX);
472        m_overlayTopStyle.setWidth(width + (2 * m_offset), Unit.PX);
473        m_overlayTopStyle.setHeight(posY - m_offset, Unit.PX);
474
475        m_borderTopStyle.setLeft(posX - m_offset, Unit.PX);
476        m_borderTopStyle.setTop(posY - (2 * m_offset), Unit.PX);
477        if (m_hasButtonBar) {
478            m_borderTopStyle.setWidth(width + (2 * m_offset) + BUTTON_BAR_WIDTH, Unit.PX);
479        } else {
480            m_borderTopStyle.setWidth(width + (2 * m_offset), Unit.PX);
481        }
482
483        m_overlayBottomStyle.setLeft(posX - m_offset, Unit.PX);
484        m_overlayBottomStyle.setWidth(width + m_offset + m_offset, Unit.PX);
485        m_overlayBottomStyle.setHeight(useHeight - posY - height - m_offset, Unit.PX);
486        m_overlayBottomStyle.setTop(posY + height + m_offset, Unit.PX);
487
488        m_borderBottomStyle.setLeft(posX - m_offset, Unit.PX);
489        m_borderBottomStyle.setTop((posY + height) + m_offset, Unit.PX);
490        if (m_hasButtonBar) {
491            m_borderBottomStyle.setWidth(width + (2 * m_offset) + BUTTON_BAR_WIDTH, Unit.PX);
492        } else {
493            m_borderBottomStyle.setWidth(width + (2 * m_offset), Unit.PX);
494        }
495
496        m_overlayRightStyle.setLeft(posX + width + m_offset, Unit.PX);
497        m_overlayRightStyle.setWidth(useWidth - posX - width - m_offset, Unit.PX);
498        m_overlayRightStyle.setHeight(useHeight, Unit.PX);
499
500        m_borderRightStyle.setHeight(height + (4 * m_offset), Unit.PX);
501        m_borderRightStyle.setTop(posY - (2 * m_offset), Unit.PX);
502        if (m_hasButtonBar) {
503            m_borderRightStyle.setLeft(posX + width + m_offset + BUTTON_BAR_WIDTH, Unit.PX);
504        } else {
505            m_borderRightStyle.setLeft(posX + width + m_offset, Unit.PX);
506        }
507
508        m_buttonBar.getStyle().setTop(posY - m_offset, Unit.PX);
509        m_buttonBar.getStyle().setHeight(height + (2 * m_offset), Unit.PX);
510        m_buttonBar.getStyle().setLeft(posX + width + m_offset + 1, Unit.PX);
511    }
512}