« Noviembre 2004 | Inicio | Enero 2005 »

Diciembre 30, 2004

El hombre del aÒo

"... el hombre del aÒo es el que cava una zanja m·s en el portal de mi casa, el que muere sin alcanzar la costa de EspaÒa donde espera lo que no hay".

No, yo no lo he escrito, ha sido, una vez m·s, D. Eduardo Haro Tecglen.

Si quieres leer el texto completo: sigue el link.

Y si el texto no te conmueve, eriza el vello, o te hace soltar una lagrimita, tal vez deberÌas replantarte unas cuantas cosas....

Diciembre 14, 2004

°Participa en el concurso de aplicaciones para FlashLite!

Probablemente ya lo hayas oÌdo ( mejor dicho, leÌdo ), pero Macromedia ha convocado un concurso para decidir cu·l es la mejor aplicaciÛn para FlashLite. °Y hay buenos premios!

Los detalles sobre el concurso est·n aquÌ:

http://www.macromedia.com/mobile/special/contest/

Obviamente, necesitar·s el player de Flash Lite para probar tu aplicaciÛn. Puedes pedirlo enviando un email a flashlite_contest@macromedia.com, invluyendo el modelo del dispositivo y el IMEI del mismo.

Si quieres saber si tu dispositivo est· soportado, aquÌ tienes la lista:

http://www.macromedia.com/mobile/supported_devices/#tmobile

°Corre!. °El plazo de presentaciÛn termina el 1 de febrero!.

Diciembre 13, 2004

[PPC] AplicaciÛn del modelo-vista-controlador a una aplicaciÛn para PocketPC

Vamos a construir una aplicaciÛn muy sencilla, pero que puede ser un buen ejemplo de cÛmo aplicar el paradigma del modelo-vista-controlador a una aplicaciÛn para PocketPC. Y adem·s, utilizando la nueva clase Delegate.

Pues el caso es que habÌa quedado con unos amigos para jugar al scalextric, pero alguien se acordÛ de que el cuentavueltas no funcionaba, asÌ que pensÈ: "podemos usar un l·piz y un papel, seguro, pero °Èste es un trabajo para mi pocket pc!".

AsÌ que hice un cuentavueltas. …ste es el resultado final:

lapcounter_ok.jpg

La aplicaciÛn es muy simple. Pero la he implementado utilizando el modelo-vista-controlador, y por probar, usando los componentes de IU est·ndares de flash. No he utilizado los propios del PocketPC CDK porque, aunque ya ha pasado un aÒo desde la ˙ltima aplicaciÛn que hice para PocketPC, todavÌa no se me ha pasado el susto. Dicho de otro modo, no es que sean demasiado eficaces.

Antes de comenzar, una pequeÒa nota al margen. Utilizo mi propio sistema de eventos. Ya lo he utilizado con anterioridad en otros posts, pero lo he incluÌdo en la descarga con las fuentes de esta aplicaciÛn. B·sicamente, consiste en una clase base que deben extender todas las que sean emisoras de eventos ( EventSource ), y que implementa un interfaz llamado IEventRegister. TambiÈn utilizo un HashMap, que esta incluido en el zip

Pues al ataque. Los componentes y el textfield para la salida est·n colocados en el stage. Esos son los gr·ficos que estar·n controlados por la vista ( CounterView.as )

import mx.utils.Delegate; import net.designnation.lapcounter.LapCounterController class net.designnation.lapcounter.CounterView { private var timeline : MovieClip; private var controllerVal : LapCounterController; function CounterView( timeline: MovieClip ) { this.timeline = timeline; this.initView( ); } private function initView( ) { var viewRef: CounterView = this; this.timeline.resetButton.label= "Reset"; this.timeline.resetButton.addEventListener( "click", Delegate.create( this, onResetBTClicked ) ); this.timeline.countButton.label = "Tap me, please!"; this.timeline.countButton.addEventListener( "click", Delegate.create( this, onCountBTClicked ) ); this.timeline.switchCB.setStyle( "color", 0xFFFFFF ); this.timeline.switchCB.label="increment"; this.timeline.switchCB.addEventListener( "click", Delegate.create( this, onCheckBoxClicked ) ); this.timeline.switchCB.selected = true; } public function set controller( controllerRef: LapCounterController ) { this.controllerVal = controllerRef; } public function onCounterChanged( param: Object ) { this.timeline.result.text = param.value; } public function onCountBTClicked( ) { this.controllerVal.screenClicked( ); } public function onResetBTClicked( ) { this.controllerVal.reset( ); } public function onCheckBoxClicked( ) { this.controllerVal.incFlagChanged( ); } }

