28 agosto, 2009

Eclipse v/s Netbeans . . . Introduccion

Una disputa de nunca acabar. Una guerra santa mas
en el universo del software.

Cuando no es Windows v/s Linux, Kde v/s Gnome, GTK v/s QT. Siempre hay un buen motivo para pelear. Y como no se puede ser menos, tambien hay bandos que luchan a muerte por imponer al bando contrario la idea de que su IDE favorito es el mejor.

No pretendo aqui decantarme por un bando en particular. He usado ambos IDE en los ultimos 6
meses, lo suficiente como para dar una opinion objetiva y sin cegueras causadas por el fanatismo. Cada IDE tiene sus pro y sus contra, y la decision final dependera de las necesidades y el gusto personal de cada uno.

Preparando el terreno:

Antes de comenzar, quiero poner mis piezas sobre el tablero
para que asi se entienda en cierta manera mi perspectiva:


  • Llevo casi 3 años programando en Java, 11 años usando Linux, y trabaje 5 años como administrador de redes antes de programar.

  • Cuando aprendi Java (hice un curso), fue con Eclipse. Mis primeros pasos en Java fueron con Eclipse.

  • Durante 1 año y medio aprox trabaje exclusivamente con NetBeans, desarrollando en J2EE, Struts y Swing.

  • Los ultimos 6 meses han sido simultaneamente en Eclipse y Netbeans dependiendo del problema a enfrentar. Ha sido trabajando en una aplicacion Swing con base de datos Postgres


Durate los proximos meses ire escribiendo acerca de mis impresiones, las cuales emanan desde una prespectiva de usuario de la aplicacion, el cual debe lidiar con factores como la configuracion, ayuda, herramientas disponibles, extensiones, bugs, etc. Factores que finalmente influyen en algo tan trascendental como es la productividad de quien utiliza estas herramientas.

No pretendo hacer una comparación demasiado acuciosa, sino más bien que estas líneas y sus futuras extensiones sirvan de orientación al usuario neófito que debe decidir por cual herramienta decantarse.


Let's dance!

21 agosto, 2009

AudioCopy 0.1


AudioCopy es una sencilla herramienta para copiar y transferir archivos de audio
desde una carpeta a otra.

A diferencia de los complementos y plugins de algunos reproductores de musica,
AudioCopy tiene algunas ventajas:


  • Copia hasta 4 archivos simultaneamente (por defecto 2)

  • Permite pausar las copias

  • Es ligero y consume pocos recursos del computador.

  • 100% Java. Puede ser ejecutado en cualquier computador con Java 6 o superior

  • Codigo Libre. Puedes modificar, contribuir o simplemente curiosear respetando
    la licencia. Tambies puedes compartirlo con quien quieras.

  • Facil de usar. Su interfaz es simple y no tiene menus extravagantes o atajos
    dificiles de aprender.


Caracteristicas de la version actual 0.1

  • Hasta 4 copias simultaneas

  • Pausado de copia.

  • Agregar nuevas pistas a la cola mientras copia.

  • Crea carpetas destino segun tags de archivos de audio

  • Licencia GPL V2



  artista/año - album/pista - titulo.extension
  Depeche Mode/1997 - Ultra/01 - Barrel of a Gun.mp3

Requisitos:

  • Java 1.6 o superior



Errores conocidos en la version actual.

  • Falla con carpetas de musica demasiado grande

  • Se puede cambiar la carpeta origen durante la exploracion, lo que da problemas

  • No manipula bien los errores durante la copia (disco lleno, etc.)


Mejoras proximas:

  • Corregir errores conocidos

  • Acelerar la exploracion.

  • Recordar carpetas origen (con contenido) y destino.

  • Cancelar la copia

  • Seleccionr codificacion (ISO/UTF) segun filesystem de carpeta destino



Posiblemente en un futuro distante:

  • Tooltips y ayudas contextuales

  • Informar progreso de copias individuales.

  • Editor de tags

  • Detectar automaticamente unidades USB

  • ¿Reproductor? ... por ahora me quedo con Jajuk



La version actual puede obtenerse aqui

19 agosto, 2009

Control y pausa de ciclos for, do, while en Java

Un problema que sucede a menudo cuando se necesita procesar gran cantidad de informacion en un ciclo for o do/while es el de pausar el ciclo, esperar alguna señal y continuar.

for (condicion) {
    ....hacer algo...
    ....esperar....
    ....continuar.....
}

Por ejemplo, un problema al que me vi enfrentado fue el de ingresar el precio para una gran cantidad de productos, teniendo los productos en un Collection, parecia facil iterar sobre este listado, mostrar una ventana para que el usuario ingresara el precio y continuar:

Collection productos;
for (Producto p : productos) {
    ingresarPrecio(p);
}


A primera vista se ve simple, pero si este codigo es ejecutado, su resultado sera abrir tantas ventanas de ingreso como productos tenga el Collection.

