Universidad Carlos III de Madrid

Grado en Ingeniería Telemática/Grado en Ingeniería de Sistemas de Comunicaciones

Enero-Mayo 2014

Interfaces Gráficas de Usuario

Lab Section1. Sesión 4 (laboratorio): Interfaces Gráficas de Usuario (II)

Exercise Section1.1. Calculadora en Swing, ahora con eventos

En este ejercicio debes finalizar la calculadora que empezaste a programar en la práctica anterior. Utiliza tu solución a dicha práctica como punto de partida. Si no tienes el código a mano, utiliza la solución oficial que se ha publicado para dicho ejercicio.

Para simplificar el problema, esta versión de la calculadora sólo debe trabajar con números enteros. Por tanto, deshabilita en la interfaz el botón ".", de tal forma que no sea pinchable.

Se te proporciona la lógica interna de la calculadora ya programada en la clase CalculatorLogic que se muestra a continuación, pero necesitarás enlazarla adecuadamente con tu interfaz gráfica. Esta clase dispone de métodos públicos para que, cada vez que el usuario pulse un botón, se lo comuniques. Además, tras comunicar cada pulsación debes invocar al método getDisplay(), que te indicará qué debes mostrar en la pantalla de la calculadora.

/**
 * Class that implements the logic of a basic calculator for integer
 * numbers.
 *
 */
public class CalculatorLogic {
    public static final int OPERATOR_NONE = 0;
    public static final int OPERATOR_SUM = 1;
    public static final int OPERATOR_SUB = 2;
    public static final int OPERATOR_MUL = 3;
    public static final int OPERATOR_DIV = 4;

    private int accumulator;
    private int currentOperand;
    private int currentOperator;
    private boolean operandAvailable;

    /**
     * Creates a new instance of the calculator logic. In its initial
     * state the calculator displays just a zero, the value of its
     * internal accumulator is also zero and there is no operator
     * pending to be applied.
     *
     */
    public CalculatorLogic() {
        pressReset();
    }

    /**
     * Returns the information that should be shown at the display of
     * the calculator for its current state.
     *
     * @return The information to be shown at the display.
     *
     */
    public String getDisplay() {
        String display;
        if (operandAvailable) {
            display = Integer.toString(currentOperand);
        } else {
            display = Integer.toString(accumulator);
        }
        return display;
    }

    /**
     * Notifies that the user has pressed a button with a digit.
     *
     * @param digit The digit the user has pressed (0 to 9).
     *
     */
    public void pressDigit(int digit) {
        currentOperand = currentOperand * 10 + digit;
        operandAvailable = true;
    }

    /**
     * Notifies that the user has pressed a button with an operand.
     *
     * @param operator The operand expressed with one of the constants
     *                 OPERATOR_ADD, OPERATOR_SUB, OPERATOR_MUL or
     *                 OPERATOR_DIV.
     *
     */
    public void pressOperator(int operator) {
        if (operandAvailable) {
            performPendingOperation();
        }
        expectNewOperand(operator);
    }

    /**
     * Notifies that the user has pressed the "=" button.
     *
     */
    public void pressEqual() {
        if (operandAvailable) {
            performPendingOperation();
        }
        expectNewOperand(OPERATOR_NONE);
    }

    /**
     * Notifies that the user has pressed the "C" button, which removes
     * the last digit that was typed by the user.
     *
     */
    public void pressDelete() {
        currentOperand /= 10;
        if (currentOperand == 0) {
            operandAvailable = false;
        }
    }

    /**
     * Notifies that the user has pressed the "AC" button, which
     * resets the state of the calculator to its initial state
     * (display 0, accumulator 0, no pending operators.)
     *
     */
    public void pressReset() {
        accumulator = 0;
        expectNewOperand(OPERATOR_NONE);
    }

    private void expectNewOperand(int operator) {
        currentOperator = operator;
        currentOperand = 0;
        operandAvailable = false;
    }

    private void performPendingOperation() {
        switch (currentOperator) {
        case OPERATOR_NONE:
            accumulator = currentOperand;
            break;
        case OPERATOR_SUM:
            accumulator += currentOperand;
            break;
        case OPERATOR_SUB:
            accumulator -= currentOperand;
            break;
        case OPERATOR_MUL:
            accumulator *= currentOperand;
            break;
        case OPERATOR_DIV:
            accumulator /= currentOperand;
            break;
        }
    }
}

Exercise Section1.2. Seguimiento del cursor

En este ejercicio programarás una interfaz gráfica en que los ojos de un muñeco deben seguir la posición del cursor cuando este se encuentre dentro de la ventana. El programa debe tener una apariencia similar a la siguiente:

El contorno exterior del ojo se está dibujando como un círculo de radio 60. La pupila tiene radio 10. Su posición depende de la posición del cursor dentro de la ventana: debe estar siempre sobre la recta que une el cursor con el centro del ojo, a exactamente 30 píxeles de este último. A continuación se muestra un ejemplo:

