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.ade.editprovider.client;
029
030import org.opencms.gwt.client.util.CmsPositionBean;
031
032import java.util.ArrayList;
033import java.util.Collections;
034import java.util.Comparator;
035import java.util.HashMap;
036import java.util.List;
037import java.util.Map;
038
039/**
040 * This class is used to calculate positions for a set of direct edit buttons so that
041 * they don't overlap.<p>
042 *
043 * @since 8.0.0
044 */
045public class CmsEditablePositionCalculator {
046
047    /**
048     * A comparator class which compares position beans by their left edge.<p>
049     */
050    protected class LeftComparator implements Comparator<CmsPositionBean> {
051
052        /**
053         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
054         */
055        public int compare(CmsPositionBean o1, CmsPositionBean o2) {
056
057            int l1 = o1.getLeft();
058            int l2 = o2.getLeft();
059            if (l1 < l2) {
060                return -1;
061            }
062            if (l1 > l2) {
063                return +1;
064            }
065            return 0;
066        }
067    }
068
069    /** A map of positions by element id. */
070    private Map<String, CmsPositionBean> m_positionMap = new HashMap<String, CmsPositionBean>();
071
072    /** The internal list of positions. */
073    private List<CmsPositionBean> m_positions = new ArrayList<CmsPositionBean>();
074
075    /** The assumed width of a direct edit button bar. */
076    private static int WIDTH = 65;
077
078    /** The assumed height of a direct edit button bar. */
079    private static int HEIGHT = 24;
080
081    /**
082     * Creates a new instance.<p>
083     *
084     * @param positions the map of original positions by element id (will not be altered)
085     */
086    public CmsEditablePositionCalculator(Map<String, CmsPositionBean> positions) {
087
088        for (Map.Entry<String, CmsPositionBean> entry : positions.entrySet()) {
089            CmsPositionBean newPos = new CmsPositionBean(entry.getValue());
090            m_positionMap.put(entry.getKey(), newPos);
091            m_positions.add(newPos);
092        }
093    }
094
095    /**
096     * Calculates non-overlapping positions for the button bars and returns them in a map with
097     * the element ids as keys.<p>
098     *
099     * @return the map of non-overlapping positions
100     */
101    public Map<String, CmsPositionBean> calculatePositions() {
102
103        int maxCollisions = 500;
104        // if there are more than 500 collisions, the style is probably messed up; give up.
105        while (checkCollision() && (maxCollisions > 0)) {
106            maxCollisions -= 1;
107        }
108        return m_positionMap;
109    }
110
111    /**
112     * Checks whether a collision occurs and handle it if necessary.<p>
113     *
114     * @return true if a collision occured
115     */
116    protected boolean checkCollision() {
117
118        // sort the positions by their left x coordinate, so we can easily exclude
119        // pairs of positions which don't overlap horizontally
120        sortByLeft();
121        int i;
122        for (i = 0; i < m_positions.size(); i++) {
123            for (int j = i + 1; (j < m_positions.size())
124                && intersectsHorizontally(m_positions.get(i), m_positions.get(j)); j++) {
125                if (intersectsVertically(m_positions.get(i), m_positions.get(j))) {
126                    handleCollision(m_positions.get(i), m_positions.get(j));
127                    return true;
128                }
129            }
130        }
131        return false;
132    }
133
134    /**
135     * Handles a collision by moving the lower position down.<p>
136     *
137     * @param p1 the first position
138     * @param p2 the second position
139     */
140    protected void handleCollision(CmsPositionBean p1, CmsPositionBean p2) {
141
142        CmsPositionBean positionToChange = p1;
143        if (p1.getTop() <= p2.getTop()) {
144            positionToChange = p2;
145        }
146        positionToChange.setTop(positionToChange.getTop() + 25);
147    }
148
149    /**
150     * Checks for intersection of two one-dimensional intervals.<p>
151     *
152     * @param a1 the left edge of the first interval
153     * @param a2 the right edge of the first interval
154     * @param b1 the left edge of the second interval
155     * @param b2 the right edge of the second interval
156     *
157     * @return true if the intervals intersect
158     */
159    protected boolean intersectIntervals(int a1, int a2, int b1, int b2) {
160
161        return !((a2 < b1) || (a1 > b2));
162    }
163
164    /**
165     * Checks whether two positions intersect horizontally.<p>
166     *
167     * @param p1 the first position
168     * @param p2 the second position
169     *
170     * @return true if the positions intersect horizontally
171     */
172    protected boolean intersectsHorizontally(CmsPositionBean p1, CmsPositionBean p2) {
173
174        return intersectIntervals(p1.getLeft(), p1.getLeft() + WIDTH, p2.getLeft(), p2.getLeft() + WIDTH);
175    }
176
177    /**
178     * Checks whether two positions intersect vertically.<p>
179     *
180     * @param p1 the first position
181     * @param p2 the second position
182     *
183     * @return if the positions intersect vertically
184     */
185    protected boolean intersectsVertically(CmsPositionBean p1, CmsPositionBean p2) {
186
187        return intersectIntervals(p1.getTop(), p1.getTop() + HEIGHT, p2.getTop(), p2.getTop() + HEIGHT);
188    }
189
190    /**
191     * Sorts the internal list of positions by their left edge.<p>
192     */
193    protected void sortByLeft() {
194
195        Collections.sort(m_positions, new LeftComparator());
196    }
197
198}