La forma de ir mostrando las ventanas de forma ordenada, es pausar el ciclo for hasta que la ventana genere algun evento. Para ello lo primero sera hacer correr el ciclo dentro de un Thread diferente del actual de forma que pueda ser controlado sin interrumpir la ejecucion del programa:
Runnable hiloFor = new Runnable() {

    public void run() {
        for (Producto p : productos) {
            ingresarPrecio(p);
        }
    }
}

new Thread(hiloFor).start;


Esto lanzara nuestro ciclo for en un hilo distinto del actual, abriendonos la posibilidad de control. Para ello utilizaremos los metodos proporcionados por la clase Thread.

Lo que haremos a continuacion sera sincronizar el Thread donde vive nuestro ciclo for con el Thread principal donde se enta ejecutando nuestra aplicacion (en el caso de una aplicacion Swing sera el Event dispatching thread
Collection productos;
Thread hilo;

Runnable hiloFor = new Runnable() {

    public void run() {
        Thread actual = Thread.currentThread();
            synchronized(actual) {
                for (Producto p: Productos) {
                    ingresarPrecio(p);
                    actual.wait();
                }
            }
        }
}

hilo = new Thread(hiloFor);
hilo.start();


Puede notarse de inmediato los cambios introducidos: Primero obtenemos el objeto Thread donde se ejecuta nuestro for y luego ejecutamos el ciclo dentro de un bloque sincronizado con este hilo, invocamos el metodo que abre la ventana y pausamos la ejecucion del hilo.

En este punto el hilo donde se ejecuta el ciclo for estara pausado de forma indefinida esperando la señal notify(). Luego el metodo ingresarPrecio(Producto p) debera:


  • Abrir la ventana para ingresar el precio en el event dispatching thread

  • Notificar al hilo que puede continuar una vez se haya hecho el ingreso


Para ello, bastara que el metodo ingresarPrecio(Producto p) incluya en su interior algo similar a:


ingresarPrecio(final Producto p) {
    Runnable abrir = new Runnable() {

        public void run() {
            VentanaIngreso v = new VentanaIngreso(p);
            v.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    continuar();
                }
            });

        }

        private void continuar() {
            synchronized (hilo) {
                hilo.notify();
            }
        }
    }

    SwingUtilities.invokeLater(abrir);
}


La parte importante aca es el metodo continuar(). Aqui se le dice al hilo que continue ejecutandose. Como puede verse en este sencillo ejemplo, este metodo se llama desde un ActionListener que sera gatillado dentro de la ventana de ingreso ante ciertas acciones (cancelar, grabar el precio, etc).

El ejemplo puede mejorarse y extenderse a otras situaciones.


Bytes!

13 agosto, 2009

Control de Look and Feel en Swing

Un problema recurrente de los usuarios de aplicaciones
creadas en Java es el del look an feel. "No se ve como
si fuera windows" es el tipico comentario de quien usa
una aplicacion Swing en este sistema operativo. Los
usuarios de *nix estan mas acostumbrados a las diferencias
de l&f segun el toolkit de la aplicacion (gtk+, qt, motif.. etc)
y no suelen quejarse tanto.

Personalmente encuentro horrible el l&f por defecto (metal)
asi que tengo a mano un codigo que selecciona automaticamente
el l&f dependiendo del sistema:


----------

String jVersion = System.getProperty("java.version");
String os = System.getProperty("os.name");
String[] ver = jVersion.split("_");



