ch.unifr.pai.twice.mousecontrol.client.TouchPadWidget.java Source code

Java tutorial

Introduction

Here is the source code for ch.unifr.pai.twice.mousecontrol.client.TouchPadWidget.java

Source

package ch.unifr.pai.twice.mousecontrol.client;

/*
 * Copyright 2013 Oliver Schmid
 * 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.
 */
import java.util.Date;

import ch.unifr.pai.twice.authentication.client.Authentication;
import ch.unifr.pai.twice.module.client.TWICEAnnotations.Configurable;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.web.bindery.event.shared.HandlerRegistration;

/**
 * A generic implementation of a remote mouse control component
 * 
 * @author Oliver Schmid
 * 
 */
public abstract class TouchPadWidget extends LayoutPanel {

    /**
     * The interval in ms of updating messages
     */
    @Configurable("Movement interval")
    static int MOVEMENTUPDATEINTERVAL = 80;
    /**
     * The threshold of distance in pixels that has to be exceeded by a mouse movement to trigger an event
     */
    @Configurable("Movement threshold")
    static int MOVEMENTTHRESHOLD = 0;
    /**
     * The factor with which the movement shall be increased
     */
    @Configurable("Movement factor")
    static double MOVEFACTOR = 1.8;
    /**
     * The threshold of how long the mouse shall be pressed until the device switches to drag mode
     */
    @Configurable("Mouse down threshold")
    static int MOUSEDOWNTHRESHOLD = 300;
    /**
     * In which interval the client shall try to assign a cursor on the shared screen
     */
    @Configurable("Look for cursor interval")
    static int LOOKFORCURSORINTERVAL = 2000;

    private String uuid;
    private String host;
    private Integer port;
    private String currentColor;
    private String screenDimension;
    protected int screenWidth;
    protected int screenHeight;
    private boolean active = true;
    private int currentX = -1;
    private int currentY = -1;
    private boolean running;
    private boolean downLastAction = false;
    private boolean doLog = false;
    private StringBuilder log = new StringBuilder();
    private String header;
    private String[] availableClients;
    private final Label label = new Label();
    // private TextBox focusTextBox = new TextBox();
    protected boolean dragging = false;

    // private final static String controlServlet = "mouseManager";
    private final static String controlServlet = "mouseManagerXBrowser";

    // private final static String controlServlet = "mouseManagerXBrowserWS";

    public String getHeader() {
        return header;
    }

    public void setHeader(String header) {
        this.header = header;
    }

    /**
     * Add data to the log
     * 
     * @param name
     * @param attributes
     * @param content
     */
    public void addToLog(String name, String attributes, String content) {
        log.append("<").append(name);
        log.append(" time=\"").append(new Date().getTime()).append("\" uuid=\"").append(uuid)
                .append("\" color=\"" + getColor() + "\"");
        if (attributes != null) {
            log.append(" ").append(attributes);
        }
        log.append(">");
        if (content != null)
            log.append(content);
        log.append("</").append(name).append(">");
    }

    /**
     * Clear the log
     * 
     * @return the log state before clearance
     */
    public String flushLog() {
        String result = log.toString();
        log = new StringBuilder();
        return result;
    }

    /**
     * @return true if the component is writing to the log
     */
    public boolean isDoLog() {
        return doLog;
    }

    /**
     * define if the component shall write to the log
     * 
     * @param doLog
     */
    public void setDoLog(boolean doLog) {
        this.doLog = doLog;
    }

    /**
     * defines if drag is enabled
     */
    protected boolean dragModeEnabled = true;

    public TouchPadWidget(boolean handleFocus) {
        super();
        // add(focusTextBox);
        add(label);
        // focusTextBox.getElement().getStyle().setZIndex(-1);
        // focusTextBox.getElement().getStyle().setVisibility(Visibility.HIDDEN);
        // send(null);
    }

    /**
     * @return true if drag mode is enabled, false otherwise
     */
    public boolean isDragModeEnabled() {
        return dragModeEnabled;
    }

    /**
     * set if drag mode is enabled
     * 
     * @param dragModeEnabled
     */
    public void setDragModeEnabled(boolean dragModeEnabled) {
        this.dragModeEnabled = dragModeEnabled;
    }

    /**
     * timer which sends out movement events
     */
    private final Timer movement = new Timer() {

        @Override
        public void run() {
            int x = getX();
            int y = getY();
            if (currentX != x || currentY != y) {
                move(x, y);
                currentX = x;
                currentY = y;
            }
        }
    };