Esta clase asigna las etiquetas a los botones y al checkbox, y utiliza la clase Delegate para asignar los manejadores de los eventos de dichos componentes. Por tanto, cuando se haga click en countButton ( el botÛn grandote ), el evento ser· escuchado por el mÈtodo "onCountBTClicked" the la clase CounterView.

Hay un mÈtodo para actualizar la salida por pantalla ( asignando el valor de un campo de texto llamado result ), y tres mÈtodos para manejar las acciones del usuario ( uno por componente, gracias a la delegaciÛn ). Estos mÈtodos ejecutan a su vez un mÈtodo p˙blico del controlador

Ya sÈ que la vista puede, y probablemente deba, atacar directamente al modelo para que Èste cambie su estado, pero eso no es algo que me guste mucho. Yo prefiero, siempre que sea posible, hacerlo a travÈs del controlador

Pero, ø dÛnde est· el controlador ?. Pues se construye en el primer frame del fla, mediante el siguiente cÛdigo:

import net.designnation.lapcounter.* var controller: LapCounterController = new LapCounterController( new CounterView( this ) );

Por tanto, se inicializa el controlador y se le pasa una instancia de la vista. Veamos el controlador con m·s detalle.

import net.designnation.lapcounter.* class net.designnation.lapcounter.LapCounterController { private var lapCounter : ILapCounterActions; private var counterView : CounterView; private var timeline : MovieClip; function LapCounterController( view: CounterView ) { this.counterView = view; this.counterView.controller = this; this.initCounter( ); } private function initCounter( ) { this.lapCounter = new LapCounter( ); this.lapCounter.registerEvent( this.counterView, "onCounterChanged" ); } public function screenClicked( ) { this.lapCounter.addLap( ); } public function incFlagChanged( ) { this.lapCounter.setInc( ); } public function reset( ) { this.lapCounter.reset( ); } }

El controlador tiene una referencia a la vista, y es el encargado de crear el modelo ( LapCounter ). No mantiene una referencia al modelo como tal, sino al interaz del mismo ( ILapCounterActions ). Pero veremos el modelo m·s tarde

Cuando se crea el modelo, el modelo registra a la vista como event listener del modelo. Por tanto, cuando Èste cambie su estado ( los datos que contiene ), emitir· un evento que ser· escuchado directamente por la vista.

TambiÈn hay tres mÈtodos ( screenClicked, incFlagChanged, and reset ), que son los que utiliza la vista para comunicarse con el modelo

Y, por ˙ltimo, llegamos al modelo ( LapCounter.as )

import net.designnation.events.EventSource import net.designnation.lapcounter.ILapCounterActions class net.designnation.lapcounter.LapCounter extends EventSource implements ILapCounterActions { private var actualLaps : Number; private var incFlag : Boolean; function LapCounter( ) { this.actualLaps = 0; this.incFlag = true; } public function addLap( ) { var multiplier: Number = ( this.incFlag )? 1: -1; this.actualLaps+= 1* multiplier; if ( this.actualLaps< 0 ) this.actualLaps = 0; updateView( ); } private function updateView( ) { fireEvent( "onCounterChanged", { value: this.actualLaps } ); } public function setInc( ) { this.incFlag = !this.incFlag; } public function reset( ) { this.actualLaps = 0; updateView( ); } }

Esta clase extiende EventSource ( para heredar asÌ el manejo de eventos ), e implementa un interfaz ( ILapCounterActions ). Ese interfaz ser· la referencia que tiene el controlador del modelo. De este modo, se podrÌa cambiar el modelo, sin afectar al resto de la aplicaciÛn ( siempre y cuando el nuevo modelo tambiÈn implemente ese interfaz, claro ). Este interfaz extiende a su vez del interfaz de registro de eventos.

