Disable AND grey out an SWT composite - swt

I have a Composite that I want to be able to enable/disable programatically. The Control.setEnabled(boolean enabled) method works fine, but it does not give any visual information that the widget(s) are disabled.
What I would like to do is to have the disabled state mean the widgets are greyed out. Right now they just enter a weird state where the user is unable to click or perform any action on them.

A Composite is a container control that hold other controls using a layout - you can't see a composite really, you can only see the controls it holds. To disable and visually see then disabled, you'll have to call setEnabled(false) on all the children, assuming they're not containers too. Basically, to have to enable/disable the leaf widgets and you will see visual indication.
The reason you can't do anything with the widgets when disabling the Composite is because the Composite is eating all the events. Although the child widgets are not getting the events forwarded, they know nothing about the state of their parent, so they aren't greyed out.

The problem was indeed that I was disabling the composite and not the controls inside it. What I ended up doing was something like this:
public void recursiveSetEnabled(Control ctrl, boolean enabled) {
if (ctrl instanceof Composite) {
Composite comp = (Composite) ctrl;
for (Control c : comp.getChildren())
recursiveSetEnabled(c, enabled);
} else {
ctrl.setEnabled(enabled);
}
}

The other solutions posted here are pretty primitive. They have several shortcomings:
Even if a control is disabled to start with, it will become enabled if its control tree is disabled and then enabled. You probably want to keep such a control disabled.
Sometimes nested controls should remain enabled when their control tree is disabled.
It is useful to distinguish between two different disabled states:
Disabled state, with no information to show. This should be clearly visually indicated to the user.
Displaying information, but read-only state. It is useful to be able to copy text in text fields in this state.
The code below solves these problems. It is the ultimate enabler/disabler for SWT.
It keeps track of the modified controls by tagging them with Widget.setData, so that it only enables the contols it previously has disabled. It handles different kinds of controls differently in tree states: DISABLED, READ_ONLY and EDITABLE.
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
public class GuiEnabler {
/**
* Used to set the enable state of a tree of controls.
*/
public enum EnableState {
/**
* The control is disabled, for when there is no information to show in
* it. All controls, including labels, are disabled.
*/
DISABLED,
/**
* For when there is information to show in the control, but it should
* be read-only. Controls are disabled, except Text which is
* non-editable, and Lables, which are enabeled.
*/
READ_ONLY,
/**
* All controls are enabled and editable.
*/
EDITABLE
}
private static final String ENABLED_KEY = GuiEnabler.class.getName() + ".disabled";
private static final String EDITABLE_KEY = GuiEnabler.class.getName() + ".read_only";
/**
* Disables or makes read-only {#code control} and all its child controls (recursively).
* Also restores the state of controls previously disabled by this method. The action
* performed on the controls is determined by {#link EnableState enableState}.
*
* #param excluded These controls (and their children) are not modified by
* the method.
*/
public static void recursiveUpdateEnableState(Control control, EnableState enableState, Control... excluded) {
updateEnabledState(control, enableState, new HashSet<>(Arrays.asList(excluded)));
}
/**
* See {#link GuiEnabler#recursiveUpdateEnableState(Control, EnableState, Control...)}.
*/
public static void updateEnabledState(Control control, EnableState enableState, Set<Control> excluded) {
if (excluded.contains(control)) {
return;
} else if (control instanceof ExpandableComposite) {
updateEnabledState(((ExpandableComposite) control).getClient(), enableState, excluded);
} else if (control instanceof Composite && !(control instanceof Combo)) {
for (Control child : ((Composite) control).getChildren()) {
updateEnabledState(child, enableState, excluded);
}
} else {
updateControl(control, enableState);
}
}
/**
* Updates a single control to have its proper state for enableState.
*/
private static void updateControl(Control control, EnableState enableState) {
if (enableState == EnableState.DISABLED) {
makeDisabled(control);
} else if (enableState == EnableState.READ_ONLY) {
if (control instanceof Text) {
makeNonEditable((Text) control);
makeEnabled(control);
} if (control instanceof Label) {
makeEnabled(control);
} else {
makeDisabled(control);
}
} else if (enableState == EnableState.EDITABLE) {
makeEnabled(control);
if (control instanceof Text) makeEditable((Text) control);
}
}
private static void makeEnabled(Control control) {
if (control.getData(ENABLED_KEY) != null) {
control.setData(ENABLED_KEY, null);
control.setEnabled(true);
}
}
private static void makeDisabled(Control control) {
if (control.getEnabled()) {
control.setData(ENABLED_KEY, "marked");
control.setEnabled(false);
}
}
private static void makeEditable(Text text) {
if (text.getData(EDITABLE_KEY) != null) {
text.setData(EDITABLE_KEY, null);
text.setEditable(true);
}
}
private static void makeNonEditable(Text text) {
if (text.getEditable()) {
text.setData(EDITABLE_KEY, "marked");
text.setEditable(false);
}
}
}
One limitation of this is that even in disabled state it is still possible to change the active tab in a TabFolder control.

