/**
* Copyright 2010 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 org.waveprotocol.wave.client.autohide;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import org.waveprotocol.wave.client.common.util.SignalEvent;
import org.waveprotocol.wave.client.common.util.SignalEventImpl;
import org.waveprotocol.wave.client.common.util.SignalEvent.KeySignalType;
import java.util.ArrayList;
import java.util.List;
/**
* Detects auto-hide events by installing an event preview.
*
* This registrar handles multiple simultaneous AutoHiders by treating the ones
* opened later as 'higher up' than the ones opened earlier, and so events that
* cause auto-hides will be routed to the higher AutoHiders first. This
* behaviour can be changed in the future by introducing a more sophisticated
* structure to store the AutoHiders in and, for instance, having each AutoHider
* declaring a parent AutoHider.
*
*/
public class EventPreviewAutoHiderRegistrar implements AutoHiderRegistrar, NativePreviewHandler,
ResizeHandler, ValueChangeHandler<String> {
/**
* List of AutoHiders that currently need to be considered when interpreting
* incoming events.
*/
private final List<AutoHider> autoHiders = new ArrayList<AutoHider>();
/**
* Used to deregister this object from the event preview when there are no
* registered AutoHiders.
*/
private HandlerRegistration eventPreviewRegistration;
private HandlerRegistration onResizeRegistration;
private HandlerRegistration onHistoryRegistration;
@Override
public void registerAutoHider(final AutoHider autoHider) {
autoHider.setRegistered(true);
autoHiders.add(autoHider);
if (eventPreviewRegistration == null) {
eventPreviewRegistration = Event.addNativePreviewHandler(this);
}
if (onResizeRegistration == null) {
onResizeRegistration = Window.addResizeHandler(this);
}
if (onHistoryRegistration == null) {
onHistoryRegistration = History.addValueChangeHandler(this);
}
}
@Override
public void deregisterAutoHider(AutoHider autoHider) {
autoHiders.remove(autoHider);
autoHider.setRegistered(false);
if (autoHiders.isEmpty()) {
if (eventPreviewRegistration != null) {
eventPreviewRegistration.removeHandler();
eventPreviewRegistration = null;
}
if (onResizeRegistration != null) {
onResizeRegistration.removeHandler();
onResizeRegistration = null;
}
if (onHistoryRegistration!= null) {
onHistoryRegistration.removeHandler();
onHistoryRegistration = null;
}
}
}
@Override
public void onPreviewNativeEvent(NativePreviewEvent previewEvent) {
if (autoHiders.isEmpty()) {
return;
}
// TODO(danilatos,user,user): Push signal down a layer - clean this up.
Event event = Event.as(previewEvent.getNativeEvent());
int lowLevelType = event.getTypeInt();
// TODO(danilatos): Insert this logic deeply rather than
// sprinkling it in event handlers. Also the return value
// of onEventPreview is the reverse of signal handlers.
SignalEvent signal = SignalEventImpl.create(event, false);
if (signal == null) {
return;
}
// Key events (excluding escape) and mousewheel events use hideTopmostAutoHiderForKeyEvent
if (lowLevelType == Event.ONMOUSEWHEEL || signal.isKeyEvent()) {
if (hideTopmostAutoHiderForKeyEvent(false)) {
// TODO(user): We don't call previewEvent.cancel() here, since for the floating-buttons
// menu we want, for example, space-bar to still shift focus to the next blip.
// The to-do is to audit the previewEvent.cancel call below and see why it's there (and if
// it's not needed, eliminate it).
return;
}
}
// Pressing escape at any time causes us to close and discard the event.
if (signal.getKeySignalType() == KeySignalType.NOEFFECT &&
event.getKeyCode() == KeyCodes.KEY_ESCAPE) {
if (hideTopmostAutoHiderForKeyEvent(true)) {
previewEvent.cancel();
return;
}
}
// Click events and mouse-wheel events that fall through use hideAllAfter.
if (lowLevelType == Event.ONMOUSEDOWN || lowLevelType == Event.ONMOUSEWHEEL) {
hideAllAfter(signal.getTarget());
}
// Otherwise we don't do anything and the event continues as usual.
}
/**
* Causes all AutoHiders after the one that contains the given element to hide.
*
* @param target An element.
*/
private void hideAllAfter(Element target) {
List<AutoHider> toHide = new ArrayList<AutoHider>();
for (int i = autoHiders.size() - 1; i >= 0; i--) {
AutoHider autoHider = autoHiders.get(i);
if (autoHider.doesContain(target)) {
break;
}
toHide.add(autoHider);
}
for (AutoHider autoHider : toHide) {
autoHider.hide();
}
}
/**
* Hides the topmost AutoHider that is supposed to hide on key events.
*/
private boolean hideTopmostAutoHiderForKeyEvent(boolean keyIsEscape) {
for (int i = autoHiders.size() - 1; i >= 0; i--) {
AutoHider autoHider = autoHiders.get(i);
if (autoHider.shouldHideOnAnyKey() || (keyIsEscape && autoHider.shouldHideOnEscape())) {
autoHider.hide();
return true;
}
}
return false;
}
@Override
public void onResize(ResizeEvent event) {
List<AutoHider> toHide = new ArrayList<AutoHider>();
for (AutoHider autoHider : autoHiders) {
if (autoHider.shouldHideOnWindowResize()) {
toHide.add(autoHider);
}
}
for (AutoHider autoHider : toHide) {
autoHider.hide();
}
}
@Override
public void onValueChange(ValueChangeEvent<String> event) {
List<AutoHider> toHide = new ArrayList<AutoHider>();
for (AutoHider autoHider : autoHiders) {
if (autoHider.shouldHideOnHistoryEvent()) {
toHide.add(autoHider);
}
}
for (AutoHider autoHider : toHide) {
autoHider.hide();
}
}
}