/*
* Crea una ventana redimensionable en la que aparece un JSlider y un * JFormatterdTextField. El slider se utiliza para indicar la * frecuencia usando el ratón. El textField para indicarla * numéricamente. Ambos objetos están sincronizados, es decir, si * modificamos el estado de uno automáticamente se modifica el estado * del otro. * * Esta clase contiene además un thread que escucha un canal multicast * para conocer la frecuencia establecida en el servidor por otro * objeto FreqControl. Cuando se recibe una frecuencia distinta de la * actual, el slider y el textField son actualizados. * * La comunicación con el servidor se realiza mediante las clases * externas OutToServer y InFromServer. * * gse. 2006. */ /* Evento de manipulación de excepciones de entrada/salida. */ import java.io.IOException; /* En Java, un JFrame es una ventana independiente. */ import javax.swing.JFrame; /* Organiza objetos gráficos en una ventana. */ import javax.swing.JPanel; /* Un campo con texto que cuando se modifica genera un evento. */ import javax.swing.JFormattedTextField; /* Permite construir "potenciometros" lineales. */ import javax.swing.JSlider; /* Etiquetas de texto */ import javax.swing.JLabel; /* Permite organizar unos objetos gráficos con respecto a otros. */ import javax.swing.BoxLayout; /* Genera un evento cuando pulsamos una tecla. */ import javax.swing.KeyStroke; /* Una clase abstracta para los objetos que tratan con acciones. Las * clases abstractas no pueden ser instanciadas, sólo pueden ser * heredadas. */ import javax.swing.AbstractAction; /* Da un formato concreto a un número. Por ejemplo, esta clase se * puede utilizar para hacer que aparezca el punto del millar ... */ import javax.swing.text.NumberFormatter; /* Establece un borde alrededor de un objeto gráfico. */ import javax.swing.BorderFactory; /* Define un objeto que escucha los eventos de cambio. */ import javax.swing.event.ChangeListener; /* Se utiliza para avisar a otros objetos interesados en que ha * cambiado el estado en una fuente de eventos. */ import javax.swing.event.ChangeEvent; /* Espeficica que se ha ocurrido un evento en un componente. */ import java.awt.event.ActionEvent; /* Escucha a los eventos de ventana. */ import java.awt.event.WindowListener; /* Es una clase que contiene una serie de métodos independientes de la * plataforma (por ejemplo, el que genera un "beep"). */ import java.awt.Toolkit; /* Interface (una clase abstracta pura, sin métodos definidos) usado * para escribir manejadores de escucha de eventos. */ import java.beans.PropertyChangeListener; /* Objeto lanzado cuando se produce un cambio en las propiedades de un * objeto. */ import java.beans.PropertyChangeEvent; /* Un componente en Java es un objeto gráfico que puede interactuar * con el usuario. */ import java.awt.Component; /* El evento de ventana. */ import java.awt.event.WindowEvent; /* El evento de teclado. */ import java.awt.event.KeyEvent; /* Interface usado cuando deseamos escuchar los cambios de estado que * se producen en los componentes. */ import java.awt.event.ComponentListener; /* El evento de cambio del estado de una componente. */ import java.awt.event.ComponentEvent; public class FreqControl extends JPanel implements Runnable, WindowListener, ChangeListener, PropertyChangeListener, ComponentListener { /* Rango de frecuencias posible. */ static final int MIN_FREQ = 0; static final int MAX_FREQ = 22050; /* Dimensiones iniciales de la ventana. */ static final int WINDOW_WIDTH = 512; static final int WINDOW_HEIGHT = 128; JFormattedTextField textField; JSlider slider; /* La frecuencia leída del servidor. */ int freq; /* El canal TCP hacia el servidor. */ OutToServer outToServer; /* El canal multicast UDP desde el servidor. */ InFromServer inFromServer; /** * Constructor. Crea la ventana con todos los componentes y * establece los cockes de entrada y salida. */ public FreqControl(String serverName) throws IOException { /* Colocaremos el textField y el slider uno encima del otro. */ setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS)); /* Creamos el label del textField. */ JLabel textFieldLabel = new JLabel("Frecuencia (Hz): ", JLabel.CENTER); textFieldLabel.setAlignmentX(Component.CENTER_ALIGNMENT); /* Creamos el textField con su formato. */ java.text.NumberFormat numberFormat = java.text.NumberFormat.getIntegerInstance(); NumberFormatter formatter = new NumberFormatter(numberFormat); formatter.setMinimum(new Double((double)MIN_FREQ)); formatter.setMaximum(new Double((double)MAX_FREQ)); textField = new JFormattedTextField(formatter); textField.setValue(new Integer(MIN_FREQ)); textField.setColumns(4); textField.addPropertyChangeListener(this); /* El textField comprobará el valor introducido por teclado * una vez que hallamos pulsado la techa <Enter>. */ textField.getInputMap(). put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),"check"); textField.getActionMap().put("check", new AbstractAction() { public void actionPerformed(ActionEvent e) { if (!textField.isEditValid()) { // Texto inválido. Toolkit.getDefaultToolkit().beep(); textField.selectAll(); } else try { // Texto válido, textField.commitEdit(); // úsalo. } catch (java.text.ParseException exc) { } } }); /* Creamos el slider y definimos su apariencia. */ slider = new JSlider(JSlider.HORIZONTAL, MIN_FREQ, MAX_FREQ, MIN_FREQ); slider.addChangeListener(this); slider.setMajorTickSpacing(1000); slider.setMinorTickSpacing(1000); slider.setPaintTicks(true); slider.setPaintLabels(true); //slider.setSnapToTicks(true); slider.setBorder(BorderFactory.createEmptyBorder(0,0,10,0)); /* Redefinimos las etiquetas del slider. */ java.util.Dictionary labelTable = slider.getLabelTable(); JLabel[] l = new JLabel[(MAX_FREQ-MIN_FREQ)/1000+1]; { int i; for(i=MIN_FREQ; i<MAX_FREQ; i+= 1000) { l[i/1000] = new JLabel(i/1000 + ""); labelTable.put(new Integer(i), l[i/1000]); } } slider.setLabelTable(labelTable); /*java.util.Hashtable labelTable = new java.util.Hashtable(); JLabel[] l = new JLabel[(MAX_FREQ-MIN_FREQ)/1000+1]; { int i; for(i=MIN_FREQ; i<MAX_FREQ; i+= 1000) { l[i/1000] = new JLabel(i/1000 + ""); labelTable.put(new Integer(i), l[i/1000]); } } slider.setLabelTable(labelTable);*/ //slider.setPaintLabels(true); /* Creamos una estructura subpanel para el textField y su * etiqueta. */ JPanel labelAndTextField = new JPanel(); labelAndTextField.add(textFieldLabel); labelAndTextField.add(textField); /* Insertamos en la ventana "FreqControl" el anterior JPanel y * el slider. */ add(labelAndTextField); add(slider); /* Distancia del contenido de la ventana a sus bordes. */ setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); /* Queremos una ventana "bonita" con la decoración y * comportamiento por defecto en Java. */ JFrame.setDefaultLookAndFeelDecorated(true); /* Creamos la ventana y establecemos su tamaño. */ JFrame frame = new JFrame("FreqControl"); /* Le indicamos el tamaño. */ frame.setPreferredSize(new java.awt.Dimension(WINDOW_WIDTH, WINDOW_HEIGHT)); /* Espeficicamos lo que hace cuando lo cerramos: finalizar. */ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); /* Insertamos el "pane" en la ventana actual. Un pane es la * estructura de objetos creada cuando hemos ido añadiendo los * diferentes objetos usando el método "add()". */ //this.setOpaque(true); //content panes must be opaque frame.setContentPane(this); /* Hace que la ventana tenga el tamaño especificado por el * método "setPreferredSize()". */ frame.pack(); /* Mostramos la ventana. */ frame.setVisible(true); /* Nos conectamos al servidor */ outToServer = new OutToServer(serverName); inFromServer = new InFromServer(); /* Hacemos que la ventana escuche a los eventos de ventana. */ frame.addWindowListener(this); /* Hacemos que la ventana escuche a los eventos provocamos por * sus componentes. */ frame.addComponentListener(this); /* Creamos y lanzamos el hilo que está pendiente de las * frecuencias especificadas por los demás clientes. */ new Thread(this).start(); } /** * Este método es el que ejecuta el hilo que escucha las * frecuencias de los demás clientes. Básicamente es un lazo sin * fin que espera a recibir un paquete del canal multicast. Cuando * lo recibe extrae de él la frecuencia y actualiza la variable * global del método "freq". */ public void run() { for(;;) { try { freq = inFromServer.receive(); } catch (IOException e) { System.out.println("FreqControl: run: unable to receive data"); } //System.out.println("freq = " + freq); /* Cuando recibimos una frecuencia actualizamos el campo * textField. */ textField.setValue(new Integer(freq)); } } /** * Método que se invoca cuando la ventana se abre. */ public void windowOpened(WindowEvent e) {} /** * Método que se invoca cuando la ventana se iconiza. */ public void windowIconified(WindowEvent e) {} /** * Método que se invoca cuando la ventana se des-iconiza. */ public void windowDeiconified(WindowEvent e) {} /** * Método que se invoca cuando la ventana se activa (cuando se * selecciona). */ public void windowActivated(WindowEvent e) {} /** * Método que se invoca cuando la ventana se des-activa. */ public void windowDeactivated(WindowEvent e) {} /** * Método que se invoca cuando la ventana se va cerrar. */ public void windowClosing(WindowEvent e) { try { /* Enviamos una frecuencia negativa indicando al servidor * que desemoa cerrar la conexión TCP. */ outToServer.sendString("-1\n"); } catch (IOException ioe) { System.out.println("FreqControl::windowClosing: unable to send data"); } try { /* Abandonamos el grupo multicast. */ inFromServer.leaveGroup(); } catch (IOException ioe) { System.out.println("FreqControl::windowClosing: unable to leave the multicast group"); } /* Cerramos el socket de recepción (canal multicast). */ inFromServer.close(); } /** * Método que se invoca cuando la ventana ya se ha cerrado. */ public void windowClosed(WindowEvent e) {} /** * Método que se invoca cuando el slider cambia de estado. */ public void stateChanged(ChangeEvent e) { JSlider source = (JSlider)e.getSource(); int local_freq = (int)source.getValue(); if (!source.getValueIsAdjusting()) { // done adjusting textField.setValue(new Integer(local_freq)); } else { //value is adjusting; just set the text textField.setText(String.valueOf(local_freq)); } /* Si la frecuencia leída a través del canal multicast es * diferente de la que indica el slider, es que estamos * interactuando con el slider. Por tanto enviamos la que * espeficica este hacia al servidor. */ if(freq!=local_freq) { try { //System.out.println(freq + " " + local_freq); outToServer.sendString(String.valueOf(local_freq) + ’\n’); } catch (IOException ioe) { System.out.println("FreqControl::stateChanged: unable to send data"); } } } /** * Escucha al textField. Este método detecta cuando el valor del * texField cambia (por el motivo que sea). */ public void propertyChange(PropertyChangeEvent e) { if ("value".equals(e.getPropertyName())) { Number value = (Number)e.getNewValue(); if (slider != null && value != null) { slider.setValue(value.intValue()); } } } /** * Método invocado si los componentes de la ventana son escondidos. */ public void componentHidden(ComponentEvent e) {} /** * Método que se invoca si los componentes de la ventana son * movidos. */ public void componentMoved(ComponentEvent e) {} /** * Método invocado cuando los componentes cambian de tamaño. */ public void componentResized(ComponentEvent e) {} /** * Método invocado cuando los componentes son mostrados. */ public void componentShown(ComponentEvent e) {} /** * El método main() es el primero que se ejecuta cuando se lanza * un objeto de la clase FreqControl. Su función principal es la * de tratar los argumentos proporcionados desde el shell a través * de la línea de comandos y de crear el objeto en cuestión. */ public static void main(String args[]) throws Exception { if(args.length <1) { System.out.println("Uso: java FreqControl servidor"); } else { new FreqControl(args[0]); } } } /* Manipula los paquetes UDP. */ import java.net.DatagramPacket; /* Socket Multicast. */ import java.net.MulticastSocket; /* Evento de manipulación de excepciones de entrada/salida. */ import java.io.IOException; /* Manipula las direcciones IP. */ import java.net.InetAddress; public class InFromServer { /* Puerto de entrada de los paquetes. */ static final int PORT = 6789; /* Socket Multicast para leer del servidor. */ MulticastSocket inFromServer; /* Paquete UDP leído del servidor. */ DatagramPacket receivedPacket; /* Dir IP del grupo multicast. */ InetAddress mCastIA; /* Constructor. */ public InFromServer() throws IOException { /* Crea un socket para recibir paquetes UDP. */ inFromServer = new MulticastSocket(PORT); /* Obtenemos la dir IP del grupo multicast. */ mCastIA = InetAddress.getByName("224.0.0.1"); /* Nos unimos al grupo multicast. */ inFromServer.joinGroup(mCastIA); /* Creamos el paquete UDP que recibiremos del servidor. */ receivedPacket = new DatagramPacket(new byte[2], 2); } /* Leemos la frecuencia. */ public int receive() throws IOException { try { inFromServer.receive(receivedPacket); } catch (IOException e) { System.out.println("InFromServer: receive: unable to receive data"); throw new IOException(); } int freq = ((int)receivedPacket.getData()[0] & 0xFF) * 256 + ((int)receivedPacket.getData()[1] & 0xFF); return freq; } /* Dejamos el grupo multicast. */ public void leaveGroup() throws IOException { try { inFromServer.leaveGroup(mCastIA); } catch (IOException ioe) { System.out.println("InFromServer: leaveGroup: unable to leave the multicast group"); throw new IOException(); } } /* Cerramos el socket. */ public void close() { inFromServer.close(); } } /* * Envía la frecuencia al servidor a través de un socket TCP. */ /* Permite escribir tipos de datos primitivos (byte, int, float, * ...). */ import java.io.DataOutputStream; /* Puerto de comunicación entre procesos. */ import java.net.Socket; /* Evento de manipulación de excepciones de entrada/salida. */ import java.io.IOException; /* Evento lanzado cuando nos conectamos a un host inexistente. */ import java.net.UnknownHostException; public class OutToServer { /* Flujo hacia el servidor. */ DataOutputStream outToServer; /* Puerto de escucha del servidor. */ static final int PORT = 6789; /* Constructor. */ public OutToServer(String serverName) throws UnknownHostException, IOException { Socket clientSocket = null; /* Consulta al DNS y establece una conexión TCP. */ try { clientSocket = new Socket(serverName, PORT); } catch (UnknownHostException e) { System.out.println("OutToServer: unknown host " + "\"" + serverName + "\""); throw new UnknownHostException(); } /* Conectamos clientSocket con outToServer. */ try { outToServer = new DataOutputStream(clientSocket.getOutputStream()); } catch (IOException e) { System.out.println("OutToServer: unable to connect to " + "\"" + serverName + "\""); throw new IOException(); } } /* Envía una cadena al servidor. */ public void sendString(String s) throws IOException { try { outToServer.writeBytes(s); } catch (IOException ioe) { System.out.println("OutToServer: unable to sendString" + "\"" + s + "\""); throw new IOException(); } } } |