In other words, you need to write code like this, given a Composite c:
for (Control child : c.getChildren())
child.setEnabled(false);

Related

How can I observe the changed state of model items in an ObservableList?

I have an ObservableList of model items. The model item is enabled for property binding (the setter fires a property changed event). The list is the content provider to a TableViewer which allows cell editing. I also intend to add a way of adding new rows (model items) via the TableViewer so the number of items in the list may vary with time.
So far, so good.
As this is all within an eclipse editor, I would like to know when the model gets changed. I just need one changed event from any changed model item in order to set the editor 'dirty'. I guess I could attach some kind of listener to each individual list item object but I wonder if there is a clever way to do it.
I think that I might have a solution. The following class is an inline Text editor. Changes to the model bean (all instances) are picked up using the listener added in doCreateElementObservable. My eclipse editor just needs to add its' own change listener to be kept informed.
public class InlineEditingSupport extends ObservableValueEditingSupport
{
private CellEditor cellEditor;
private String property;
private DataBindingContext dbc;
IChangeListener changeListener = new IChangeListener()
{
#Override
public void handleChange(ChangeEvent event)
{
for (ITableEditorChangeListener listener : listenersChange)
{
listener.changed();
}
}
};
public InlineEditingSupport(ColumnViewer viewer, DataBindingContext dbc, String property)
{
super(viewer, dbc);
cellEditor = new TextCellEditor((Composite) viewer.getControl());
this.property = property;
this.dbc = dbc;
}
protected CellEditor getCellEditor(Object element)
{
return cellEditor;
}
#Override
protected IObservableValue doCreateCellEditorObservable(CellEditor cellEditor)
{
return SWTObservables.observeText(cellEditor.getControl(), SWT.Modify);
}
#Override
protected IObservableValue doCreateElementObservable(Object element, ViewerCell cell)
{
IObservableValue value = BeansObservables.observeValue(element, property);
value.addChangeListener(changeListener); // ADD THIS LINE TO GET CHANGE EVENTS
return value;
}
private List<ITableEditorChangeListener> listenersChange = new ArrayList<ITableEditorChangeListener>();
public void addChangeListener(ITableEditorChangeListener listener)
{
listenersChange.remove(listener);
listenersChange.add(listener);
}
public void removeChangeListener(ITableEditorChangeListener listener)
{
listenersChange.remove(listener);
}
}

create new instance of class that is managed by gin

