001/*
002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     troger
016 *
017 * $Id$
018 */
019
020package org.nuxeo.ecm.platform.pictures.tiles.gwt.client.model;
021
022import java.util.ArrayList;
023import java.util.List;
024
025import com.allen_sauer.gwt.log.client.Log;
026
027/**
028 * @author <a href="mailto:troger@nuxeo.com">Thomas Roger</a>
029 */
030public class TilingModel {
031
032    public enum TilingModelEvent {
033        MOVE_EVENT, TILING_INFO_UPDATED
034    };
035
036    private TilingInfo tilingInfo;
037
038    private int totalWidth;
039
040    private int totalHeight;
041
042    private int viewAreaTop;
043
044    private int viewAreaLeft;
045
046    private int viewAreaWidth;
047
048    private int viewAreaHeight;
049
050    private int maxLeft;
051
052    private int maxTop;
053
054    // we won't zoom out below this zoom
055    private double defaultZoom;
056
057    // used to recenter the area after zooming in / out
058    private int oldXToCenterOn = 0;
059
060    private int oldYToCenterOn = 0;
061
062    private double oldZoom = 1;
063
064    private List<TilingModelListener> listeners = new ArrayList<TilingModelListener>();
065
066    private TilingInfoCallback tilingInfoCallback = new TilingInfoCallback() {
067        public void tilingInfoUpdated() {
068            updateModel();
069
070            // compute new center
071            int x = (int) Math.round(oldXToCenterOn * getCurrentZoom() / oldZoom);
072            int y = (int) Math.round(oldYToCenterOn * getCurrentZoom() / oldZoom);
073            // center
074            Log.debug("center on: x=" + x + ", y=" + y);
075            centerOn(x, y, false);
076
077            // fire the event
078            fireEvent(TilingModelEvent.TILING_INFO_UPDATED);
079        }
080    };
081
082    public TilingModel(TilingInfo ti, int viewAreaWidth, int viewAreaHeight, double defaultZoom) {
083        tilingInfo = ti;
084        this.defaultZoom = defaultZoom;
085        this.viewAreaWidth = viewAreaWidth;
086        this.viewAreaHeight = viewAreaHeight;
087        viewAreaTop = viewAreaLeft = 0;
088        updateModel();
089    }
090
091    public void notifyListeners() {
092        fireEvent(TilingModelEvent.TILING_INFO_UPDATED);
093    }
094
095    private void updateModel() {
096        totalWidth = (int) Math.round(tilingInfo.getOriginalImageWidth() * getCurrentZoom());
097        totalHeight = (int) Math.round(tilingInfo.getOriginalImageHeight() * getCurrentZoom());
098
099        maxLeft = totalWidth - viewAreaWidth;
100        maxTop = totalHeight - viewAreaHeight;
101    }
102
103    public void move(int x, int y, boolean fireEvent) {
104        viewAreaLeft += x;
105        viewAreaTop += y;
106        ensureCorrectValues();
107        if (fireEvent) {
108            fireEvent(TilingModelEvent.MOVE_EVENT);
109        }
110    }
111
112    public void move(int x, int y) {
113        move(x, y, true);
114    }
115
116    public void resetView() {
117        viewAreaLeft = viewAreaTop = 0;
118    }
119
120    public void setLocation(int left, int top, boolean fireEvent) {
121        viewAreaLeft = left;
122        viewAreaTop = top;
123        ensureCorrectValues();
124        if (fireEvent) {
125            fireEvent(TilingModelEvent.MOVE_EVENT);
126        }
127    }
128
129    public void setLocation(int left, int top) {
130        setLocation(left, top, true);
131    }
132
133    public void centerOn(int x, int y, boolean fireEvent) {
134        viewAreaLeft = x - viewAreaWidth / 2;
135        viewAreaTop = y - viewAreaHeight / 2;
136        Log.debug("viewAreaLeft= " + viewAreaLeft + " viewAreaTop= " + viewAreaTop);
137        ensureCorrectValues();
138        if (fireEvent) {
139            fireEvent(TilingModelEvent.MOVE_EVENT);
140        }
141    }
142
143    public void centerOn(int x, int y) {
144        centerOn(x, y, true);
145    }
146
147    private void ensureCorrectValues() {
148        if (viewAreaLeft > maxLeft) {
149            viewAreaLeft = maxLeft;
150        }
151        if (viewAreaTop > maxTop) {
152            viewAreaTop = maxTop;
153        }
154        if (viewAreaLeft < 0) {
155            viewAreaLeft = 0;
156        }
157        if (viewAreaTop < 0) {
158            viewAreaTop = 0;
159        }
160    }
161
162    public void zoomIn() {
163        tilingInfo.setMaxTiles(tilingInfo.getMaxTiles() * 2);
164        saveOldCoord();
165        tilingInfo.updateTilingInfo(tilingInfoCallback);
166    }
167
168    public void zoomOut() {
169        if (getCurrentZoom() <= defaultZoom) {
170            return;
171        }
172        tilingInfo.setMaxTiles(tilingInfo.getMaxTiles() / 2);
173        saveOldCoord();
174        tilingInfo.updateTilingInfo(tilingInfoCallback);
175    }
176
177    private void saveOldCoord() {
178        oldZoom = getCurrentZoom();
179        oldXToCenterOn = viewAreaLeft + viewAreaWidth / 2;
180        oldYToCenterOn = viewAreaTop + viewAreaHeight / 2;
181    }
182
183    public void addListener(TilingModelListener listener) {
184        listeners.add(listener);
185    }
186
187    public void removeListener(TilingModelListener listener) {
188        listeners.remove(listener);
189    }
190
191    private void fireEvent(TilingModelEvent event) {
192        for (TilingModelListener l : listeners) {
193            l.onModelChange(event, this);
194        }
195    }
196
197    /**
198     * @return the totalWidth.
199     */
200    public int getTotalWidth() {
201        return totalWidth;
202    }
203
204    /**
205     * @return the totalHeight.
206     */
207    public int getTotalHeight() {
208        return totalHeight;
209    }
210
211    /**
212     * @return the viewAreaTop.
213     */
214    public int getViewAreaTop() {
215        return viewAreaTop;
216    }
217
218    /**
219     * @return the viewAreaLeft.
220     */
221    public int getViewAreaLeft() {
222        return viewAreaLeft;
223    }
224
225    /**
226     * @return the viewAreaWidth.
227     */
228    public int getViewAreaWidth() {
229        return viewAreaWidth;
230    }
231
232    /**
233     * @return the viewAreaHeight.
234     */
235    public int getViewAreaHeight() {
236        return viewAreaHeight;
237    }
238
239    public int getTileWidth() {
240        return tilingInfo.getTileWidth();
241    }
242
243    public int getTileHeight() {
244        return tilingInfo.getTileHeight();
245    }
246
247    public int getWidthInTiles() {
248        return tilingInfo.getNbXTiles();
249    }
250
251    public int getHeightInTiles() {
252        return tilingInfo.getNbYTiles();
253    }
254
255    public String getBaseUrl() {
256        return tilingInfo.getBaseUrl();
257    }
258
259    public double getCurrentZoom() {
260        return tilingInfo.getZoom();
261    }
262
263    public long getLastModificationDate() {
264        return tilingInfo.getLastModificationDate();
265    }
266
267}