/* * Copyright 2008 Jeff Dwyer * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.apress.progwt.client.college.gui; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import com.allen_sauer.gwt.log.client.Log; import com.apress.progwt.client.college.gui.ext.ClientMouseImpl; import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.AbsolutePanel; import com.google.gwt.user.client.ui.ChangeListener; import com.google.gwt.user.client.ui.ChangeListenerCollection; import com.google.gwt.user.client.ui.MouseListener; import com.google.gwt.user.client.ui.MouseWheelListener; import com.google.gwt.user.client.ui.MouseWheelVelocity; import com.google.gwt.user.client.ui.SourcesChangeEvents; import com.google.gwt.user.client.ui.SourcesMouseEvents; import com.google.gwt.user.client.ui.SourcesMouseWheelEvents; import com.google.gwt.user.client.ui.Widget; public abstract class ViewPanel extends AbsolutePanel implements MouseListener, SourcesChangeEvents, MouseWheelListener { private class RedrawParams { public int centerX; public int centerY; public int halfHeight; public int halfWidth; public double yScale; } protected int backX = 0; protected int backY = 0; private ChangeListenerCollection changeCollection = new ChangeListenerCollection(); private int curbackX = 0; private int curbackY = 0; protected double currentScale = 1; private boolean doYTranslate = true; private boolean doZoom; protected boolean dragEnabled = true; private boolean dragging; private int dragStartX; private int dragStartY; private EventBackdrop focusBackdrop; protected List objects = new ArrayList(); private ClientMouseImpl clientMouseImpl; public ViewPanel() { super(); clientMouseImpl = (ClientMouseImpl) GWT .create(ClientMouseImpl.class); focusBackdrop = new EventBackdrop(); makeThisADragHandle(focusBackdrop); add(focusBackdrop, 0, 0); } public void addChangeListener(ChangeListener listener) { changeCollection.add(listener); } public void addObject(RemembersPosition rp) { add(rp.getWidget(), (int) rp.getLeft(), rp.getTop()); objects.add(rp); } protected void centerOn(int x, int y) { // Log.debug("centering on "+x+" "+ y); int width = getWidth(); int height = getHeight(); int halfWidth = width / 2; int halfHeight = height / 2; int left = (int) (x * currentScale * getXSpread()) - halfWidth; int top = (int) (y * currentScale) - halfHeight; // Log.debug("P.X "+P.X+" HW "+HALFWIDTH+" "+LEFT); // Log.debug("left "+left+" top "+top); // intuitively this is (left - curbackX) but things are reversed // int dx = left + curbackX; // int dy = top + curbackY; int dx = left + curbackX; int dy = top + curbackY; moveBy(dx, dy); } /** * */ protected void centerOnMouse() { int halfWidth = getWidth() / 2; int halfHeight = getHeight() / 2; // lastx, lasty are just a backup in case this doesn't work. // They're not a great backup, // because if child objects have obscured us we won't be getting // mouseMove events and we'll // be weird relative x & y's from senders anyway. // BUT it turns out that FF clientX & Y are rogered for // ScrollWheel events, -> replace-with // MozillaImpl Event curEvent = DOM.eventGetCurrentEvent(); int lastx = clientMouseImpl.getClientX(curEvent); int lasty = clientMouseImpl.getClientY(curEvent); int dx = lastx - getAbsoluteLeft() - halfWidth; int dy = lasty - getAbsoluteTop() - halfHeight; Log.debug("ViewPanel.centerOnMouse last x " + lastx + " absLeft " + getAbsoluteLeft() + " curbackx " + curbackX + " dx " + dx); Log.debug("ViewPanel.centerOnMouse last y " + lasty + " absTop " + getAbsoluteTop() + " curbacky " + curbackY + " dy " + dy); moveBy(dx, dy); } /** * don't let a regular clear() happen or you'll lose the focusBackdrop */ public void clear() { // Log.debug("calling our clear()"); for (Iterator iter = objects.iterator(); iter.hasNext();) { Widget w = (Widget) iter.next(); remove(w); iter.remove(); } } private void endDrag() { if (dragging) { // Log.debug("(old)back x "+backX+" cur(new) // "+curbackX); // Log.debug("(old)back y "+backY+" cur(new) // "+curbackY); backX = curbackX; backY = curbackY; } dragging = false; } /** * Make sure that we're zoomed to 'scale' or higher * * return the value that we settle on */ public double ensureZoomOfAtLeast(double scale) { if (scale > currentScale) { zoomTo(scale); } return currentScale; } protected void finishZoom(double oldScale) { setBackground(currentScale); int width = getWidth(); int height = getHeight(); int centerX = getCenterX(oldScale, width); int centerY = getCenterY(oldScale, height); // moveTo(centerX, centerY); int halfWidth = width / 2; int halfHeight = height / 2; reCenter(centerX, centerY, currentScale, halfWidth, halfHeight); redraw(); postZoomCallback(currentScale); redraw(); } public int getBackX() { return backX; } public int getBackY() { return backY; } protected int getCenterX() { return getCenterX(currentScale, getWidth()); } protected int getCenterX(double scaleToUse, int width) { int halfWidth = width / 2; int centerX = (int) ((-curbackX + halfWidth) / (scaleToUse * getXSpread())); // Log.debug("get Center X "+scaleToUse+" "+(-curbackX + // halfWidth)+" "+centerX); return centerX; } protected int getCenterY() { return getCenterY(currentScale, getHeight()); } protected int getCenterY(double scaleToUse, int height) { int halfHeight = height / 2; int centerY = (int) ((-curbackY + halfHeight) / scaleToUse); return centerY; } public int getCurbackX() { return curbackX; } public int getCurbackY() { return curbackY; } public EventBackdrop getFocusBackdrop() { return focusBackdrop; } protected abstract int getHeight(); public int[] getLongLatForXY(int absLeft, int absTop) { int oceanLeft = getBackX(); int oceanTop = getBackY(); int newLeft = absLeft - oceanLeft; int newTop = absTop - oceanTop; int lng = (int) (newLeft / currentScale); int lat = (int) (newTop / currentScale); return new int[] { lng, lat }; } private RedrawParams getParams(int dy) { RedrawParams rd = new RedrawParams(); rd.yScale = 1; if (isDoYTranslate()) { curbackY = -dy + backY; rd.yScale = currentScale; } DOM.setStyleAttribute(getElement(), "backgroundPosition", curbackX + "px " + curbackY + "px"); int width = getWidth(); int height = getHeight(); rd.halfWidth = width / 2; rd.halfHeight = height / 2; rd.centerX = getCenterX(currentScale, width); rd.centerY = getCenterY(rd.yScale, height); return rd; } public int getPositionX(int left) { // Log.debug("getPositionX " + left + " " + currentScale // + " " + getXSpread() + " " // + curbackX); return (int) (left * currentScale * getXSpread()) + curbackX; } public int getPositionXFromGUIX(int guix) { Log.debug("getPositionXFromGuiX " + guix + " " + currentScale + " " + getXSpread() + " " + curbackX); return (int) ((guix - curbackX) / currentScale / (double) getXSpread()); } protected abstract int getWidth(); /** * Basically an extra zoom factor. Spread to the size of the * background. Overridden to 600 for ZoomableTimeline * * @return */ protected int getXSpread() { return 1; } public boolean isDoYTranslate() { return doYTranslate; } public boolean isDoZoom() { return doZoom; } protected void makeThisADragHandle(Widget widget) { if (doZoom) { if (widget instanceof SourcesMouseWheelEvents) { SourcesMouseWheelEvents wheeler = (SourcesMouseWheelEvents) widget; wheeler.addMouseWheelListener(this); } } if (widget instanceof SourcesMouseEvents) { SourcesMouseEvents mouser = (SourcesMouseEvents) widget; mouser.addMouseListener(this); } } /** * This moves the background and then sets the back position. Call * this when you want a move. * * @param dx * @param dy */ public void moveBy(int dx, int dy) { moveByDelta(dx, dy); // this was normally set in finishDrag() backX = curbackX; backY = curbackY; } /** * Internal move method. Doesn't actually 'finish' the move. This * helps us make dragging smoother. * * Use moveBy() unless you'll finish yourself. * * Takes dx, dy as SouthEast (+,+) NW (-,-) * * @param dx * @param dy */ protected void moveByDelta(int dx, int dy) { curbackX = -dx + backX; RedrawParams rd = getParams(dy); // Log.debug("ViewPanel.moveByDelta dx " + dx + " dy " + // dy); for (Iterator iter = objects.iterator(); iter.hasNext();) { Object o = iter.next(); RemembersPosition rp = (RemembersPosition) o; redrawObj(rp, rd); } moveOccurredCallback(); // Log.debug("moved "+curbackX+" "+curbackY); } protected void moveOccurredCallback() { } /** * Do an absolute move * * @param x * @param y */ public void moveTo(int x, int y) { int dx = backX - x; int dy = backY - y; moveByDelta(dx, dy); // this was normally set in finishDrag() backX = curbackX; backY = curbackY; } /** * Override this if you want object move event processing * * @param o * @param halfWidth * @param halfHeight * @param centerX * @param centerY */ protected void objectHasMoved(RemembersPosition o, int halfWidth, int halfHeight, int centerX, int centerY) { } public void onMouseDown(Widget sender, int x, int y) { // Log.debug("down "+(sender instanceof Island) +" x // "+x+" y "+y +" "+dragging); // Log.debug("mouse downd " + GWT.getTypeName(sender) + " // x " + x + " y " + y + " " // + sender.getAbsoluteLeft() + " " + sender.getAbsoluteTop()); // if (!dragEnabled || sender instanceof FocusPanel) { dragging = true; // } dragStartX = x + sender.getAbsoluteLeft(); dragStartY = y + sender.getAbsoluteTop(); // Log.debug("down " + GWT.getTypeName(sender) + "dsx " + // dragStartX + " dsy " // + dragStartY + " x " + x + " y " + y + " " + // sender.getAbsoluteLeft() + " " // + sender.getAbsoluteTop()); unselect(); } public void onMouseEnter(Widget sender) { } public void onMouseLeave(Widget sender) { endDrag(); } public void onMouseMove(Widget sender, int x, int y) { int lastx = x + sender.getAbsoluteLeft(); int lasty = y + sender.getAbsoluteTop(); clientMouseImpl.setLastXY(lastx, lasty); int dx = dragStartX - x - sender.getAbsoluteLeft(); int dy = dragStartY - y - sender.getAbsoluteTop(); // Log.debug("move "+(sender instanceof Island) +" x // "+x+" y "+y +" "+dragging); if (dragging) { moveByDelta(dx, dy); changeCollection.fireChange(this); } } public void onMouseUp(Widget sender, int x, int y) { // Log.debug("up " + GWT.getTypeName(sender) + "dsx " + // dragStartX + " dsy " // + dragStartY + " x " + x + " y " + y + " " + // sender.getAbsoluteLeft() + " " // + sender.getAbsoluteTop()); // Log.debug("up "+(sender instanceof Island) +" x "+x+" // y "+y +" "+dragging); endDrag(); } public void onMouseWheel(Widget sender, MouseWheelVelocity velocity) { if (velocity.isSouth()) { zoomOut(); } else { centerOnMouse(); zoomIn(); } } protected void postZoomCallback(double currentScale) { } private void reCenter(int centerX, int centerY, double scale, int halfWidth, int halfHeight) { // Log.debug("recenter\nback X "+backX+" backy "+backY); // Log.debug("center X "+centerX+" cy "+centerY+" scale // "+scale); // Log.debug("hw "+halfWidth+" hh "+halfHeight); // backX = halfWidth - halfWidth/currentScale; int newCenterX = (int) (centerX * scale * getXSpread()); int newCenterY = (int) (centerY * scale); // Log.debug("new center X "+newCenterX+" "+newCenterY); backX = -(newCenterX - halfWidth); backY = -(newCenterY - halfHeight); // Log.debug("Newback X "+backX+" NEWbacky "+backY); } /** * redraw all */ public void redraw() { // Log.debug("ViewPanel.redraw()"); moveBy(0, 0); } /** * redraw a single object * * @param rp */ public void redraw(RemembersPosition rp) { Log.debug("ViewPanel.redraw(RP) "); RedrawParams rd = getParams(0); redrawObj(rp, rd); moveOccurredCallback(); } private void redrawObj(RemembersPosition rp, RedrawParams params) { // Log.debug("found: "+GWT.getTypeName(rp)); // Log.debug("Left "+isle.getLeft()+" Top // "+isle.getTop()); // Log.debug("cur "+curbackX+" cury "+curbackY); // setWidgetPosition(rp.getWidget(),(int)((rp.getLeft()+curbackX)*currentScale), // (int)((rp.getTop()+curbackY)*currentScale)); // Log.debug("move "+rp.getLeft()+" // "+(int)((rp.getLeft())*currentScale)+" // "+(int)((rp.getLeft())*currentScale*getXSpread())+" cs // "+currentScale); try { setWidgetPosition(rp.getWidget(), getPositionX(rp.getLeft()), (int) ((rp.getTop()) * params.yScale) + curbackY); } catch (RuntimeException e) { Log.error("ERROR: ViewPanel. couldn't move: " + rp.getWidget()); throw e; } objectHasMoved(rp, params.halfWidth, params.halfHeight, params.centerX, params.centerY); } public void removeChangeListener(ChangeListener listener) { changeCollection.remove(listener); } public boolean removeObj(Widget w) { Log.debug("ViewPanel.remove " + GWT.getTypeName(w)); super.remove(w); return objects.remove(w); } protected abstract void setBackground(double scale); public void setDoYTranslate(boolean doYTranslate) { this.doYTranslate = doYTranslate; } public void setDoZoom(boolean doZoom) { this.doZoom = doZoom; makeThisADragHandle(focusBackdrop); } protected void unselect() { } public void zoomIn() { double oldScale = currentScale; currentScale *= 2; finishZoom(oldScale); } public void zoomOut() { double oldScale = currentScale; currentScale /= 2; finishZoom(oldScale); } public void zoomTo(double scale) { if (scale == currentScale) { return; } double oldScale = currentScale; currentScale = scale; finishZoom(oldScale); } }