Im using GWTP, working on their tab panel example. My issue is i need to demonstrate taking a search term and adding a new tab to the tab panel with the search results. So if i search 5 times, i have 5 tabs. easy enough, so i thought.
Gin is used extensively in GWTP. So my method to add a new tab, which should be something as simple as
tabPanel.addTab(new SearchDataGridView(), NameTokens.searchPage + 1);
gets confusing because of the constructor for the SearchDataGridView class
#Inject
SearchDataGridView(Binder uiBinder) {
employeeDataProvider = new ListDataProvider<Employee>();
initSearchGrid();
initWidget(uiBinder.createAndBindUi(this));
}
yea, i know im not passing the search term yet, im still trying to get the tab to open.
My gin config is this
bindPresenter(
SearchDataGridPresenter.class,
SearchDataGridPresenter.MyView.class,
SearchDataGridView.class,
SearchDataGridPresenter.MyProxy.class);
The gwtp gin config
#Override
protected void configure() {
// RestDispatchAsyncModule.Builder dispatchBuilder = new RestDispatchAsyncModule.Builder();
// install(dispatchBuilder.build());
// install(new RestDispatchAsyncModule.Builder().build());
install(new DefaultModule(DefaultPlaceManager.class));
install(new ApplicationModule());
bind(CurrentUser.class).in(Singleton.class);
bind(IsAdminGatekeeper.class).in(Singleton.class);
// DefaultPlaceManager Constants
bindConstant().annotatedWith(DefaultPlace.class).to(NameTokens.homeNewsPage);
bindConstant().annotatedWith(ErrorPlace.class).to(NameTokens.homeNewsPage);
bindConstant().annotatedWith(UnauthorizedPlace.class).to(NameTokens.homeNewsPage);
bindConstant().annotatedWith(Names.named("rest")).to("http://localhost/services");
// Google Analytics
// bindConstant().annotatedWith(GaAccount.class).to("UA-8319339-6");
// Load and inject CSS resources
bind(ResourceLoader.class).asEagerSingleton();
}
How do i pull this off?
thanks
Using comments below, sorta got it working. The problem is i can't get the contents of the tab to display. I added debugging code to the setInSlot method of my SearchContainer class and realized that whenever i click the search tab, it fires this setInSlot method, but its fired with my default page presenter listed as content.
#Override
public void setInSlot(Object slot, IsWidget content) {
Window.alert("fired setInSlot: " + slot.toString());
Window.alert("fired setInSlot: " + content.toString());
if (slot == ApplicationPresenter.TYPE_SetTabContent) {
tabPanel.setPanelContent(content);
} else {
super.setInSlot(slot, content);
}
}
Thats the method im using to get my info. Its weird that the tab appears properly, the jsonRPC calls that are built into the view are executed properly, it just doesn't display.
My main container presenter has its view and proxy identified by this
public class ApplicationPresenter
extends
TabContainerPresenter<ApplicationPresenter.MyView, ApplicationPresenter.MyProxy> implements
CurrentUserChangedHandler, AsyncCallStartHandler, AsyncCallFailHandler, AsyncCallSucceedHandler,
ApplicationUiHandler {
/**
* {#link ApplicationPresenter}'s proxy.
*/
#ProxyStandard
public interface MyProxy extends Proxy<ApplicationPresenter> {
}
/**
* {#link ApplicationPresenter}'s view.
*/
public interface MyView extends TabView, HasUiHandlers<ApplicationUiHandler> {
void refreshTabs();
void setTopMessage(String string);
}
Could my issue be with my content type? Here is what i have defined for all my types
/**
* This will be the event sent to our "unknown" child presenters, in order for them to register their tabs.
*/
#RequestTabs
public static final Type<RequestTabsHandler> TYPE_RequestTabs = new Type<RequestTabsHandler>();
/**
* Fired by child proxie's when their tab content is changed.
*/
#ChangeTab
public static final Type<ChangeTabHandler> TYPE_ChangeTab = new Type<ChangeTabHandler>();
/**
* Use this in leaf presenters, inside their {#link #revealInParent} method.
*/
#ContentSlot
public static final Type<RevealContentHandler<?>> TYPE_SetTabContent = new Type<RevealContentHandler<?>>();
The proper way to do this is to use a PresenterWidget and a Provider.
In your ClientModule you define this:
bindPresenterWidget(SearchDataGridPresenter.class, SearchDataGridPresenter.MyView.class, SearchDataGridView.class);
In your Presenter that adds the SearchDataGridView you inject a Provider:
#Inject
public SeachContainerPresenter(final Provider<SearchDataGridPresenter> seachDataGridProvider) {
}
For each search you call addToSlot(TYPE_SetSearchDataGridContent,seachDataGridProvider.get()) in your SearchContainerPresenter.
In the SearchContainerView you override the addToSlot() method:
#Override
public void addToSlot(Object slot,Widget content) {
if (slot == SeachContainerPresenter.TYPE_SetSearchDataGridContent) {
tabPanel.addTab(content, NameTokens.searchPage + 1);
}
else {
super.addToSlot(slot,content);
}
}
Look in binding everything together. I think that you have to add the AsyncProvider of your presenter to yuor Ginjector.
AsyncProvider<SearchDataGridPresenter> getSearchDataGridPresenter();
You can put a SearchDataGridView getSearchDataGridView() method in the GIN Injector and call it in order to obtain the SearchDataGridView instance.

GWT Cell Tree Right Click Selection

So I have created a CellTree and what I want to do is select the cell that receives a right click so that when I open my context menu to do things, I will know what cell I am working with. Maybe I am going about it the wrong way, I can override the onBrowserEvent method and detect when someone right clicks on the tree but I can't figure out which cell is being clicked so I can manually select it. Has anyone found a solution for this problem?
The solution consists of two steps:
1)
Add a TreeViewModel to the constructor of your CellTree. With that model you can set the names of your elements in the tree. Here is a simple implementation from the API:
private static class CustomTreeModel implements TreeViewModel {
/**
* Get the {#link NodeInfo} that provides the children of the specified
* value.
*/
public <T> NodeInfo<?> getNodeInfo(T value) {
/*
* Create some data in a data provider. Use the parent value as a prefix
* for the next level.
*/
ListDataProvider<String> dataProvider = new ListDataProvider<String>();
for (int i = 0; i < 2; i++) {
dataProvider.getList().add(value + "." + String.valueOf(i));
}
// Return a node info that pairs the data with a cell.
return new DefaultNodeInfo<String>(dataProvider, new TextCell());
}
/**
* Check if the specified value represents a leaf node. Leaf nodes cannot be
* opened.
*/
public boolean isLeaf(Object value) {
// The maximum length of a value is ten characters.
return value.toString().length() > 10;
}
}
2) When you receive the right click Event, get the EventTarget name and compare it with the name of that item you set using the model.
I found a solution, I hope this helps others as I have been searching for this for a long time. There might be a better way, but here is how I accomplished the functionality that I desired:
In the cells that I used inside my tree, I did an override on the onbrowserevent to catch the mouse events and set the selection model. With abstract cells you can sink events you want it to listen to and in my case I chose mouse down.
public class CustomContactCell extends AbstractCell<ContactInfo> {
private SetSelectionModel<ContactInfo> selectionModel;
public CustomContactCell(SetSelectionModel<ContactInfo> selectionModel) {
super("mousedown");
this.selectionModel = selectionModel;
}
#Override
public void render(Context context, ContactInfo value, SafeHtmlBuilder sb) {
...
}
#Override
public void onBrowserEvent(com.google.gwt.cell.client.Cell.Context context, Element parent, ContactInfo value, NativeEvent event, ValueUpdater<ContactInfo> valueUpdater) {
if (event.getButton() == NativeEvent.BUTTON_RIGHT) {
if (selectionModel != null) {
selectionModel.clear();
selectionModel.setSelected(value, true);
}
}
super.onBrowserEvent(context, parent, value, event, valueUpdater);
}
}