El modelo no es muy complejo ( la aplicaciÛn tampoco ). Simplemente maneja un contador, y una bandera para saber si el contador debe incrementarse o decrementarse. TambiÈn hay un mÈtodo para actualziar la vista ( emitiendo el evento pertinente ).

Y eso es todo. Simplemente hay que publicar la aplicaciÛn como flash 6, crear un html para contenerla, y °a jugar al scalextric!

Y por supuesto, el cÛdigo fuente: est· aquÌ

[OT] ArqueologÌa de port·tiles

Hace unas semanas fui de visita a casa de mis padres, y mi padre me dijo que, moviendo unas cajas, habÌa encontrado el primer portatil que hubo en mi casa.

El pportatil es un Toshiba T1850. Tiene un porcesador 386SX, 4MB de RAM, and un disco duro de 120MB. Impresionante.

toshiba1.jpg

toshiba2.jpg

IntentÈ encenderlo, y para mi sorpresa, °funcionÛ!. Corre un Windows 3.11 ( para Trabajo en Grupo ), y la verdad es que no funciona mal. Incluso, estuve escribiendo un poco en Word.

toshiba3.jpg

Fue un bonito reencuentro.

Diciembre 12, 2004

[J2ME] AplicaciÛn de ejemplo: un dado

Empezamos a trabajar en serio, desarrollando una aplicaciÛn muy sencillita sobre la que iremos aÒadiendo sucesivas mejoras. Se trata de un dado

Dos capturas:

Cap1.jpg Cap2.jpg

La aplicaciÛn no es nada del otro mundo. Muestra un dado, de manera que cada vez que el usuario presiona el botÛn de disparo, o la tecla "5", se genera un n˙mero aleatorio entre 1 y 6, y se muestra la cara del dado correspondiente.

TambiÈn hay una pantalla de "acerca de", que muestra informaciÛn sobre la aplicaciÛn

Comencemos. He encapsulado la generaciÛn del n˙mero aleatorio en una clase ( DiceController.java ). AquÌ est· el cÛdigo:

package net.designnation.j2me.dice; import java.util.Random; public class DiceController implements IDiceActions{ private static int LOWER_LIMIT = 1; private static int UPPER_LIMIT = 6; private Random random; DiceController( ) { random = new Random( ); } public int rollDice( ) { return 1+ Math.abs( random.nextInt() % UPPER_LIMIT ); } }

Esta clase implementa un interfaz ( IDiceActions ):

package net.designnation.j2me.dice; public interface IDiceActions { int rollDice( ); }

øPor quÈ ese interfaz?. Porque esa ser· la referencia a esta clase que tendr· el programa. Pero eso lo veremos m·s tarde.

Las aplicaciones J2ME deben construirse sobre una clase que extienda de MIDlet. Esa clase ( al menos en este ejemplo ) tendr· la responsabilidad de manejar la interacciÛn con los botones de alto nivel ( los soft buttons, asignados por el sistema ). pero en este ejemplo, adem·s, queremos manejar la entrada utilizando un botÛn m·s ( el de disparo ), por lo que necesitaremos acceder al manejo de teclado a bajo nivel. Eso lo haremos a travÈs de otra clase, que extender· de Canvas.

el cÛdigo de la clase "principal":

