package org.geogebra.web.web.gui.view.spreadsheet;
import javax.swing.JComponent;
import org.geogebra.common.awt.GPoint;
import org.geogebra.common.awt.GRectangle;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style;
import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.user.client.ui.AbstractNativeScrollbar;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.ScrollPanel;
public class TableScroller extends ScrollPanel implements ScrollHandler {
private MyTableW table;
private Grid cellTable;
private SpreadsheetRowHeaderW rowHeader;
private SpreadsheetColumnHeaderW columnHeader;
public TableScroller(MyTableW table, SpreadsheetRowHeaderW rowHeader,
SpreadsheetColumnHeaderW columnHeader) {
super(table.getGridPanel());
this.table = table;
this.cellTable = table.getGrid();
this.rowHeader = rowHeader;
this.columnHeader = columnHeader;
addScrollHandler(this);
}
/**
* Used by the scrollRectToVisible method to determine the proper direction
* and amount to move by. The integer variables are named width, but this
* method is applicable to height also. The code assumes that
* parentWidth/childWidth are positive and childAt can be negative.
*/
private static int positionAdjustment(int parentWidth, int childWidth,
int childAt) {
// App.debug("parent width = " + parentWidth);
// App.debug("child width = " + childWidth);
// App.debug("child at = " + childAt);
// +-----+
// | --- | No Change
// +-----+
if (childAt >= 0 && childWidth + childAt <= parentWidth) {
return 0;
}
// +-----+
// --------- No Change
// +-----+
if (childAt <= 0 && childWidth + childAt >= parentWidth) {
return 0;
}
// +-----+ +-----+
// | ---- -> | ----|
// +-----+ +-----+
if (childAt > 0 && childWidth <= parentWidth) {
return -childAt + parentWidth - childWidth;
}
// +-----+ +-----+
// | -------- -> |--------
// +-----+ +-----+
if (childAt >= 0 && childWidth >= parentWidth) {
return -childAt;
}
// +-----+ +-----+
// ---- | -> |---- |
// +-----+ +-----+
if (childAt <= 0 && childWidth <= parentWidth) {
return -childAt;
}
// +-----+ +-----+
// -------- | -> --------|
// +-----+ +-----+
if (childAt < 0 && childWidth >= parentWidth) {
return -childAt + parentWidth - childWidth;
}
return 0;
}
public void scrollRectToVisible(GRectangle contentRect) {
this.contentRect = contentRect;
Scheduler.get().scheduleDeferred(scrollRectCommand);
}
GRectangle contentRect;
Scheduler.ScheduledCommand scrollRectCommand = new Scheduler.ScheduledCommand() {
@Override
public void execute() {
scrollRectToVisibleCommand();
}
};
/**
* Scrolls the view so that <code>Rectangle</code> within the view becomes
* visible.
* <p>
* This attempts to validate the view before scrolling if the view is
* currently not valid - <code>isValid</code> returns false. To avoid
* excessive validation when the containment hierarchy is being created this
* will not validate if one of the ancestors does not have a peer, or there
* is no validate root ancestor, or one of the ancestors is not a
* <code>Window</code> or <code>Applet</code>.
* <p>
* Note that this method will not scroll outside of the valid viewport; for
* example, if <code>contentRect</code> is larger than the viewport,
* scrolling will be confined to the viewport's bounds.
*
* @param contentRect
* the <code>Rectangle</code> to display
* @see JComponent#isValidateRoot
* @see java.awt.Component#isValid
* @see java.awt.Component#getPeer
*/
public void scrollRectToVisibleCommand() {
Element view = this.getWidget().getElement();
if (view == null) {
return;
}
int dx, dy;
int barHeight = AbstractNativeScrollbar.getNativeScrollbarHeight();
int barWidth = AbstractNativeScrollbar.getNativeScrollbarWidth();
dx = positionAdjustment(this.getOffsetWidth() - barWidth,
(int) contentRect.getWidth(), (int) contentRect.getX()
- getAbsoluteLeft());
dy = positionAdjustment(this.getOffsetHeight() - barHeight,
(int) contentRect.getHeight(), (int) contentRect.getY()
- getAbsoluteTop());
// App.debug("-------- dx / dy : " + dx + " / " + dy);
if (dx != 0 || dy != 0) {
GPoint viewPosition = getViewPosition();
Dimension viewSize = new Dimension(view.getOffsetWidth(),
view.getOffsetHeight());
int startX = viewPosition.x;
int startY = viewPosition.y;
Dimension extent = new Dimension(this.getOffsetWidth() - barWidth,
this.getOffsetHeight() - barHeight);
// App.debug("viewSize w / h : " + viewSize.width + " / " +
// viewSize.height);
// App.debug("viewPosition x / y : " + viewPosition.x + " / " +
// viewPosition.y);
viewPosition.x -= dx;
viewPosition.y -= dy;
// TODO In the java code a check is made here for component
// orientation, we may do this on the future?
if (extent.width > viewSize.width) {
viewPosition.x = viewSize.width - extent.width;
} else {
viewPosition.x = Math
.max(0, Math.min(viewSize.width - extent.width,
viewPosition.x));
}
if (viewPosition.y + extent.height > viewSize.height) {
viewPosition.y = Math.max(0, viewSize.height - extent.height);
} else if (viewPosition.y < 0) {
viewPosition.y = 0;
}
// App.debug("viewPosition x / y : " + viewPosition.x + " / " +
// viewPosition.y);
if (viewPosition.x != startX || viewPosition.y != startY) {
doAdjustScroll = false;
setViewPosition(viewPosition);
// NOTE: How JViewport currently works with the
// backing store is not foolproof. The sequence of
// events when setViewPosition
// (scrollRectToVisible) is called is to reset the
// views bounds, which causes a repaint on the
// visible region and sets an ivar indicating
// scrolling (scrollUnderway). When
// JViewport.paint is invoked if scrollUnderway is
// true, the backing store is blitted. This fails
// if between the time setViewPosition is invoked
// and paint is received another repaint is queued
// indicating part of the view is invalid. There
// is no way for JViewport to notice another
// repaint has occured and it ends up blitting
// what is now a dirty region and the repaint is
// never delivered.
// It just so happens JTable encounters this
// behavior by way of scrollRectToVisible, for
// this reason scrollUnderway is set to false
// here, which effectively disables the backing
// store.
// scrollUnderway = false;
}
}
}
private void setViewPosition(GPoint viewPosition) {
this.setHorizontalScrollPosition(viewPosition.x);
this.setVerticalScrollPosition(viewPosition.y);
syncHeaders();
}
private GPoint getViewPosition() {
return new GPoint(getHorizontalScrollPosition(),
getVerticalScrollPosition());
}
private static class Dimension {
int height;
int width;
public Dimension(int width, int height) {
this.width = width;
this.height = height;
}
}
boolean doAdjustScroll = true;
protected void adjustScroll() {
if (!doAdjustScroll) {
return;
}
int offH = cellTable.getAbsoluteLeft();
int offV = cellTable.getAbsoluteTop();
// get pixel coordinates of the upper left corner
int x = getHorizontalScrollPosition() + offH;
int y = getVerticalScrollPosition() + offV;
// get upper left cell coordinates
GPoint p = table.getIndexFromPixel(x, y);
if (p == null) {
return;
}
// get new pixel coordinates to place the upper left cell exactly
GPoint p2 = table.getPixel(p.x, p.y, true);
if (p2 == null) {
return;
}
// now scroll to move the upper left cell into position
int newScrollH = p2.x - offH;
int newScrollV = p2.y - offV;
// App.debug("scroll: " + x + " , " + y + " col,row: " + p.x + " , "
// + p.y + " scroll2: " + newScrollH + " , " + newScrollV);
doAdjustScroll = false;
setHorizontalScrollPosition(newScrollH);
setVerticalScrollPosition(newScrollV);
doAdjustScroll = true;
}
@Override
public void onScroll(ScrollEvent event) {
adjustScroll();
syncHeaders();
}
private void syncHeaders(){
int t = -getVerticalScrollPosition();
int l = -getHorizontalScrollPosition();
rowHeader.setTop(t);
columnHeader.setLeft(l);
}
/*
* Fits the content of spreadsheet for its header on the left.
*/
public void syncTableTop(){
setVerticalScrollPosition(rowHeader.getTop());
}
/**
* @param showHScrollBar
* true = hide the horizontal scroll bar
*/
public void setShowHScrollBar(boolean showHScrollBar) {
getScrollableElement().getStyle().setOverflowX(
showHScrollBar ? Style.Overflow.AUTO : Style.Overflow.HIDDEN);
}
/**
* @param showVScrollBar true = hide the vertical scroll bar
*/
public void setShowVScrollBar(boolean showVScrollBar) {
getScrollableElement().getStyle().setOverflowY(
showVScrollBar ? Style.Overflow.AUTO : Style.Overflow.HIDDEN);
}
}