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;
029
030import org.opencms.gwt.client.ui.css.I_CmsLayoutBundle;
031import org.opencms.gwt.client.util.CmsPositionBean;
032import org.opencms.gwt.client.util.CmsStyleVariable;
033
034import java.util.ArrayList;
035import java.util.Collections;
036import java.util.List;
037
038import com.google.gwt.core.client.GWT;
039import com.google.gwt.dom.client.DivElement;
040import com.google.gwt.dom.client.Document;
041import com.google.gwt.dom.client.Element;
042import com.google.gwt.dom.client.Node;
043import com.google.gwt.dom.client.NodeList;
044import com.google.gwt.dom.client.Style;
045import com.google.gwt.dom.client.Style.Unit;
046import com.google.gwt.uibinder.client.UiBinder;
047import com.google.gwt.uibinder.client.UiField;
048import com.google.gwt.user.client.Window;
049import com.google.gwt.user.client.ui.Composite;
050import com.google.gwt.user.client.ui.HTML;
051
052import elemental2.dom.DomGlobal;
053import jsinterop.base.Js;
054
055/**
056 * A Widget to display a highlighting border around a specified position.<p>
057 *
058 * @since 8.0.0
059 */
060public class CmsHighlightingBorder extends Composite {
061
062    /** Enumeration of available border colours. */
063    public enum BorderColor {
064
065        /** Color blue. */
066        blue(I_CmsLayoutBundle.INSTANCE.highlightCss().colorBlue()),
067
068        /** Color grey. */
069        grey(I_CmsLayoutBundle.INSTANCE.highlightCss().colorGrey()),
070
071        /** Color red. */
072        red(I_CmsLayoutBundle.INSTANCE.highlightCss().colorRed()),
073
074        /** Solid grey. */
075        solidGrey(I_CmsLayoutBundle.INSTANCE.highlightCss().colorSolidGrey());
076
077        /** CSS class used to display the border colour. */
078        private String m_cssClass;
079
080        /**
081         * Constructor.<p>
082         *
083         * @param cssClass the CSS class to display the border colour
084         */
085        private BorderColor(String cssClass) {
086
087            m_cssClass = cssClass;
088        }
089
090        /**
091         * Returns the associated CSS class.<p>
092         *
093         * @return the CSS class
094         */
095        public String getCssClass() {
096
097            return m_cssClass;
098        }
099    }
100
101    /** The ui-binder interface for this composite. */
102    interface I_CmsHighlightingBorderUiBinder extends UiBinder<HTML, CmsHighlightingBorder> {
103        // GWT interface, nothing to do here
104    }
105
106    /** The default border offset to the given position. */
107    private static final int BORDER_OFFSET = 4;
108
109    /** The border width. */
110    private static final int BORDER_WIDTH = 2;
111
112    /** The ui-binder instance. */
113    private static I_CmsHighlightingBorderUiBinder uiBinder = GWT.create(I_CmsHighlightingBorderUiBinder.class);
114
115    /** Horizontal offset of the midpoint separators. */
116    public static final int SEPARATOR_OFFSET = 30;
117
118    /** The bottom border. */
119    @UiField
120    protected DivElement m_borderBottom;
121
122    /** The left border. */
123    @UiField
124    protected DivElement m_borderLeft;
125
126    /** The right border. */
127    @UiField
128    protected DivElement m_borderRight;
129
130    /** The element containing the midpoint separators, if any. */
131    @UiField
132    protected DivElement m_midpoints;
133
134    /** The top border. */
135    @UiField
136    protected DivElement m_borderTop;
137
138    /** The border offset. */
139    private int m_borderOffset;
140
141    /** The style variable used to change the color of the border. */
142    private CmsStyleVariable m_colorVariable;
143
144    /** The positioning parent element. */
145    private Element m_positioningParent;
146
147    private boolean m_correctTop;
148
149    /**
150     * Constructor.<p>
151     *
152     * @param position the position data
153     * @param color the border color
154     */
155    public CmsHighlightingBorder(CmsPositionBean position, BorderColor color) {
156
157        this(
158            position.getHeight(),
159            position.getWidth(),
160            position.getLeft(),
161            position.getTop(),
162            color,
163            BORDER_OFFSET,
164            false);
165    }
166
167    /**
168     * Constructor.<p>
169     *
170     * @param position the position data
171     * @param color the border color
172     * @param borderOffset the border offset
173     */
174    public CmsHighlightingBorder(CmsPositionBean position, BorderColor color, int borderOffset) {
175
176        this(
177            position.getHeight(),
178            position.getWidth(),
179            position.getLeft(),
180            position.getTop(),
181            color,
182            borderOffset,
183            false);
184    }
185
186    /**
187     * Constructor.<p>
188     *
189     * @param positioningParent the element the border is positioned around, position is set relative to it
190     * @param color the border color
191     */
192    public CmsHighlightingBorder(Element positioningParent, BorderColor color) {
193
194        m_borderOffset = BORDER_OFFSET;
195        initWidget(uiBinder.createAndBindUi(this));
196        m_colorVariable = new CmsStyleVariable(getWidget());
197        m_colorVariable.setValue(color.getCssClass());
198        m_positioningParent = positioningParent;
199        resetPosition();
200    }
201
202    /**
203     * Constructor.<p>
204     *
205     * @param height the height
206     * @param width the width
207     * @param positionLeft the absolute left position
208     * @param positionTop the absolute top position
209     * @param color the border color
210     * @param borderOffset the border offset
211     */
212    public CmsHighlightingBorder(
213        int height,
214        int width,
215        int positionLeft,
216        int positionTop,
217        BorderColor color,
218        int borderOffset,
219        boolean correctTop) {
220
221        m_borderOffset = borderOffset;
222        initWidget(uiBinder.createAndBindUi(this));
223        m_colorVariable = new CmsStyleVariable(getWidget());
224        m_colorVariable.setValue(color.getCssClass());
225        m_correctTop = correctTop;
226        setPosition(height, width, positionLeft, positionTop);
227    }
228
229    /**
230     * Enables the border animation.<p>
231     * (Is enabled by default)<p>
232     *
233     * @param animated <code>true</code> to enable border animation
234     */
235    public void enableAnimation(boolean animated) {
236
237        if (animated) {
238            addStyleName(I_CmsLayoutBundle.INSTANCE.highlightCss().animated());
239        } else {
240            removeStyleName(I_CmsLayoutBundle.INSTANCE.highlightCss().animated());
241        }
242    }
243
244    /**
245     * Gets the vertical offsets (relative to the viewport) of the horizontal lines (top, midpoints, bottom, in this order).
246     *
247     * @return the vertical offsets of the horizonral lines
248     */
249    public List<Integer> getClientVerticalOffsets() {
250
251        List<Integer> result = new ArrayList<>();
252        List<DivElement> elements = new ArrayList<>();
253        elements.add(m_borderTop);
254        for (int i = 0; i < m_midpoints.getChildCount(); i++) {
255            elements.add((DivElement)m_midpoints.getChild(i));
256        }
257        elements.add(m_borderBottom);
258        for (DivElement elem : elements) {
259            elemental2.dom.Element elem0 = Js.cast(elem);
260            int top = (int)Math.round(elem0.getBoundingClientRect().top);
261            result.add(Integer.valueOf(top));
262        }
263        return result;
264    }
265
266    /**
267     * Hides the border.<p>
268     */
269    public void hide() {
270
271        setVisible(false);
272    }
273
274    /**
275     * Recalculates the position and dimension when a positioning parent is given.<p>
276     */
277    public void resetPosition() {
278
279        // fail if no positioning parent given
280        assert m_positioningParent != null;
281        if (m_positioningParent != null) {
282            setPosition(m_positioningParent.getOffsetHeight(), m_positioningParent.getOffsetWidth(), 0, 0);
283        }
284    }
285
286    /**
287     * Sets the color of the border.<p>
288     *
289     * @param color the color of the border
290     */
291    public void setColor(BorderColor color) {
292
293        m_colorVariable.setValue(color.getCssClass());
294    }
295
296    /**
297     * Sets the midpoint separators, given a list of their vertical offsets from the top.
298     *
299     * @param verticalOffsets the list of midpoint offsets
300     */
301    public void setMidpoints(List<Integer> verticalOffsets) {
302
303        m_midpoints.removeAllChildren();
304        if (verticalOffsets == null) {
305            verticalOffsets = Collections.emptyList();
306        }
307        for (Integer midpoint : verticalOffsets) {
308            DivElement midpointLine = Document.get().createDivElement();
309            midpointLine.addClassName(I_CmsLayoutBundle.INSTANCE.highlightCss().midpointSeparator());
310            midpointLine.getStyle().setTop(midpoint.doubleValue() + BORDER_OFFSET, Unit.PX);
311            midpointLine.getStyle().setLeft(SEPARATOR_OFFSET, Unit.PX);
312            m_midpoints.appendChild(midpointLine);
313
314        }
315    }
316
317    /**
318     * Sets the border position.<p>
319     *
320     * @param position the position data
321     */
322    public void setPosition(CmsPositionBean position) {
323
324        setPosition(position.getHeight(), position.getWidth(), position.getLeft(), position.getTop());
325    }
326
327    /**
328     * Sets the border position.<p>
329     *
330     * @param height the height
331     * @param width the width
332     * @param positionLeft the absolute left position
333     * @param positionTop the absolute top position
334     */
335    public void setPosition(int height, int width, int positionLeft, int positionTop) {
336
337        positionLeft -= m_borderOffset;
338
339        // make sure highlighting does not introduce additional horizontal scroll-bars
340        if ((m_positioningParent == null) && (positionLeft < 0)) {
341            // position left should not be negative
342            width += positionLeft;
343            positionLeft = 0;
344        }
345        width += (2 * m_borderOffset) - BORDER_WIDTH;
346        if ((m_positioningParent == null)
347            && (Window.getClientWidth() < (width + positionLeft))
348            && (Window.getScrollLeft() == 0)) {
349            // highlighting should not extend over the right hand
350            width = Window.getClientWidth() - (positionLeft + BORDER_WIDTH);
351        }
352        Style style = getElement().getStyle();
353        style.setLeft(positionLeft, Unit.PX);
354        int correction = m_correctTop
355        ? (int)(DomGlobal.document.body.getBoundingClientRect().top
356            - DomGlobal.document.documentElement.getBoundingClientRect().top)
357        : 0;
358        style.setTop(positionTop - m_borderOffset - correction, Unit.PX);
359
360        setHeight((height + (2 * m_borderOffset)) - BORDER_WIDTH);
361        setWidth(width);
362    }
363
364    /**
365     * Shows the border.<p>
366     */
367    public void show() {
368
369        setVisible(true);
370    }
371
372    /**
373     * Sets the highlighting height.<p>
374     *
375     * @param height the height
376     */
377    private void setHeight(int height) {
378
379        m_borderRight.getStyle().setHeight(height, Unit.PX);
380        m_borderLeft.getStyle().setHeight(height, Unit.PX);
381        m_borderBottom.getStyle().setTop(height, Unit.PX);
382    }
383
384    /**
385     * Sets the highlighting width.<p>
386     *
387     * @param width the width
388     */
389    private void setWidth(int width) {
390
391        m_borderTop.getStyle().setWidth(width + BORDER_WIDTH, Unit.PX);
392        m_borderBottom.getStyle().setWidth(width + BORDER_WIDTH, Unit.PX);
393        m_borderRight.getStyle().setLeft(width, Unit.PX);
394        NodeList<Node> midpoints = m_midpoints.getChildNodes();
395        for (int i = 0; i < midpoints.getLength(); i++) {
396            DivElement midpoint = (DivElement)midpoints.getItem(i);
397            midpoint.getStyle().setWidth((width + BORDER_WIDTH) - (2 * SEPARATOR_OFFSET), Unit.PX);
398        }
399    }
400
401}