if (ver.length > 0) {
    String vMajor = ver[0];
    Double update = Double.parseDouble(ver[1]);
    try {
       if ((vMajor.compareTo("1.6.0") > -1 && update >= 10) || (vMajor.compareTo("1.7.0") > -1)) {
           UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
       } else {
           if (os.toLowerCase().contains("windows")) {
           UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
       } else if (os.toLowerCase().contains("linux")) {
           UIManager.setLookAndFeel("com.sun.java.swing.plaf.gtk.GTKLookAndFeel");
       }
    } catch (ClassNotFoundException ex) {
        Logger.getLogger(Main.class.getName()).log(Level.WARNING, bundle.getString("error.lf"), ex);
    } catch (InstantiationException ex) {
        Logger.getLogger(Main.class.getName()).log(Level.WARNING, bundle.getString("error.lf"), ex);
    } catch (IllegalAccessException ex) {
        Logger.getLogger(Main.class.getName()).log(Level.WARNING, bundle.getString("error.lf"), ex);
    } catch (UnsupportedLookAndFeelException ex) {
        Logger.getLogger(Main.class.getName()).log(Level.WARNING, bundle.getString("error.lf"), ex);
    }
}


----------

El codigo en si es bastante simple:

- Obtengo la version de la VM de java.
- Obtengo el nombre del sistema operativo.
- Obtengo el numero de actualizacion de la VM.

Desde el update 10 de la JVM 1.6 se encuentra disponible
el l&f "Nimbus", asi que si nuestra version de JVM es
1.6_10 o superior se selecciona este L&F para la
aplicacion. En caso contrario si el sistema es Windows
se selecciona el L&F de Windows, si el OS es Linux
se selecciona el L&F GTK (como quisiera un l&f QT!!!).

Si no es posible seleccionar alguno de estos L&F se
mantendra el por defecto (metal).

Bastara con que este codigo se inserte en el main() de
nuestra aplicacion Swing, antes que se dibuje el primer
JFrame y lo demas sera coser y cantar.

Esta solucion es muy simple y utiliza las bibliotecas
estandar de la JVM. No obstante podemos extender
las capacidades de L&F de nuestra aplicacion utilizando
la completisima biblioteca substance.




bytes!!

12 agosto, 2009

Moviendo un JScrollPane de forma automatica

Uno de los problemas clasicos al tener un JPanel dentro
de un JScrollPane es como mover automaticamente la
barra de Scroll del JScrollPane. Para ello existe el metodo
scrollRectToVisible(Rectangle r) que hara que el o los
ScrollBar se desplazen para hacer que el rectangulo indicado
sea visible.

Hasta aqui la cosa no es muy complicada, pero ¿que ocurre
si dentro de nuestro JPanel tenemos uno o mas JPanel
anidados? La cosa tiende a complicarse un poco ya
que la posicion final de nuestro componente es desconocida
para el JScrollPane.

Consideremos el siguiente ejemplo:


--------------------
JScrollPane scrPane = new JScrollPane();
JPanel panelPrincipal = new JPanel();
JPanel panelAnidado1 = new JPanel();
JPanel panelAnidado2 = new JPanel();

JLabel label = new JLabel("....");
JTextField txt = new JTextField("......")M

panelAnidado2.add(txt);
panelAnidado2.add(label)

panelAnidado1.add(panelAnidado2);
panelPrincipal.add(panelAnidado1)

scrPane.setViewportView(panelPrincipal);
----------------------


Si queremos desplazar el JScrollPane hasta la posicion del
label o el txt, la primera idea que se nos viene a la cabeza
seria:


-------
scrPane.scrollRectToVisible(label.getBounds());
-------


Lamentablemente esto no funcionara ya que scrPane solo
"sabe de la existencia" de panelPrincipal e ignora su contenido.
Por lo tanto lo que debemos hacer es calcular la posicion
relativa de label con respecto a panelPrincipal.

Para ello, el siguiente codigo de ejemplo puede ser util;



-------------

//obtengo el padre del label (panelAnidado2)
JPanel padre = (JPanel) label.getParent();
//Obtengo la posicion del padre
Point posPadre = padre.getLocation();
//Obtengo el 'padre del padre' (panelAnidado1) y su posicion
JPanel padre2 = (JPanel)
Point posPadre2 = padre2.getLocation();

//Conociendo las posiciones, puedo hacer el calculo
Double y = posPadre2.getY() + posPadre1.getY() + label.getLocation.getY();

 /*
  * la variable y contiene la distancia desde el borde del
  * panelAnidado1 hasta el borde del label
  */
//Creo un punto en la posicon relativa del Label

Point relativo = new Point(new Double(posPadre2.getX()).intValue(), y.intValue());

//Con este punto relativo puedo crear un rectangulo y desplazar el JScrollPane

Rectangle moverA = new Rectangle(relativo, label.getPreferredSize());
scrPane.scrollRectToVisible(moverA);

---------------


Resumen:

Como 'panelPrincipal' desconoce la presencia y posicon
de label, calculamos la posicion relativa de label con
respecto a 'panelPrincipal' en base a la posicion de su padre
y cualquier panel previo. Una vez conocida esta posicion
relativa, basta desplazar el scrPane hasta un rectangulo
ubicado en esta posicion relativa con el tamaño del
componente que queremos enfocar.


Agregando este codigo a un FocusListener, es facil
hacer que el JScrollPane se desplace hasta la posicion
del componente que obtiene el foco:

Ejemplo:


--------------


private JScrollPane scroll;
private Jpanel panel;
private FocusListener focoDesplazamiento = new FocusAdapter() {

@Override
public void focusGained(FocusEvent e) {
    Point posicionScroll = scroll.getLocation();
   
    Container parent = e.getComponent().getParent();
    int y = posicionOpcional.y + parent.getLocation().y;
    Point moverA = new Point(posicionScroll.x, y + parent.getHeight());

    panel.scrollRectToVisible(new Rectangle(moverA, parent.getPreferredSize()));
    }
};








....



  
  /*
   * En alguna parte del codigo, hacemos que el JPanel panel este contenido
   * dentro del JScrollPane scroll

   *
   */


   panel = new JPanel();
   scroll = new JScrollPane(panel);


   /*
    * Luego, a todos los componentes que se agregan a panel se les agrega
    * nuestro FocusListener
    */


    JTextField txt1 = new JTextField();
    txt1.addFocusListener(focoDesplazamiento);
    panel.add(txt1);
    
    ...


    //y asi sucesivamente.
}

--------------


bytes!