Como puedes observar en el esquema que se muestra a continuación, si se conocen las coordenadas del centro del ojo (O), las coordenadas del cursor (C) y la distancia d = 30 píxeles, calcular la posición en que se debe situar la pupila (X) consiste en resolver un problema de semejanza de triángulos rectángulos:

Apartado 1

En primer lugar, escribe el código necesario para que la aplicación funcione con respecto a un punto fijo. Esto es, en vez de seguir el cursor, las pupilas se alinearán con respecto a un punto fijo de prueba que definas. De esta forma, no necesitarás manejar eventos por el momento.

Puedes basarte en el código del ejercicio de dibujo de la práctica anterior. Para hacerte más fácil el dibujo de círculos, puedes añadir a tu código el método siguiente, que recibe las coordenadas del centro del círculo, su radio, el color a usar y un parámetro booleano que indica si se debe pintar el relleno o el borde. Para pintar un círculo con relleno blanco y borde negro, pinta primero un círculo relleno de color blanco y después un círculo sin relleno de color negro.

    private void drawCircle(Graphics g, int x, int y, int radius, Color color,
                            boolean filled) {
        int posX = x - radius;
        int posY = y - radius;
        g.setColor(color);
        if (filled) {
            g.fillOval(posX, posY, 2 * radius, 2 * radius);
        } else {
            g.drawOval(posX, posY, 2 * radius, 2 * radius);
        }
    }

Prueba la aplicación con distintos puntos fijos.

Apartado 2

Ahora debes hacer que los ojos sigan la posición del cursor. Para ello, debes hacer que tu panel de dibujo ejerza de escuchador MouseMotionListener para él mismo. Esto es, desde el propio panel debes manejar los eventos que se producen cuando se mueve el cursor en el interior del panel.

Cada vez que detectes que se ha movido el cursor debes recoger las nuevas coordenadas del mismo y forzar que se actualice el dibujo invocando al método repaint() que tiene el panel. Cuando invocas a este método, estás pidiendo al panel que se vuelva a dibujar. Como parte de dicha operación de dibujo el panel volverá a invocar al método paintComponent() que has programado, que debe hacer de nuevo el dibujo teniendo en cuenta la nueva posición del cursor.

Apartado 3

Ahora queremos que, cada vez que el usuario pulse el botón izquierdo del ratón, el programa conmute entre dos modos. En el primer modo hace seguimiento del ratón como en el apartado anterior. En el segundo modo no hace seguimiento del ratón, y las pupilas se colocan en posición de reposo en el centro del ojo.

Para detectar cuándo el usuario presiona un botón del ratón debes hacer que tu panel también ejerza de MouseListener para él mismo.

Apartado 4

Ahora queremos que, cuando el cursor abandone el área del panel, el muñeco muestre una cara de enfado como se ve en la imagen siguiente, y que recupere la sonrisa cuando el cursor vuelva a entrar. Examina la API de MouseListener para saber cómo detectar cuándo entra o sale el cursor.

Homework Section2. Actividades para casa

Exercise Section2.1. Tres En Raya

En este ejercicio vamos a utilizar un juego conocido, el "Tres en Raya", como ejemplo de Interfaz Gráfica de Usuario (GUI) que integra la correspondiente Gestión de Eventos.

Apartado 1

Implementa en Java el tablero de un tres en raya, tal y como se presenta en la figura. Para ello crea una clase TicTacToe y haz uso de las clases JFrame, JPanel , JLabel y JButton del paquete javax.swing y de las clases BorderLayout y GridLayout del paquete java.awt. Incluye un atributo denominado player en tu clase para mantener el turno del jugador e implementa los métodos get y set correspondientes. Finalmente, implementa un método main que te permita probar esta clase

Apartado 2

Implementa en Java una clase TicTacToeListener que recoja los eventos de los usuarios al pulsar los botones que forman el tablero del tres en raya. Esta clase implementará la interfaz ActionListener y tendrá dos atributos, la instancia de la clase principal TicTacToe a usar, porque necesitarás acceder a sus métodos desde el escuchador, y la instancia de la etiqueta en que se indica quién es el siguiente jugador. El constructor de esta clase se encargará de inicializar estos dos atributos.

El método actionPerformed que tendrás que implementar en esta clase debe incluir la lógica siguiente:

  • Deshabilitar el botón que fue pulsado

  • Mostrar una "X" si el jugador 1 pulsó el botón y una "O" si el jugador 2 pulsó el botón.

  • Actualizar el valor de player para cambiar el turno de juego

  • Actualizar la etiqueta del tablero para mostrar el jugador que tiene el turno y su símbolo

Apartado 3

Implementa en la clase TicTacToe el método boolean isWinner(String value) que compruebe si un jugador ha ganado a partir del valor de su símbolo. Ten en cuenta todas las posibles combinaciones del tres en raya para que exista un ganador.

Invoca a este método desde actionPerformed. En caso de que algún usuario sea ganador muestra un mensaje por pantalla utilizando la clase JOptionPane.