    /**
     * @return the id of the current available shared screen
     */
    private String getCurrentClient() {
        // TODO the user should select the client if there are multiple. For
        // testing, we take the latest
        return availableClients != null && availableClients.length > 0
                ? availableClients[availableClients.length - 1]
                : null;
    }

    /**
     * if no cursor is available on the shared screen, try to gather one with the given interval
     */
    private void noCursorAvailable() {
        setActive(false);
        if (lookForCursor != null)
            lookForCursor.schedule(LOOKFORCURSORINTERVAL);
    }

    /**
     * If a cursor is assigned, start to fire events
     */
    private void cursorAssigned() {
        setActive(true);
        running = true;
        String updateInterval = Window.Location.getParameter("update");
        if (updateInterval != null)
            MOVEMENTUPDATEINTERVAL = Integer.parseInt(updateInterval);
        movement.scheduleRepeating(MOVEMENTUPDATEINTERVAL);
        keyboardHandler = Event.addNativePreviewHandler(keyboardPreviewHandler);
    }

    /**
     * Handler for keyboard events and invocation of the key events on the shared screen
     */
    protected NativePreviewHandler keyboardPreviewHandler = new NativePreviewHandler() {

        @Override
        public void onPreviewNativeEvent(NativePreviewEvent event) {
            switch (event.getTypeInt()) {
            case Event.ONKEYDOWN:
                // if (handleFocus)
                // focusTextBox.setFocus(true);
                send("a=kd&kc=" + event.getNativeEvent().getKeyCode() + "&cc="
                        + event.getNativeEvent().getCharCode());
                break;
            case Event.ONKEYUP:
                // if (handleFocus)
                // focusTextBox.setFocus(true);
                send("a=ku&kc=" + event.getNativeEvent().getKeyCode() + "&cc="
                        + event.getNativeEvent().getCharCode());
                break;
            case Event.ONKEYPRESS:
                // if (handleFocus)
                // focusTextBox.setFocus(true);
                send("a=kp&kc=" + event.getNativeEvent().getKeyCode() + "&cc="
                        + event.getNativeEvent().getCharCode());
                break;
            }
        }
    };

    protected HandlerRegistration keyboardHandler;

    private Timer lookForCursor;

    /**
     * Invoked if the last action has changed. Forces the stop or the start of dragging mode based on information originated on the server
     * 
     * @param action
     */
    protected void onActionChanged(String action) {
        if (action != null) {
            if (action.equals("startDrag"))
                dragging = true;
            else if (action.equals("endDrag"))
                stopDragging();
        }
    }

    /**
     * stops the dragging
     */
    protected void stopDragging() {
        dragging = false;
    }

    /**
     * starts the execution of the component
     */
    public void start() {
        if (!running) {
            label.setText("looking for available remote-clients");
            getAvailableClients(new Command() {

                @Override
                public void execute() {
                    label.setText((availableClients == null ? "0" : availableClients.length) + " clients found");
                    if (getCurrentClient() != null) {
                        label.setText("looking for cursor on client " + getCurrentClient());
                        lookForCursor = new Timer() {

                            @Override
                            public void run() {
                                try {
                                    new RequestBuilder(RequestBuilder.GET, GWT.getHostPageBaseURL() + controlServlet
                                            + "?a=x"
                                            + (getCurrentClient() != null ? "&targetUUID=" + getCurrentClient()
                                                    : "")
                                            + (uuid != null ? "&uuid=" + uuid : "")
                                            + (host != null ? "&host=" + host : "")
                                            + (port != null ? "&port=" + port : "")).sendRequest(null,
                                                    new RequestCallback() {

                                                        @Override
                                                        public void onResponseReceived(Request request,
                                                                Response response) {
                                                            if (response.getStatusCode() > 400)
                                                                onError(request, null);
                                                            label.setText("GOT DATA: " + response.getText());
                                                            String color = extractColor(response);
                                                            if (color == null || color.isEmpty()
                                                                    || color.equals("#null"))
                                                                color = null;
                                                            extractLastAction(response);

                                                            setScreenDimension(extractScreenDimensions(response));
                                                            if (color != null) {
                                                                setColor(color);
                                                                cursorAssigned();
                                                            } else {
                                                                noCursorAvailable();
                                                            }
                                                        }

                                                        @Override
                                                        public void onError(Request request, Throwable exception) {
                                                            noCursorAvailable();
                                                        }
                                                    });
                                } catch (RequestException e) {
                                    noCursorAvailable();
                                }
                            }
                        };
                        lookForCursor.run();
                    }
                }
            });
        }
    }