package net.designnation.j2me.dice; import javax.microedition.lcdui.Command; import javax.microedition.lcdui.CommandListener; import javax.microedition.lcdui.Display; import javax.microedition.lcdui.Displayable; import javax.microedition.midlet.MIDlet; import javax.microedition.midlet.MIDletStateChangeException; public class Dice extends MIDlet implements IExitActions, CommandListener { private Display display; private DiceCanvas diceCanvas; private Command exitCommand = new Command("Exit", Command.EXIT, 1); private Command backCommand = new Command( "Back", Command.BACK, 2 ); private Command aboutCommand = new Command( "About", Command.SCREEN, 5 ); protected static String APP_NAME = "Dice"; public Dice() { super(); diceCanvas = new DiceCanvas( ); diceCanvas.addCommand( exitCommand ); diceCanvas.addCommand( aboutCommand ); diceCanvas.setCommandListener( this ); } protected void startApp() throws MIDletStateChangeException { display = Display.getDisplay( this ); display.setCurrent( diceCanvas ); } protected void pauseApp() { } protected void destroyApp( boolean arg0 ) { notifyDestroyed( ); } public void exitApp( ) { destroyApp( true ); } public void commandAction (Command c, Displayable d) { if (c == exitCommand ) { exitApp( ); } if( c == aboutCommand ) { HelpScreen helpScreen = new HelpScreen( ); helpScreen.addCommand( backCommand ); helpScreen.setCommandListener( this ); display.setCurrent( helpScreen ); } if( c == backCommand ) { display.setCurrent( diceCanvas ); } } }

Esta clase hereda de MIDlet ( es obligatorio ), y por tanto, implementar· tres mÈtodos ( startApp, pauseApp, destroyApp ), que deber·n ser sobreescritos. TambiÈn he creado tres comandos ( exitCommand, aboutCommand, backCommand ), que son los que ser·n asignados a los soft buttons

Cuando se instancia esta clase, se crea tambiÈn una instance de DiceCanvas ( m·s tarde la veremos con m·s calma, pero es una clase que extiende de Canvas ). TambiÈn se registra como listener para los comandos.

public Dice() { super(); diceCanvas = new DiceCanvas( ); diceCanvas.addCommand( exitCommand ); diceCanvas.addCommand( aboutCommand ); diceCanvas.setCommandListener( this ); }

Cuando comienza la ejecuciÛn de la aplicaciÛn ( y por tanto, se ejecuta el mÈtodo startApp ), se hace que el display sea ocupado por el canvas

display = Display.getDisplay( this ); display.setCurrent( diceCanvas );

La interacciÛn de alto nivel est· manejada por el mÈtodo commandAction. Cuando el usuario selecciona "about", se crea una instancia de HelpScreen, y se la hace ocupar el display

if( c == aboutCommand ) { HelpScreen helpScreen = new HelpScreen( ); helpScreen.addCommand( backCommand ); helpScreen.setCommandListener( this ); display.setCurrent( helpScreen ); }

Ahora, echÈmosle un vistazo a DiceCanvas:

package net.designnation.j2me.dice; import java.io.IOException; import javax.microedition.lcdui.Canvas; import javax.microedition.lcdui.Font; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Image; public class DiceCanvas extends Canvas { private IDiceActions dice; DiceCanvas( ) { dice = new DiceController( ); } protected void paint( Graphics g ) { Font font = null; Image diceIcon = null; g.setColor( 0xffffff ); g.fillRect( 0, 0, getWidth( ), getHeight( ) ); g.setColor(0); font = Font.getFont( Font.FACE_PROPORTIONAL, Font.STYLE_BOLD, Font.SIZE_SMALL ); g.setFont( font ); try { String imageName = "/resources/icon"+ dice.rollDice( ) + ".png"; diceIcon = Image.createImage( imageName ); } catch (IOException e) { throw new RuntimeException ("Unable to load diceIcon: "+e); } g.drawImage( diceIcon, getWidth( )/ 2, 30, Graphics.TOP | Graphics.HCENTER ); g.drawString ("Press fire / 5", getWidth () / 2, getHeight () -30, Graphics.HCENTER | Graphics.BASELINE); g.drawString ("to roll the dice", getWidth () / 2, getHeight () -10, Graphics.HCENTER | Graphics.BASELINE); } public void keyReleased( int keyCode ) { if ( getGameAction( keyCode ) == FIRE ) { repaint( ); } } }

øRecuerdas el interfaz que implementaba DiceController?. La referencia a DiceController en DiceCanvas est· tipeada como IDiceActions, por tanto DiceCanvas sÛlo sabe que hay un mÈtodo llamado rollDice que le va a devolver un integer, pero no sabe a quÈ clase pertenece ese mÈtodo.

