/*
* Copyright 2008 Google Inc.
*
* 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.google.gwt.widgetideas.client;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.WindowResizeListener;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* A collection of {@link ResizableWidget} that periodically checks the outer
* dimensions of a widget and redraws it as necessary. Every
* {@link ResizableWidgetCollection} uses a timer, so consider the cost when
* adding one.
*
* Typically, a {@link ResizableWidgetCollection} is only needed if you expect
* your widgets to resize based on window resizing or other events. Fixed sized
* Widgets do not need to be added to a {@link ResizableWidgetCollection} as
* they cannot be resized.
*/
public class ResizableWidgetCollection implements WindowResizeListener,
Iterable<ResizableWidget> {
/**
* Information about a widgets size.
*/
static class ResizableWidgetInfo {
private ResizableWidget widget;
private int curOffsetHeight = 0;
private int curOffsetWidth = 0;
private int curClientHeight = 0;
private int curClientWidth = 0;
/**
* Constructor.
*
* @param widget the widget that will be monitored
*/
public ResizableWidgetInfo(ResizableWidget widget) {
this.widget = widget;
updateSizes();
}
public int getClientHeight() {
return curClientHeight;
}
public int getClientWidth() {
return curClientWidth;
}
public int getOffsetHeight() {
return curOffsetHeight;
}
public int getOffsetWidth() {
return curOffsetWidth;
}
/**
* Update the current sizes.
*
* @return true if the sizes changed, false if not.
*/
public boolean updateSizes() {
int offsetWidth = widget.getElement().getOffsetWidth();
int offsetHeight = widget.getElement().getOffsetHeight();
int clientWidth = widget.getElement().getClientWidth();
int clientHeight = widget.getElement().getClientHeight();
if (offsetWidth != curOffsetWidth || offsetHeight != curOffsetHeight
|| clientWidth != curClientWidth || clientHeight != curClientHeight) {
this.curOffsetWidth = offsetWidth;
this.curOffsetHeight = offsetHeight;
this.curClientWidth = clientWidth;
this.curClientHeight = clientHeight;
return true;
}
return false;
}
}
/**
* The default delay between resize checks in milliseconds.
*/
private static final int DEFAULT_RESIZE_CHECK_DELAY = 400;
/**
* A static {@link ResizableWidgetCollection} that can be used in most cases.
*/
private static ResizableWidgetCollection staticCollection = null;
/**
* Get the globally accessible {@link ResizableWidgetCollection}. In most
* cases, the global collection can be used for all {@link ResizableWidget}s.
*
* @return the global {@link ResizableWidgetCollection}
*/
public static ResizableWidgetCollection get() {
if (staticCollection == null) {
staticCollection = new ResizableWidgetCollection();
}
return staticCollection;
}
/**
* The timer used to periodically compare the dimensions of elements to their
* old dimensions.
*/
private Timer resizeCheckTimer = new Timer() {
@Override
public void run() {
// Ignore changes that result from window resize events
if (windowHeight != Window.getClientHeight()
|| windowWidth != Window.getClientWidth()) {
windowHeight = Window.getClientHeight();
windowWidth = Window.getClientWidth();
schedule(resizeCheckDelay);
return;
}
// Look for elements that have new dimensions
checkWidgetSize();
// Start checking again
if (resizeCheckingEnabled) {
schedule(resizeCheckDelay);
}
}
};
/**
* A hash map of the resizable widgets this collection is checking.
*/
private Map<ResizableWidget, ResizableWidgetInfo> widgets = new HashMap<ResizableWidget, ResizableWidgetInfo>();
/**
* The current window height.
*/
private int windowHeight = 0;
/**
* The current window width.
*/
private int windowWidth = 0;
/**
* The hook used to remove the window handler.
*/
private HandlerRegistration windowHandler;
/**
* The delay between resize checks.
*/
private int resizeCheckDelay = DEFAULT_RESIZE_CHECK_DELAY;
/**
* A boolean indicating that resize checking should run.
*/
private boolean resizeCheckingEnabled;
/**
* Create a ResizableWidget.
*/
public ResizableWidgetCollection() {
this(DEFAULT_RESIZE_CHECK_DELAY);
}
/**
* Constructor.
*
* @param resizeCheckingEnabled false to disable resize checking
*/
public ResizableWidgetCollection(boolean resizeCheckingEnabled) {
this(DEFAULT_RESIZE_CHECK_DELAY, resizeCheckingEnabled);
}
/**
* Constructor.
*
* @param resizeCheckDelay the delay between checks in milliseconds
*/
public ResizableWidgetCollection(int resizeCheckDelay) {
this(resizeCheckDelay, true);
}
/**
* Constructor.
*/
protected ResizableWidgetCollection(int resizeCheckDelay,
boolean resizeCheckingEnabled) {
setResizeCheckDelay(resizeCheckDelay);
setResizeCheckingEnabled(resizeCheckingEnabled);
}
/**
* Add a resizable widget to the collection.
*
* @param widget the resizable widget to add
*/
public void add(ResizableWidget widget) {
widgets.put(widget, new ResizableWidgetInfo(widget));
}
/**
* Check to see if any Widgets have been resized and call their handlers
* appropriately.
*/
public void checkWidgetSize() {
for (Map.Entry<ResizableWidget, ResizableWidgetInfo> entry : widgets.entrySet()) {
ResizableWidget widget = entry.getKey();
ResizableWidgetInfo info = entry.getValue();
// Call the onResize method only if the widget is attached
if (info.updateSizes()) {
// Check that the offset width and height are greater than 0.
if (info.getOffsetWidth() > 0 && info.getOffsetHeight() > 0
&& widget.isAttached()) {
// Send the client dimensions, which is the space available for
// rendering.
widget.onResize(info.getOffsetWidth(), info.getOffsetHeight());
}
}
}
}
/**
* Get the delay between resize checks in milliseconds.
*
* @return the resize check delay
*/
public int getResizeCheckDelay() {
return resizeCheckDelay;
}
/**
* Check whether or not resize checking is enabled.
*
* @return true is resize checking is enabled
*/
public boolean isResizeCheckingEnabled() {
return resizeCheckingEnabled;
}
public Iterator<ResizableWidget> iterator() {
return widgets.keySet().iterator();
}
/**
* Called when the browser window is resized.
*
* @param width the width of the window's client area.
* @param height the height of the window's client area.
*/
@Deprecated
public void onWindowResized(int width, int height) {
checkWidgetSize();
}
/**
* Remove a {@link ResizableWidget} from the collection.
*
* @param widget the widget to remove
*/
public void remove(ResizableWidget widget) {
widgets.remove(widget);
}
/**
* Set the delay between resize checks in milliseconds.
*
* @param resizeCheckDelay the new delay
*/
public void setResizeCheckDelay(int resizeCheckDelay) {
this.resizeCheckDelay = resizeCheckDelay;
}
/**
* Set whether or not resize checking is enabled. If disabled, elements will
* still be resized on window events, but the timer will not check their
* dimensions periodically.
*
* @param enabled true to enable the resize checking timer
*/
public void setResizeCheckingEnabled(boolean enabled) {
if (enabled && !resizeCheckingEnabled) {
resizeCheckingEnabled = true;
if (windowHandler == null) {
windowHandler = Window.addResizeHandler(new ResizeHandler() {
public void onResize(ResizeEvent event) {
onWindowResized(event.getWidth(), event.getHeight());
}
});
}
resizeCheckTimer.schedule(resizeCheckDelay);
} else if (!enabled && resizeCheckingEnabled) {
resizeCheckingEnabled = false;
if (windowHandler != null) {
windowHandler.removeHandler();
windowHandler = null;
}
resizeCheckTimer.cancel();
}
}
/**
* Inform the {@link ResizableWidgetCollection} that the size of a widget has
* changed and already been redrawn. This will prevent the widget from being
* redrawn on the next loop.
*
* @param widget the widget's size that changed
*/
public void updateWidgetSize(ResizableWidget widget) {
if (!widget.isAttached()) {
return;
}
ResizableWidgetInfo info = widgets.get(widget);
if (info != null) {
info.updateSizes();
}
}
}