    /**
     * stops the execution of the component (also interrupts the sending of events).
     */
    public void stop() {
        if (running) {
            running = false;
            movement.cancel();
        }
    }

    /**
     * Define the cursor id as well as the host and port for the target of the mouse control events. If not defined, the session id, localhost and the standard
     * port will be used.
     * 
     * @param uuid
     * @param host
     * @param port
     */
    public void initialize(String uuid, String host, Integer port) {
        this.uuid = uuid;
        this.host = host;
        this.port = port;
    }

    /**
     * send a mouse down event to the mouse control servlet
     * 
     * @param leftButton
     */
    protected void down(boolean leftButton) {
        send("a=d&b=" + (leftButton ? "l" : "r"));
        downLastAction = true;
    }

    /**
     * send a mouse up event to the mouse control servlet
     * 
     * @param leftButton
     */
    protected void up(boolean leftButton) {
        send("a=u&b=" + (leftButton ? "l" : "r"));
        downLastAction = false;
    }

    /**
     * send a hide request to the mouse control servlet that lets the mouse pointer disappear on the shared screen
     */
    protected void hide() {
        Scheduler.get().scheduleDeferred(new ScheduledCommand() {

            @Override
            public void execute() {
                send("a=h");
            }
        });
    }

    /**
     * @return the current x coordinate of the mouse pointer on the shared screen
     */
    protected abstract int getX();

    /**
     * @return the current y coordinate of the mouse pointer on the shared screen
     */
    protected abstract int getY();

    /**
     * Send a movement request to the mouse control servlet
     * 
     * @param x
     * @param y
     */
    protected void move(int x, int y) {
        if (screenDimension == null)
            send(null);
        else
            send("a=m&x=" + x + "&y=" + y);
    }

    /**
     * Delegator method to send a query to the mouse control servlet without callback
     * 
     * @param query
     */
    protected void send(String query) {
        send(query, null);
    }

    /**
     * Sends a request to the server to get the current status
     * 
     * @param callback
     */
    protected void getStatus(Command callback) {
        send(null, callback);
    }

    private boolean noConnection;