private IDiceActions dice; DiceCanvas( ) { dice = new DiceController( ); }

El mÈtodo paint est· heredado de Canvas, y se ejecuta cada vez que queremos refrescar el display. Por tanto, es donde escribimos el texto y presentamos los iconos del dado. Simplemente, cargamos un icono diferente dependiendo del valor que nos devuelva rollDice( ). Con cada refresco se borra el display completo, lo cual no es nada eficiente, pero eso cambiar· en la siguiente versiÛn.

try { String imageName = "/resources/icon"+ dice.rollDice( ) + ".png"; diceIcon = Image.createImage( imageName ); }

TambiÈn manejamos las pulsaciones de teclado, utilizando el mÈtodo keyReleased

public void keyReleased( int keyCode ) { if ( getGameAction( keyCode ) == FIRE ) { repaint( ); } }

Y finalmente, hay una pantalla de "acerca de", que extiende de Form, y que contiene el icono del programa y un texto informativo sobre la aplicaciÛn

package net.designnation.j2me.dice; import java.io.IOException; import javax.microedition.lcdui.Form; import javax.microedition.lcdui.Image; import javax.microedition.lcdui.ImageItem; public class HelpScreen extends Form { HelpScreen( ) { super( "About "+ Dice.APP_NAME ); try { ImageItem logo = new ImageItem( "", Image.createImage( "/resources/icon.png"), ImageItem.LAYOUT_CENTER | ImageItem.LAYOUT_NEWLINE_BEFORE | ImageItem.LAYOUT_NEWLINE_AFTER, "" ); append( logo ); } catch (IOException e) { throw new RuntimeException ("Unable to load Image: "+e); } append("You can roll the dice using the fire button or the '5' key\n\nDeveloped by Cesar Tardaguila\nIcons by Celia Carracedo\n\nhttp://www.design-nation.net/en"); } }

Es un Form muy simple, que contiene un icono y un texto. Nada especial

Hay varias cosas que tienen que mejorarse. Lo primero de todo, la aplicaciÛn pesa bastante ( sobre 18 Kb ), por los iconos. Por eso, en la siguiente versiÛn, intentarÈ dibujar los iconos por cÛdigo. TambiÈn intentarÈ aÒadir un poco de animaciÛn, para intentar hacer el resultado un poco "flashero", asÌ que habr· que manejar Threads. Y tambiÈn habr· que mejorar el refresco de pantalla.

Queda mucho por hacer, pero al menos ya hay un punto de partida.

Si te quieres bajar la aplicaciÛn:

http://www.design-nation.net/j2me/dice.jad

http://www.design-nation.net/j2me/dice.jar

El cÛdigo fuente y los iconos:

http://www.design-nation.net/j2me/dice_source.zip

Yo sÛlo lo he podido probaar en mi N-Gage, por lo que si lo pruebas en alg˙n otro dispositivo, y encuentras alg˙n problema, por favor, h·zmelo saber.

Y ya es suficiente para un domingo, ø no ?

Diciembre 01, 2004

[J2ME]: Clase Random

øCu·ntas veces habrÈ oido a alg˙n javero decir que el flash es muy raro?. Bueno, pues Java tambiÈn tiene sus cositas. Como por ejemplo la clase Random, que no es igual en J2ME y en J2EE.

Una vez leÌda la documentaciÛn con m·s atenciÛn, resulta obvio, pero el caso es que hoy he perdido casi una hora intentando generar un n˙mero aleatorio en un rango determinado.

El problema viene porque la clase Random que forma parte del MIDP 1.0 no implementa el mÈtodo nextInt( int i ), que generarÌa un n˙mero aleatorio menor que i, y que sÌ implementa la clase Random de J2EE "est·ndar".

øLa soluciÛn?. Recurrir al mÈtodo nextInt( ), que sÌ est· implementado:

Random random = new Random( ); int randomNumber = Math.abs( random.nextInt() % UPPER_LIMIT );

donde UPPER_LIMIT es una constante cuyo valor es la cota superior del intervalo.

Supongo que para los que tengan m·s experiencia en J2ME es obvio, pero para mÌ no lo ha sido.