Converting GWT Click Events to Touch Events

I am working on a Big Project and i have a lot of GWT code written. Now i am working on making the project fully compatible with Tablets like iPad and Android Tablets.
As a part of this, i have noticed that touch devices takes 300ms delay to handle click events. In this project, writing touch events again is a very tedious task. I have done a lot of researches in this and found the Google Fast Buttons API used in Google Voice Application. I tried that and its working good but requires a lot of coding and JSNI.
My question is, Is there anything else available in your knowledge to easily overcome this delay?
Here is a pure java implementation of the fast button.It doesn't include a single line of JNSI
package com.apollo.tabletization.shared.util;
import java.util.Date;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Touch;
import com.google.gwt.event.dom.client.HasAllTouchHandlers;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;
/** Implementation of Google FastButton {#link http://code.google.com/mobile/articles/fast_buttons.html} */
public class FastButton extends Composite {
private boolean touchHandled = false;
private boolean clickHandled = false;
private boolean touchMoved = false;
private int startY;
private int startX;
private int timeStart;
public FastButton(Widget child) {
// TODO - messages
assert (child instanceof HasAllTouchHandlers) : "";
assert (child instanceof HasClickHandlers) : "";
initWidget(child);
sinkEvents(Event.TOUCHEVENTS | Event.ONCLICK);
}
#Override
public Widget getWidget() {
return super.getWidget();
}
#Override
public void onBrowserEvent(Event event) {
timeStart = getUnixTimeStamp();
switch (DOM.eventGetType(event)) {
case Event.ONTOUCHSTART:
{
onTouchStart(event);
break;
}
case Event.ONTOUCHEND:
{
onTouchEnd(event);
break;
}
case Event.ONTOUCHMOVE:
{
onTouchMove(event);
break;
}
case Event.ONCLICK:
{
onClick(event);
return;
}
}
super.onBrowserEvent(event);
}
private void onClick(Event event) {
event.stopPropagation();
int timeEnd = getUnixTimeStamp();
if(touchHandled) {
//Window.alert("click via touch: "+ this.toString() + "..." +timeStart+"---"+timeEnd);
touchHandled = false;
clickHandled = true;
super.onBrowserEvent(event);
}
else {
if(clickHandled) {
event.preventDefault();
}
else {
clickHandled = false;
//Window.alert("click nativo: "+ this.toString()+ "..." +(timeStart-timeEnd)+"==="+timeStart+"---"+timeEnd);
super.onBrowserEvent(event);
}
}
}
private void onTouchEnd(Event event) {
if (!touchMoved) {
touchHandled = true;
fireClick();
}
}
private void onTouchMove(Event event) {
if (!touchMoved) {
Touch touch = event.getTouches().get(0);
int deltaX = Math.abs(startX - touch.getClientX());
int deltaY = Math.abs(startY - touch.getClientY());
if (deltaX > 5 || deltaY > 5) {
touchMoved = true;
}
}
}
private void onTouchStart(Event event) {
Touch touch = event.getTouches().get(0);
this.startX = touch.getClientX();
this.startY = touch.getClientY();
touchMoved = false;
}
private void fireClick() {
NativeEvent evt = Document.get().createClickEvent(1, 0, 0, 0, 0, false,
false, false, false);
getElement().dispatchEvent(evt);
}
private int getUnixTimeStamp() {
Date date = new Date();
int iTimeStamp = (int) (date.getTime() * .001);
return iTimeStamp;
}
}
I have tried to use the above answers and comments to take a stab at this implementation.
I have also posteds a sample GWT project which can be used for easy comparison:
http://gwt-fast-touch-press.appspot.com/
https://github.com/ashtonthomas/gwt-fast-touch-press
Please note that you will only be able to see the time saved if you are on a mobile device (or devices that handles the touch events and doesn't just fall back to onClick).
I have added 3 fast buttons and 3 normal buttons. You can easily see an improvement when on older mobile devices and sometimes less so on newer (Samsung Galaxy Nexus only showed delays of around 100ms while the 1st gen iPad was over 400ms almost every time). The biggest improvement is when you try to rapidly and consecutively click the boxes (not really buttons here but can be adapted)
package io.ashton.fastpress.client.fast;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Touch;
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.ui.Composite;
import com.google.gwt.user.client.ui.Widget;
/**
*
* GWT Implementation influenced by Google's FastPressElement:
* https://developers.google.com/mobile/articles/fast_buttons
*
* Using Code examples and comments from:
* http://stackoverflow.com/questions/9596807/converting-gwt-click-events-to-touch-events
*
* The FastPressElement is used to avoid the 300ms delay on mobile devices (Only do this if you want
* to ignore the possibility of a double tap - The browser waits to see if we actually want to
* double top)
*
* The "press" event will occur significantly fast (around 300ms faster). However the biggest
* improvement is from enabling fast consecutive touches.
*
* If you try to rapidly touch one or more FastPressElements, you will notice a MUCH great
* improvement.
*
* NOTE: Different browsers will handle quick swipe or long hold/drag touches differently.
* This is an edge case if the user is long pressing or pressing while dragging the finger
* slightly (but staying on the element) - The browser may or may not fire the event. However,
* the browser will always fire the regular tap/press very quickly.
*
* TODO We should be able to embed fastElements and have the child fastElements NOT bubble the event
* So we can embed the elements if needed (???)
*
* #author ashton
*
*/
public abstract class FastPressElement extends Composite implements HasPressHandlers {
private boolean touchHandled = false;
private boolean clickHandled = false;
private boolean touchMoved = false;
private boolean isEnabled = true;
private int touchId;
private int flashDelay = 75; // Default time delay in ms to flash style change
public FastPressElement() {
// Sink Click and Touch Events
// I am not going to sink Mouse events since
// I don't think we will gain anything
sinkEvents(Event.ONCLICK | Event.TOUCHEVENTS); // Event.TOUCHEVENTS adds all (Start, End,
// Cancel, Change)
}
public FastPressElement(int msDelay) {
this();
if (msDelay >= 0) {
flashDelay = msDelay;
}
}
public void setEnabled(boolean enabled) {
if (enabled) {
onEnablePressStyle();
} else {
onDisablePressStyle();
}
this.isEnabled = enabled;
}
/**
* Use this method in the same way you would use addClickHandler or addDomHandler
*
*/
#Override
public HandlerRegistration addPressHandler(PressHandler handler) {
// Use Widget's addHandler to ensureHandlers and add the type/return handler
// We don't use addDom/BitlessHandlers since we aren't sinkEvents
// We also aren't even dealing with a DomEvent
return addHandler(handler, PressEvent.getType());
}
/**
*
* #param event
*/
private void firePressEvent(Event event) {
// This better verify a ClickEvent or TouchEndEvent
// TODO might want to verify
// (hitting issue with web.bindery vs g.gwt.user package diff)
PressEvent pressEvent = new PressEvent(event);
fireEvent(pressEvent);
}
/**
* Implement the handler for pressing but NOT releasing the button. Normally you just want to show
* some CSS style change to alert the user the element is active but not yet pressed
*
* ONLY FOR STYLE CHANGE - Will briefly be called onClick
*
* TIP: Don't make a dramatic style change. Take note that if a user is just trying to scroll, and
* start on the element and then scrolls off, we may not want to distract them too much. If a user
* does scroll off the element,
*
*/
public abstract void onHoldPressDownStyle();
/**
* Implement the handler for release of press. This should just be some CSS or Style change.
*
* ONLY FOR STYLE CHANGE - Will briefly be called onClick
*
* TIP: This should just go back to the normal style.
*/
public abstract void onHoldPressOffStyle();
/**
* Change styling to disabled
*/
public abstract void onDisablePressStyle();
/**
* Change styling to enabled
*
* TIP:
*/
public abstract void onEnablePressStyle();
#Override
public Widget getWidget() {
return super.getWidget();
}
#Override
public void onBrowserEvent(Event event) {
switch (DOM.eventGetType(event)) {
case Event.ONTOUCHSTART: {
if (isEnabled) {
onTouchStart(event);
}
break;
}
case Event.ONTOUCHEND: {
if (isEnabled) {
onTouchEnd(event);
}
break;
}
case Event.ONTOUCHMOVE: {
if (isEnabled) {
onTouchMove(event);
}
break;
}
case Event.ONCLICK: {
if (isEnabled) {
onClick(event);
}
return;
}
default: {
// Let parent handle event if not one of the above (?)
super.onBrowserEvent(event);
}
}
}
private void onClick(Event event) {
event.stopPropagation();
if (touchHandled) {
// if the touch is already handled, we are on a device
// that supports touch (so you aren't in the desktop browser)
touchHandled = false;// reset for next press
clickHandled = true;//
super.onBrowserEvent(event);
} else {
if (clickHandled) {
// Not sure how this situation would occur
// onClick being called twice..
event.preventDefault();
} else {
// Press not handled yet
// We still want to briefly fire the style change
// To give good user feedback
// Show HoldPress when possible
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
#Override
public void execute() {
// Show hold press
onHoldPressDownStyle();
// Now schedule a delay (which will allow the actual
// onTouchClickFire to executed
Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
#Override
public boolean execute() {
// Clear the style change
onHoldPressOffStyle();
return false;
}
}, flashDelay);
}
});
clickHandled = false;
firePressEvent(event);
}
}
}
private void onTouchStart(Event event) {
onHoldPressDownStyle(); // Show style change
// Stop the event from bubbling up
event.stopPropagation();
// Only handle if we have exactly one touch
if (event.getTargetTouches().length() == 1) {
Touch start = event.getTargetTouches().get(0);
touchId = start.getIdentifier();
touchMoved = false;
}
}
/**
* Check to see if the touch has moved off of the element.
*
* NOTE that in iOS the elasticScroll may make the touch/move cancel more difficult.
*
* #param event
*/
private void onTouchMove(Event event) {
if (!touchMoved) {
Touch move = null;
for (int i = 0; i < event.getChangedTouches().length(); i++) {
if (event.getChangedTouches().get(i).getIdentifier() == touchId) {
move = event.getChangedTouches().get(i);
}
}
// Check to see if we moved off of the original element
// Use Page coordinates since we compare with widget's absolute coordinates
int yCord = move.getPageY();
int xCord = move.getPageX();
boolean yTop = getWidget().getAbsoluteTop() > yCord; // is y above element
boolean yBottom = (getWidget().getAbsoluteTop() + getWidget().getOffsetHeight()) < yCord; // y
// below
boolean xLeft = getWidget().getAbsoluteLeft() > xCord; // is x to the left of element
boolean xRight = (getWidget().getAbsoluteLeft() + getWidget().getOffsetWidth()) < xCord; // x
// to
// the
// right
if (yTop || yBottom || xLeft || xRight) {
touchMoved = true;
onHoldPressOffStyle();// Go back to normal style
}
}
}
private void onTouchEnd(Event event) {
if (!touchMoved) {
touchHandled = true;
firePressEvent(event);
event.preventDefault();
onHoldPressOffStyle();// Change back the style
}
}
}
I think the code from the prior answer as written has a few problems, in particular when their are multiple touches.
(NOTE: I'm looking at code I wrote using the Elemental library as reference, so some of the calls might be different in the user library).
a) The code is not filtering touches aimed at the button; it calls TouchEvent.getTouches(). You want to call TouchEvent.getTargetTouches() on touchstart and touchmove to get the the touches just for your button. You want to call TouchEvent.getChangedTouches() on touchend to get the end touch.
b) The code does not take into account multitouch. On touchstart, you can check that a single touch is available and bail out if there is more than one. Also, on touchstart, stash away the id of touch, then use this in touchmove and touchend to find your touch id in the array that is returned (in case the user has touched another finger later on). You can also simplify and check for multiple touches on touchmove and touchend and bail again there.
c) I believe you need to call stopPropagation on touchstart, since you are handling the event. I don't see where they call event.stopPropagation on the touchstart event You can see that this happens in the click handlers, but not the touchstart. This prevents the touch from being turned into a click automatically by the browser, which would cause multiple clicks.
There is also a simpler way. If you don't care about dragging starting on a button, then you can simply call your click logic in the touchstart event (and make sure you check for single touch, and call event.stopPropagation ) and ignore touchmove and touchend. All the touchmove and touchend stuff is to handle the case of allowing dragging to start on the button.
Also Try FastClick
FastClick is a simple, easy-to-use library for eliminating the 300ms delay between a physical tap and the firing of a click event on mobile browsers. The aim is to make your application feel less laggy and more responsive while avoiding any interference with your current logic.
FastClick is developed by FT Labs, part of the Financial Times.
The library has been deployed as part of the FT Web App and is tried and tested on the following mobile browsers:
Mobile Safari on iOS 3 and upwards
Chrome on iOS 5 and upwards
Chrome on Android (ICS)
Opera Mobile 11.5 and upwards
Android Browser since Android 2
PlayBook OS 1 and upwards
FastClick doesn't attach any listeners on desktop browsers as it is not needed. Those that have been tested are:
Safari
Chrome
Internet Explorer
Firefox
Opera