    /**
     * Sends the given query to the mouse pointer controller servlet
     * 
     * @param query
     * @param callback
     */
    protected void send(String query, final Command callback) {
        try {
            if (active) {

                new RequestBuilder(RequestBuilder.GET,
                        GWT.getHostPageBaseURL() + controlServlet + "?" + (query != null ? query : "a=x")
                                + (getCurrentClient() != null ? "&targetUUID=" + getCurrentClient() : "")
                                + (uuid != null ? "&uuid=" + uuid : "") + (host != null ? "&host=" + host : "")
                                + (port != null ? "&port=" + port : "") + ("&user=" + Authentication.getUserName()))
                                        .sendRequest(null, new RequestCallback() {

                                            @Override
                                            public void onResponseReceived(Request request, Response response) {
                                                if (response.getStatusCode() > 400)
                                                    onError(request, null);
                                                String color = extractColor(response);
                                                if (response.getText().trim().isEmpty()) {
                                                    label.setText("No connection available");
                                                    noConnection = true;
                                                } else {
                                                    if (noConnection) {
                                                        label.setText("");
                                                        noConnection = false;
                                                    }

                                                    if (color == null || color.isEmpty() || color.equals("#null"))
                                                        color = null;
                                                    extractLastAction(response);
                                                    setColor(color);
                                                    setScreenDimension(extractScreenDimensions(response));
                                                    if (callback != null)
                                                        callback.execute();
                                                }
                                            }

                                            @Override
                                            public void onError(Request request, Throwable exception) {
                                                setActive(false);
                                            }
                                        });
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * sets the mouse control active or inactive
     * 
     * @param active
     */
    private void setActive(boolean active) {
        this.active = active;
        if (!active) {
            this.getElement().getStyle().setBackgroundColor("grey");
            label.setText("Looking for available cursors... not found one so long.");
        } else {
            label.setText("");
        }
    }

    /**
     * Further actions when the dimensions of the shared screen change
     */
    protected abstract void updateScreenDimensions();

    /**
     * Adapts the widget to the given screen dimensions of the shared screen
     * 
     * @param dimension
     */
    private void setScreenDimension(String dimension) {
        if (dimension != null && (screenDimension == null || !screenDimension.equals(dimension))) {
            String[] values = dimension.split("x");
            if (values.length == 2) {
                screenWidth = Integer.parseInt(values[0]);
                screenHeight = Integer.parseInt(values[1]);
                updateScreenDimensions();
                screenDimension = dimension;
            }
        }
    }

    /**
     * @param resp
     * @return the screen dimensions of the shared screen separated by a "x"
     */
    private static String extractScreenDimensions(Response resp) {
        return extractByRegex(resp, "[0-9]*x[0-9]*");
    }

    /**
     * @param resp
     * @return the color of the assigned the mouse pointer
     */
    private static String extractColor(Response resp) {
        return extractByRegex(resp, "#.{6}");
    }

    protected String lastAction = null;

    /**
     * @param response
     *            - the last action that has been applied
     */
    private void extractLastAction(Response response) {
        String[] split = response.getText().split("@");
        if (split.length > 2) {
            if (!split[2].equals(lastAction)) {
                lastAction = split[2];
                onActionChanged(lastAction);
                GWT.log("Action changed: " + lastAction);
            }
        }
    }

    /**
     * Helper method to extract values from the HTTP-response by the given regular expression
     * 
     * @param response
     * @param regex
     * @return
     */
    private static String extractByRegex(Response response, String regex) {
        RegExp re = RegExp.compile(regex);
        MatchResult result = re.exec(response.getText());
        return result.getGroup(0);
    }

    /**
     * Sets the color of the font based on the brightness of the background color either to black or to white
     * 
     * @param color
     */
    protected void setColor(String color) {
        if (color != null) {
            color = color.trim();
            if (currentColor == null || !currentColor.equals(color)) {
                currentColor = color;
                this.getElement().getStyle().setBackgroundColor(color);
                // this.getElement().getStyle().setColor(getInvertedColor(color));
                this.getElement().getStyle().setColor(isColorBright(color) ? "#000000" : "#ffffff");
            }
        }
    }

    /**
     * @param color
     * @return true if the color brightness is above the average and false if not
     */
    protected boolean isColorBright(String color) {
        if (color != null) {
            if (color.startsWith("#") && color.length() == 7) {
                try {
                    Integer red = Integer.parseInt(color.substring(1, 3).toLowerCase(), 16);
                    Integer green = Integer.parseInt(color.substring(3, 5).toLowerCase(), 16);
                    Integer blue = Integer.parseInt(color.substring(5, 7).toLowerCase(), 16);
                    Double d = (red + green + blue) / 3.0;
                    // 128 is the average brightness (16/2)
                    return d > 128;
                } catch (NumberFormatException e) {
                    return true;
                }
            }
        }
        return true;
    }

    /**
     * Request the server for available shared devices
     * 
     * @param callback
     */
    private void getAvailableClients(final Command callback) {
        try {
            new RequestBuilder(RequestBuilder.GET, GWT.getHostPageBaseURL() + controlServlet + "?a=g")
                    .sendRequest(null, new RequestCallback() {

                        @Override
                        public void onResponseReceived(Request request, Response response) {
                            if (response.getText() != null && !response.getText().isEmpty()) {
                                availableClients = response.getText().split("\n");
                            }
                            if (callback != null)
                                callback.execute();
                        }

                        @Override
                        public void onError(Request request, Throwable exception) {
                            GWT.log("Available clients request", exception);
                            if (callback != null)
                                callback.execute();
                        }
                    });
        } catch (RequestException e) {
            GWT.log("Request Exception", e);
            if (callback != null)
                callback.execute();

        }
    }

    /**
     * @return the color of the mouse pointer / background
     */
    public String getColor() {
        return currentColor;
    }

    /**
     * @param color
     *            in HTML hexadecimal writing (e.g. "#000000");
     * @return the inverted color
     */
    protected String getInvertedColor(String color) {
        if (color != null) {
            if (color.startsWith("#")) {
                String hexString = color.substring(1);
                Integer i;
                Integer maxHex;
                try {
                    i = Integer.parseInt(hexString.toLowerCase(), 16);
                    maxHex = Integer.parseInt("ffffff", 16);
                } catch (NumberFormatException e) {
                    return "#000000";
                }
                String newHexString = Integer.toHexString(maxHex - i);
                return "#" + newHexString;
            }
        }
        return "#000000";
    }

    /**
     * @return true if the widget shall be attached to the root panel or false if it takes care about the attachment by itself
     */
    public boolean attachToRootPanel() {
        return false;
    }
}