/*
* Copyright (c) 2010 The Jackson Laboratory
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jax.gwtutil.client;
import com.google.gwt.event.dom.client.ErrorEvent;
import com.google.gwt.event.dom.client.ErrorHandler;
import com.google.gwt.event.dom.client.LoadEvent;
import com.google.gwt.event.dom.client.LoadHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Widget;
/**
* Base class for zoom-scroll bars
* @author <A HREF="mailto:keith.sheppard@jax.org">Keith Sheppard</A>
*/
public abstract class ZoomScrollBar extends Composite
{
private static final String BASE_STYLE_NAME = "jax-ZoomScrollBar";
private static final String SLIDER_RIGHT_STYLE_NAME =
BASE_STYLE_NAME + "-SliderRight";
private static final String SLIDER_LEFT_STYLE_NAME =
BASE_STYLE_NAME + "-SliderLeft";
private static final String SLIDER_MIDDLE_STYLE_NAME =
BASE_STYLE_NAME + "-SliderCenter";
private static final String BACK_PANEL_STYLE_NAME =
BASE_STYLE_NAME + "-BackPanel";
private static final String LEFT_SLIDER_IMAGE_URL = "images/left-zoom-handle.png";
private static final String RIGHT_SLIDER_IMAGE_URL = "images/right-zoom-handle.png";
private int widthPixles = 0;
private final AbsolutePanel absolutePositionsPanel;
private final FocusPanel mainFocusPanel;
private final Image leftSliderImage;
private final Image rightSliderImage;
private final Widget centerSliderWidget;
private int leftSliderWidth = 0;
private int rightSliderWidth = 0;
private int maximumImageHeight = 0;
private int backgroundImageWidth = 0;
private Image backgroundImage;
private HandlerRegistration backgroundLoadReg;
private HandlerRegistration backgroundErrorReg;
private final LoadHandler resizeOnLoadListener = new LoadHandler()
{
/**
* {@inheritDoc}
*/
public void onLoad(LoadEvent event)
{
ZoomScrollBar.this.resizeScrollBar();
}
};
private final ErrorHandler errorHandler = new ErrorHandler()
{
public void onError(ErrorEvent event)
{
System.err.println("Failed to load: " + event.getSource());
}
};
private HandlerRegistration previewRegistration;
private static NativePreviewHandler preventDefaultMouseEvents = new NativePreviewHandler()
{
/**
* {@inheritDoc}
*/
public void onPreviewNativeEvent(NativePreviewEvent event)
{
switch(event.getTypeInt())
{
case Event.ONMOUSEDOWN:
case Event.ONMOUSEMOVE:
event.getNativeEvent().preventDefault();
}
}
};
/**
* Construct a new zoom/scroll bar.
*/
public ZoomScrollBar()
{
this.mainFocusPanel = new FocusPanel();
this.initWidget(this.mainFocusPanel);
this.absolutePositionsPanel = new AbsolutePanel();
this.mainFocusPanel.setWidget(this.absolutePositionsPanel);
this.absolutePositionsPanel.setStyleName(BACK_PANEL_STYLE_NAME);
this.leftSliderImage = new Image(LEFT_SLIDER_IMAGE_URL);
this.leftSliderImage.addLoadHandler(this.resizeOnLoadListener);
this.leftSliderImage.addErrorHandler(this.errorHandler);
this.leftSliderImage.setStyleName(SLIDER_LEFT_STYLE_NAME);
this.absolutePositionsPanel.add(this.leftSliderImage);
this.rightSliderImage = new Image(RIGHT_SLIDER_IMAGE_URL);
this.rightSliderImage.addLoadHandler(this.resizeOnLoadListener);
this.rightSliderImage.addErrorHandler(this.errorHandler);
this.rightSliderImage.setStyleName(SLIDER_RIGHT_STYLE_NAME);
this.absolutePositionsPanel.add(this.rightSliderImage);
this.centerSliderWidget = new AbsolutePanel();
this.centerSliderWidget.setStyleName(SLIDER_MIDDLE_STYLE_NAME);
this.absolutePositionsPanel.add(this.centerSliderWidget);
ScrollZoomMouseListener scrollZoomListener =
new ScrollZoomMouseListener();
this.mainFocusPanel.addMouseUpHandler(scrollZoomListener);
this.mainFocusPanel.addMouseDownHandler(scrollZoomListener);
this.mainFocusPanel.addMouseOverHandler(scrollZoomListener);
this.mainFocusPanel.addMouseOutHandler(scrollZoomListener);
this.mainFocusPanel.addMouseMoveHandler(scrollZoomListener);
}
/**
* Resize the scroll bar to fit its contents
*/
private void resizeScrollBar()
{
this.leftSliderWidth = this.leftSliderImage.getOffsetWidth();
this.rightSliderWidth = this.rightSliderImage.getOffsetWidth();
this.backgroundImageWidth = this.backgroundImage.getOffsetWidth();
this.maximumImageHeight = Math.max(
this.leftSliderImage.getOffsetHeight(),
this.rightSliderImage.getOffsetHeight());
this.maximumImageHeight = Math.max(
this.maximumImageHeight,
this.backgroundImage.getOffsetHeight());
this.setPixelSize(
this.leftSliderWidth + this.rightSliderWidth + this.backgroundImageWidth,
this.maximumImageHeight);
}
/**
* Returns the image width that shoudl be used for
* {@link #setBackgroundImage(Image)} to achieve the desired
* total zoom/scroll bar with. This accounts for the handle widgets that
* are at either side of this widget
* @param desiredScrollBarWidth
* the scroll bar width that we want to achieve
* @return
* the image width that should be used
*/
public int getImageWidthForScrollBarWidth(int desiredScrollBarWidth)
{
return (desiredScrollBarWidth - this.leftSliderWidth) - this.rightSliderWidth;
}
/**
* Getter for the background image
* @return the background image
*/
public Image getBackgroundImage()
{
return this.backgroundImage;
}
/**
* Setter for the background image
* @param backgroundImage
* the background image to set
*/
public synchronized void setBackgroundImage(Image backgroundImage)
{
if(this.backgroundImage != null)
{
this.backgroundLoadReg.removeHandler();
this.backgroundErrorReg.removeHandler();
this.absolutePositionsPanel.remove(this.backgroundImage);
}
this.backgroundImage = backgroundImage;
if(this.backgroundImage != null)
{
this.backgroundLoadReg = this.backgroundImage.addLoadHandler(
this.resizeOnLoadListener);
this.backgroundErrorReg = this.backgroundImage.addErrorHandler(
this.errorHandler);
this.absolutePositionsPanel.add(this.backgroundImage);
}
this.resizeScrollBar();
}
/**
* {@inheritDoc}
*/
@Override
public void setPixelSize(int width, int height)
{
super.setPixelSize(width, height);
this.absolutePositionsPanel.setPixelSize(width, height);
this.widthPixles = width;
if(this.backgroundImage != null)
{
this.absolutePositionsPanel.setWidgetPosition(
this.backgroundImage,
this.leftSliderWidth,
0);
// this.absolutePositionsPanel.getWidgetTop(this.backgroundImage));
}
this.refreshScrollBar(true);
}
/**
* Get the full range start in pixel units
* @return
* the full range start in pixels
*/
protected int getFullRangeStartInPixels()
{
return this.leftSliderImage.getOffsetWidth();
}
/**
* Get the full range extent in pixels
* @return
* the full range extent in pixels
*/
protected int getFullRangeExtentInPixels()
{
return (this.widthPixles - this.leftSliderImage.getOffsetWidth()) -
this.rightSliderImage.getOffsetWidth();
}
/**
* Get's the left boundary pixel position of the scroll bar
* @return
* the left boundary
*/
protected abstract int getSelectedRangeStartInPixels();
/**
* Gets the width of the scroll bar in pixels.
* @return
* the width
*/
protected abstract int getSelectedRangeExtentInPixels();
/**
* Get the end in pixels.
* overlap
* @return
* the selected range end in pixels
*/
private int getSelectedRangeEndInPixels()
{
return this.getSelectedRangeStartInPixels() +
this.getSelectedRangeExtentInPixels();
}
/**
* Update internal information based on a scroll bar drag
* @param newSelectedRangeStartInPixels
* the new left boundary in pixels
* @param newSelectedRangeExtentInPixels
* the new width in pixels
* @param zoomScrollComplete
* if zoom scroll was complete
*/
protected abstract void updateSelectedRangeInPixels(
int newSelectedRangeStartInPixels,
int newSelectedRangeExtentInPixels,
boolean zoomScrollComplete);
/**
* Refresh the scroll bar position
* @param zoomScrollComplete
* if zoom scroll was complete
*/
protected void refreshScrollBar(boolean zoomScrollComplete)
{
this.absolutePositionsPanel.setWidgetPosition(
this.leftSliderImage,
this.getSelectedRangeStartInPixels() - this.leftSliderWidth,
(int)(this.maximumImageHeight / 2.0 - this.leftSliderImage.getOffsetHeight() / 2.0));
this.absolutePositionsPanel.setWidgetPosition(
this.rightSliderImage,
this.getSelectedRangeEndInPixels(),
(int)(this.maximumImageHeight / 2.0 - this.rightSliderImage.getOffsetHeight() / 2.0));
this.centerSliderWidget.setPixelSize(
this.getSelectedRangeExtentInPixels(),
this.maximumImageHeight);
this.absolutePositionsPanel.setWidgetPosition(
this.centerSliderWidget,
this.getSelectedRangeStartInPixels(),
0);
}
private enum DragType
{
/**
* enum for left zoom handle
*/
LEFT_ZOOM_HANDLE,
/**
* enum for right zoom handle
*/
RIGHT_ZOOM_HANDLE,
/**
* enum for slider handle
*/
SLIDER_HANDLE,
/**
* enum for no dragging
*/
NO_DRAG
}
private class ScrollZoomMouseListener
implements MouseUpHandler, MouseDownHandler, MouseOverHandler, MouseOutHandler, MouseMoveHandler
{
/**
* The starting drag position
*/
private int dragStartX;
private DragType dragType = DragType.NO_DRAG;
private int dragStartSelectedRangeExtent;
private int dragStartSelectedRangeStart;
private int dragStartSelectedRangeEnd;
private int dragStartFullRangeExtent;
private int dragStartFullRangeStart;
private int dragStartFullRangeEnd;
/**
* {@inheritDoc}
*/
public void onMouseUp(MouseUpEvent event)
{
if(this.dragType != DragType.NO_DRAG)
{
this.dragType = DragType.NO_DRAG;
Widget widget = (Widget)event.getSource();
DOM.releaseCapture(widget.getElement());
ZoomScrollBar.this.zoomScrollComplete();
}
}
/**
* {@inheritDoc}
*/
public void onMouseDown(MouseDownEvent event)
{
Widget widget = (Widget)event.getSource();
this.dragStartX = event.getX();
this.dragStartSelectedRangeExtent =
ZoomScrollBar.this.getSelectedRangeExtentInPixels();
this.dragStartSelectedRangeStart =
ZoomScrollBar.this.getSelectedRangeStartInPixels();
this.dragStartSelectedRangeEnd =
this.dragStartSelectedRangeStart + this.dragStartSelectedRangeExtent;
this.dragStartFullRangeExtent =
ZoomScrollBar.this.getFullRangeExtentInPixels();
this.dragStartFullRangeStart =
ZoomScrollBar.this.getFullRangeStartInPixels();
this.dragStartFullRangeEnd =
this.dragStartFullRangeStart + this.dragStartFullRangeExtent;
int dragStartingLeftZoomX = ZoomScrollBar.this.getSelectedRangeStartInPixels();
int dragStartingRightZoomX =
dragStartingLeftZoomX +
ZoomScrollBar.this.getSelectedRangeExtentInPixels();
if(dragStartingLeftZoomX - this.dragStartX <= ZoomScrollBar.this.leftSliderWidth &&
dragStartingLeftZoomX - this.dragStartX >= 0)
{
this.dragType = DragType.LEFT_ZOOM_HANDLE;
DOM.setCapture(widget.getElement());
}
else if(this.dragStartX - dragStartingRightZoomX <= ZoomScrollBar.this.rightSliderWidth &&
this.dragStartX - dragStartingRightZoomX >= 0)
{
this.dragType = DragType.RIGHT_ZOOM_HANDLE;
DOM.setCapture(widget.getElement());
}
else if(this.dragStartX >= dragStartingLeftZoomX &&
this.dragStartX <= dragStartingRightZoomX)
{
this.dragType = DragType.SLIDER_HANDLE;
DOM.setCapture(widget.getElement());
}
else
{
this.dragType = DragType.NO_DRAG;
}
}
/**
* {@inheritDoc}
*/
public void onMouseOver(MouseOverEvent event)
{
ZoomScrollBar.this.previewRegistration =
Event.addNativePreviewHandler(preventDefaultMouseEvents);
}
/**
* {@inheritDoc}
*/
public void onMouseOut(MouseOutEvent event)
{
if(ZoomScrollBar.this.previewRegistration != null)
{
ZoomScrollBar.this.previewRegistration.removeHandler();
}
}
/**
* {@inheritDoc}
*/
public void onMouseMove(MouseMoveEvent event)
{
int deltaX = event.getX() - this.dragStartX;
switch(this.dragType)
{
case LEFT_ZOOM_HANDLE:
{
this.dragLeftZoomHandle(deltaX);
}
break;
case RIGHT_ZOOM_HANDLE:
{
this.dragRightZoomHandle(deltaX);
}
break;
case SLIDER_HANDLE:
{
this.dragSliderHandle(deltaX);
}
break;
case NO_DRAG:
{
// no-op
}
break;
default:
{
System.err.println("unknown drag type: " + this.dragType);
}
break;
}
}
/**
* @param deltaX
*/
private void dragSliderHandle(int deltaX)
{
int newSelectedRangeStart =
this.dragStartSelectedRangeStart + deltaX;
int newSelectedRangeEnd =
this.dragStartSelectedRangeEnd + deltaX;
// bound the drag
if(newSelectedRangeStart < this.dragStartFullRangeStart)
{
ZoomScrollBar.this.updateSelectedRangeInPixels(
this.dragStartFullRangeStart,
this.dragStartSelectedRangeExtent,
false);
}
else if(newSelectedRangeEnd > this.dragStartFullRangeEnd)
{
ZoomScrollBar.this.updateSelectedRangeInPixels(
this.dragStartFullRangeEnd - this.dragStartSelectedRangeExtent,
this.dragStartSelectedRangeExtent,
false);
}
else
{
ZoomScrollBar.this.updateSelectedRangeInPixels(
newSelectedRangeStart,
this.dragStartSelectedRangeExtent,
false);
}
}
private void dragRightZoomHandle(int deltaX)
{
int newSelectedRangeEnd =
this.dragStartSelectedRangeEnd + deltaX;
// bound the drag
if(newSelectedRangeEnd < this.dragStartSelectedRangeStart)
{
ZoomScrollBar.this.updateSelectedRangeInPixels(
this.dragStartSelectedRangeStart,
0,
false);
}
else if(newSelectedRangeEnd > this.dragStartFullRangeEnd)
{
ZoomScrollBar.this.updateSelectedRangeInPixels(
this.dragStartSelectedRangeStart,
this.dragStartFullRangeEnd - this.dragStartSelectedRangeStart,
false);
}
else
{
ZoomScrollBar.this.updateSelectedRangeInPixels(
this.dragStartSelectedRangeStart,
this.dragStartSelectedRangeExtent + deltaX,
false);
}
}
private void dragLeftZoomHandle(int deltaX)
{
int newSelectedRangeStart =
this.dragStartSelectedRangeStart + deltaX;
// bound the drag
if(newSelectedRangeStart < this.dragStartFullRangeStart)
{
ZoomScrollBar.this.updateSelectedRangeInPixels(
this.dragStartFullRangeStart,
this.dragStartSelectedRangeEnd - this.dragStartFullRangeStart,
false);
}
else if(newSelectedRangeStart > this.dragStartSelectedRangeEnd)
{
ZoomScrollBar.this.updateSelectedRangeInPixels(
this.dragStartSelectedRangeEnd,
0,
false);
}
else
{
ZoomScrollBar.this.updateSelectedRangeInPixels(
newSelectedRangeStart,
this.dragStartSelectedRangeExtent - deltaX,
false);
}
}
}
/**
* The zoom & scroll are complete
*/
protected abstract void zoomScrollComplete();
}