GWT 2.2.0 PopupPanel autoHide on TOUCH Events

PopupPanel is a class within GWT written (akhem) a long time ago (which is why it sucks so much) that allows for showing popups with content. One of the options is autoHide where if there's a certain event outside of the popup it closes the popup. It works well on anything EXCEPT Safari Mobil (SM). Reason is SM doesn't fire click events on touch. It fires touch events. PopupPanel is hard coded to look for ClickEvents.
Specifically, the code says:
case Event.ONMOUSEDOWN:
...
if (!eventTargetsPopupOrPartner && autoHide) {
hide(true);
...
Obviously this isn't complete and it should also include Event.ONTOUCHSTART
Problem is, all the methods and fields are private so I cannot add this functionality. That's a big boo-boo on the part of GWT team but not really a concern since I could just make my own class and copy contents of PopupPanel. The big problem is that nativeEventPreview doesn't capture Touch Events!
I tried adding the following to Event Preview the following:
private static NativePreviewHandler nativePreviewHandler = new NativePreviewHandler() {
public void onPreviewNativeEvent(NativePreviewEvent event) {
Event nativeEvent = Event.as(event.getNativeEvent());
switch (nativeEvent.getTypeInt()) {
case Event.ONTOUCHSTART:
case Event.ONMOUSEDOWN:
EventTarget target = nativeEvent.getEventTarget();
if (!Element.is(target) || !popup.getElement().isOrHasChild(Element.as(target))) {
popup.hide();
} break;
}
}
};
Where 'popup' is the PopupPanel I'm trying to get to close on outside touch events.
Sad thing is it works for mouse down when testing in any other browser on Earth, but not on iPad.
Another thing I tried was adding a TouchStartHandler to the glass of the PopupPanel (the gray looking thing behind it). I war hoping that I could catch the touch events that way, but I was unable to get events to fire on glass since it's attached to DOM in some funny way. My code:
private static class ProperPopupPanel extends PopupPanel {
public ProperPopupPanel() {
super();
}
void setHideOnGlassTouch() {
setGlassEnabled(true);
TouchableLabeThatDoesntCrashOnWrap glass = new TouchableLabeThatDoesntCrashOnWrap(getGlassElement());
glass.addTouchStartHandler(new TouchStartHandler() {
#Override
public void onTouchStart(TouchStartEvent event) {
hide();
}
});
glass.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
hide();
}
});
}
private class TouchableLabeThatDoesntCrashOnWrap extends Label {
public TouchableLabeThatDoesntCrashOnWrap(Element element) {
super(element);
super.onAttach();
}
}
}
In my mind this should work, but it doesn't. I don't know why. Any ideas or suggestions are welcome. Thanks.
Not enough GWT users here... well I made my own class that adds touch handlers through JSNI ...
/**
* Overwrite of the usual PopupPanel with a modification that this one
* works well on touch-enabled browsers.
* #author McTrafik
*/
public class ProperPopupPanel extends PopupPanel {
////////////////////////////////////////////////////////////
/////////// OVERRIDES //////////////////////////////////////
////////////////////////////////////////////////////////////
public ProperPopupPanel() {
super();
setTouchListener();
}
#Override
public void hide() {
super.hide();
removeTouchListener();
}
#Override
public void show() {
super.show();
addTouchListener();
}
////////////////////////////////////////////////////////////
/////////// NANDLERS ///////////////////////////////////////
////////////////////////////////////////////////////////////
protected JavaScriptObject touchHandler;
/**
* Handle a touch event that happened while the popup is open.
* #param event - The event to handle
*/
protected void handleTouchEvent(Event event) {
// Check to see if the events should be firing in the first place.
if (!isShowing()) {
removeTouchListener();
return;
}
// Check if the event happened within the popup
EventTarget target = event.getEventTarget();
if (!Element.is(target) || !getElement().isOrHasChild(Element.as(target))) {
// Stop event if the popup is modal
if (isModal()) event.preventDefault();
// Close the popup if the event happened outside
if (isAutoHideEnabled()) {
hide(true);
removeTouchListener();
}
}
};
/**
* Create a touchHandler that knows how to point to this instance.
* Without it there's a cast exception that happens.
*/
protected native void setTouchListener() /*-{
var caller = this;
this.#[package].ProperPopupPanel::touchHandler = function(event) {
caller.#[package].ProperPopupPanel::handleTouchEvent(Lcom/google/gwt/user/client/Event;)(event);
}
}-*/;
/**
* Add a touch listener that will listen to touch events.
*/
protected native void addTouchListener() /*-{
$doc.addEventListener(
"touchstart",
this.#[package].ProperPopupPanel::touchHandler,
true
);
$doc.addEventListener(
"MozTouchDown",
this.#[package].ProperPopupPanel::touchHandler,
true
);
}-*/;
/**
* Remove the touch listeners
*/
protected native void removeTouchListener() /*-{
$doc.removeEventListener(
"touchstart",
this.#[package].ProperPopupPanel::touchHandler,
true
);
$doc.removeEventListener(
"MozTouchDown",
this.#[package].ProperPopupPanel::touchHandler,
true
);
}-*/;
}