//
// $Id$

package com.threerings.gwt.util;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.Label;

/**
 * Allows one to wire up a button and a service call into one concisely specified little chunk of
 * code. Be sure to call <code>super.onSuccess()</code> and <code>super.onFailure()</code> if you
 * override those methods so that they can automatically reenable the trigger button.
 *
 * <p> When using the ClickCallback on a Label, the callback automatically adds the style
 * <code>actionLabel</code> to the label and removes the style when the label is disabled during
 * the service call. This style should at a minimum add 'text-decoration: underline; cursor: hand'
 * to the label to indicate to the user that it is clickable.
 */
public abstract class ClickCallback<T>
    implements AsyncCallback<T>
{
    /**
     * Creates a callback for the supplied trigger (the constructor will automatically add this
     * callback to the trigger as a click listener). Failure will automatically be reported.
     */
    public ClickCallback (HasClickHandlers trigger)
    {
        _trigger = trigger;
        _clickreg = _trigger.addClickHandler(_onClick);
        if (_trigger instanceof Label) {
            // make sure to add our style, but don't doubly add it if it's already added
            ((Label)_trigger).removeStyleName("actionLabel");
            ((Label)_trigger).addStyleName("actionLabel");
        }
    }

    // from interface AsyncCallback
    public void onSuccess (T result)
    {
        setEnabled(gotResult(result));
    }

    // from interface AsyncCallback
    public void onFailure (Throwable cause)
    {
        Console.log("Callback failure", "for", _trigger, cause);
        setEnabled(true);
        reportFailure(cause);
    }

    /**
     * This method is called when the trigger button is clicked. Pass <code>this</code> as the
     * {@link AsyncCallback} to a service method. Return true from this method if a service request
     * was initiated and the button that triggered it should be disabled.
     */
    protected abstract boolean callService ();

    /**
     * This method will be called when the service returns successfully. Return true if the trigger
     * should now be reenabled, false to leave it disabled.
     */
    protected abstract boolean gotResult (T result);

    /**
     * Reports a failure to the user. Derived classes can override this and customize the way
     * failure is reported if they so desire.
     */
    protected abstract void reportFailure (Throwable cause);

    protected void takeAction ()
    {
        if (callService()) {
            setEnabled(false);
        }
    }

    protected void setEnabled (boolean enabled)
    {
        if (_trigger instanceof FocusWidget) {
            ((FocusWidget)_trigger).setEnabled(enabled);

        } else if (_trigger instanceof Label) {
            Label tlabel = (Label)_trigger;
            tlabel.removeStyleName("actionLabel");
            if (enabled) {
                tlabel.addStyleName("actionLabel");
            }
        }

        // always remove first so that if we do end up adding, we don't doubly add
        if (_clickreg != null) {
            _clickreg.removeHandler();
            _clickreg = null;
        }
        if (enabled) {
            _clickreg = _trigger.addClickHandler(_onClick);
        }
    }

    protected ClickHandler _onClick = new ClickHandler() {
        public void onClick (ClickEvent event) {
            takeAction();
        }
    };

    protected HasClickHandlers _trigger;
    protected HandlerRegistration _clickreg;
}

@@ -402,16 +402,111 @@
 i.Label;
+%0Aimport com.google.gwt.user.client.ui.TextBox;%0A%0Aimport com.threerings.gwt.ui.EnterClickAdapter;
 %0A%0A/**%0A *
@@ -1488,226 +1488,324 @@
     
-_trigger = trigger
+this(trigger, null)
 ;%0A    
+%7D%0A%0A
     
-_clickreg = _trigger.addClickHandler(_onClick);%0A        if (_trigger instanceof Label) %7B%0A   
+/**%0A     * Creates a callback for the supplied trigger (the constructor will automatically add this%0A     * callback to the trigger as a click listener). Failure will automatically be reported.%0A
      
+*/%0A
     
-// make sure to add our style, but don't doubly add it if it's already added
+public ClickCallback (HasClickHandlers trigger, TextBox onEnter)
 %0A    
+%7B%0A
         
 ((La
@@ -1804,56 +1804,54 @@
     
-((Label)_trigger).removeStyleName(%22actionLabel%22)
+_trigger = trigger;%0A        _onEnter = onEnter
 ;%0A  
@@ -1860,68 +1860,62 @@
     
- 
-   ((Label)_trigger).addStyleName(%22actionLabel%22);%0A        %7D
+setEnabled(true); // this will wire up all of our bits
 %0A   
@@ -3771,78 +3771,318 @@
 if (
-enabled) %7B%0A            _clickreg = _trigger.addClickHandler(_onClick);
+_enterreg != null) %7B%0A            _enterreg.removeHandler();%0A            _enterreg = null;%0A        %7D%0A        if (enabled) %7B%0A            _clickreg = _trigger.addClickHandler(_onClick);%0A            if (_onEnter != null) %7B%0A                _onEnter.addKeyPressHandler(new EnterClickAdapter(_onClick));%0A            %7D
 %0A   
@@ -4332,11 +4332,89 @@
 lickreg;
+%0A%0A    protected TextBox _onEnter;%0A    protected HandlerRegistration _enterreg;
 %0A%7D%0A

Add optional TextBox which can also be used to trigger the action. De-duped configuration of handlers.