Dibujando con el ratón

En entrada anteriores he intentado entender cómo funcionan los eventos de teclado, como se dibujan elementos y como se maneja el patrón MVC. Ahora juntaré estos elementos para crear un sencillo programa que dibujará rectas en una ventana gestionando las entradas del ratón.

1. Diseño, ¿objetos?

El primer paso es decidir que va a hacer el programa. En el caso que nos ocupa algo aparentemente sencillo: dibujar lineas rectas en una ventana. Claro que esa aparente sencillez oculta que vamos a tener que controlar los eventos de ratón, guardar las pulsaciones, crear las rectas , guardarlas y finalmente dibujarlas. Fácil, ¿No?

Siguiendo con el patrón MVC he decicido trabajar con cuatro tipos de objetos: Una vista (DVistaDibujo), un controlador (DControlador), un modelo de datos con el dibujo entero (DDibujo) y un modelo de datos para las rectas (DRectas). Las relaciones entre ellos se pueden representar con el siguiente esquema:

Diseño del programa dibu

Un punto importante es como se ha decidido que el dibujo estaría compuesto por varios elementos (rectas) y que la vista sería única. Es posible diseñar un programa dónde cada recta dibujada tenga su propia vista. Apple no recomienda esto por los posibles problemas de rendimiento. E igualmente es posible un dibujo que contenga solamente una lista puntos (posiblemente más fácil…)

2. Crear las clases y sus relaciones

Una vez decidido el diseño del programa ya se puede empezar a trabajar. Se crea el proyecto y vamos a Interface Builder. En IB creamos la ventana del programa, la vista personalizada (desciende de NSView), el controlador y el dibujo (descienden de NSObject). Podemos también crear el objeto DRecta o bien esperar a hacerlo más adelante en XCode.

Diseño de la ventana del programa

Luego hay que crean las instancias de los objetos para añadir las relaciones entre los objetos siguiendo el esquema del diseño.

Instancias y relaciones

Para acabar en IB hay que crear los ficheros de cada clase (recuerda: Menú Classes | Create files for …)

3. Rectas y dibujo

Aquí esta la principal decisión de diseño del programa, ¿Cómo ir añadiendo elementos al dibujo? De momento rectas (¿ o mejor dicho, segmentos?), pero después me interesará añadir elementos más complejos. ¿Y cómo controlar las propiedades del dibujo, color, grosor? Aún esta un poco lejos de mis capacidades, pero hay que pensar en el futuro. Por eso cada dibujo estará formado por distintos elementos/objetos.

La interface de la recta será:

//
//  DRecta.h
//  Dibus versión: 0.01
//
//  Created by Luis Rey Cabrerizo on 11/01/08.
//  Copyright 2008 LRC. All rights reserved.
//
#import <Cocoa/Cocoa.h>

@interface DRecta : NSObject {
    NSPoint origen, final;
    NSColor *color;
}

-(id) init;
-(void) setPuntoOrigen: (NSPoint)punto;
-(void) setPuntoFinal: (NSPoint)punto;
-(NSPoint) puntoOrigen;
-(NSPoint) puntoFinal;

@end

Nada complicado solamente puntos inicial y final y color. Más adelante se podrán añadir más propiedades.

Y la implementación:

//
//  Drecta.m
//  Dibus versión: 0.01
//
//  Created by Luis Rey Cabrerizo on 11/01/08.
//  Copyright 2008 LRC. All rights reserved.
//
#import "DRecta.h"

@implementation DRecta
// Inicialización
// Establece unos puntos de origen y final arbitarios (0, 0)
// y el color inicial, Negro
-(id) init {
	origen = NSMakePoint(0, 0);
	final = NSMakePoint(0, 0);
	color = [NSColor blackColor];
	[super init];
    return self;
}

// Establece el punto de origen de la recta a partir de
// un punto dado
-(void) setPuntoOrigen: (NSPoint)punto{
	origen = punto;
}

// Establece el punto final de la recta a partir de
// un punto dado
-(void) setPuntoFinal: (NSPoint)punto{
	final = punto;
}

// Devuelve en punto de origen de la recta
-(NSPoint) puntoOrigen{
	return origen;
}

// Devuelve en punto final de la recta
-(NSPoint) puntoFinal{
	return final;
}

@end

Poca cosa más que definiciones de métodos muy triviales.

Para el dibujo la interface es:

//
//  DDibujo.h
//  Dibus versión: 0.01
//
//  Created by Luis Rey Cabrerizo on 11/01/08.
//  Copyright 2008 LRC. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import "Drecta.h"

@interface DDibujo : NSObject
{
NSMutableArray *elementos;  //Array con los elementos de forman el dibujo
}

- (id) init;
- (void) addElemento: (DRecta *) recta;
- (NSMutableArray *) caminos;

@end

En este caso la interface es lo más jugoso, ya que define un NSMutableArray dónde iremos añadiendo los elementos/objetos del dibujo. Eso deja la implementación en algo casi trivial:

//
//  DDibujo.m
//  Dibus versión: 0.01
//
//  Created by Luis Rey Cabrerizo on 11/01/08.
//  Copyright 2008 LRC. All rights reserved.
//
#import "DDibujo.h"

@implementation DDibujo

// Inicialización
// Crea la matriz con los elementos del dibujo
- (id) init {
	elementos = [[NSMutableArray alloc] init];
	[super init];
	return self;
}

//  Recibe la recta y la añade al dibujo
- (void) addElemento: (DRecta *) recta
{
	[elementos addObject: recta];
}

//  Devuelve los diferentes elementos que componen el dibujo
- (NSMutableArray *) caminos
{
	return elementos;
}

@end

Realmente me ha impresionado la elegancia y potencia de Cocoa para resolver algo tan complicado como es gestionar un conjunto de objetos que pueden ser muy diferentes entre si.

4. ¿Que hacer con un clic de ratón?

Para poder gestionar los eventos de ratón vamos a distribuir el trabajo en dos partes. La vista se encargará de capturar los eventos y luego enviará aquel que nos interese al controlador, que se encargará del resto del trabajo.

2.1. Recibiendo los eventos de ratón

Para que la vista reciba los eventos de ratón ha de aceptar ser firstResponde, para ellos sobreescribimos el método acceptFirstResponder:

//Permite aceptar los eventos como gestor por defecto
- (BOOL)acceptsFirstResponder {
	return YES;
}

Luego, claro esta, hay que enviar el clic del ratón al controlador, sobreescribimos el evento mouseDown:

// mouseDown:(NSEvent)
//  Recibe los eventos del clic de ratón y envia las coordenadas del punto
//  hacia el controlador
- (void)mouseDown:(NSEvent *)evento {
	NSPoint curPoint = [self convertPoint:[evento locationInWindow] fromView:nil];
	[miControl clicRaton:curPoint ];
}

Sencillo, pero hay que ir con cuidado y enviar al controlador las coordenadas del punto respecto a la vista y no respecto al total de la ventana, y eso es lo que hace el mensaje convertPoint:. Recibe las coordenadas donde se ha producido el clic [evento locationInWindow] y las convierte en un punto de la vista.

2.2. Tratar el clic de ratón

El controlador es la parte más compleja en flujo de programa, la interface es sencilla, recordando sólo las salidas hacia la vista y los datos:

//
//  DControlador.h
//  Dibus versión: 0.01
//
//  Created by Luis Rey Cabrerizo on 11/01/08.
//  Copyright 2008 LRC. All rights reserved.
//
#import <Cocoa/Cocoa.h>

@interface DControlador : NSObject
{
    IBOutlet id miVista;
    IBOutlet id miDibujo;
    int numClic;		// Número de clics
    NSPoint primerPunto;	// Coordenadas del primer punto entrado
}

- (id) init;
- (void) clicRaton:(NSPoint) punto;

@end

En cambio la implementación tiene más complicaciones:

//
//  DControlador.m
//  Dibus versión: 0.01
//
//  Created by Luis Rey Cabrerizo on 11/01/08.
//  Copyright 2008 LRC. All rights reserved.
//
#import "DControlador.h"
#import "DDibujo.h"

@implementation DControlador

// Inicialización, pone el número de clics a cero
- (id) init {
	numClic = 0;
	[super init];
	return self;
}

// clicRaton:(NSPoint)
//  Recibe las coordenadas del punto dónde se ha hecho clic con el ratón
//  En el segundo clic crea la recta y la añade al dibujo.
- (void) clicRaton:(NSPoint) punto{
    numClic++;
    if (numClic == 1)	// Primer clic
    {
        //Guarda las coordenadas hasta el siguiente clic
        primerPunto = punto;
    }
    else	    //Segundo clic
    {
	//Definimos un nuevo elemento DRecta
	DRecta *unaRecta = [[DRecta alloc] init];
	[unaRecta setPuntoOrigen:primerPunto];
	[unaRecta setPuntoFinal:punto];

        //Añadimos el elemento al dibujo y pedimos una actualización de la vista
        [miDibujo addElemento:unaRecta];
	[miVista setNeedsDisplay:YES];

	//Reiniciamos el contador de clics
	numClic = 0;
    }
}

@end

En el método init simplemente ponemos el contador de número de clics a cero (una buena costumbre es siempre asegurarse de poner a cero los valores numéricos) y llamamos al inicializador super para las demás acciones.

En el método clicRaton: se trata el punto recibido desde la vista. Si es el primer clic simplemente se guarda el punto para más tarde. Si ya es la segunda pulsación se recupera el punto anterior, se crea la recta, se definen los puntos de la recta, se añade al dibujo (enviando el mensaje addElemento: que hemos creado en la clase DDibujo) y se informa a la vista que tendrá que redibujarse para añadir el nuevo elemento. Para luego poner el contador del número de clics a cero.

3. Dibujando…

La tarea más importante de la vista personalizada que hemos definido es dibujar los diferentes elementos del dibujo:

// Dibuja la Vista
- (void)drawRect:(NSRect)fondo
{
    //Dibujar el fondo de color blanco
    [[NSColor whiteColor] set];
    [NSBezierPath fillRect:fondo];	

    // Se recoge en un Array los elementos que forman el dibujo
    // y luego se recorre el Array para dibujarlos.
    NSArray *elementos = [miDibujo caminos];
    NSEnumerator *n = [elementos objectEnumerator];
    DRecta *unaRecta;
    while (unaRecta = [n nextObject]){
	NSPoint inicioRecta  = [unaRecta puntoOrigen];
	NSPoint finRecta = [unaRecta puntoFinal];
	[[NSColor blackColor] set];
        [NSBezierPath strokeLineFromPoint:inicioRecta toPoint:finRecta];
    }
}

Utilizando NSEnumerator y un bucle while podemos acceder a cada uno de los elementos del dibujo (rectas), recuperar los puntos iniciales y finales y dibujarlas con NSBezierPath strokeLineFromPoint: toPoint:

Un aspecto importante es añadir al código de la vista la directiva

#import "DDibujo.h"

Esta directiva nos permite enviar mensajes al dibujo y través del dibujo poder crear objetos DRecta que dibujar.

El programa funcionando

4. Más información

El código fuente lo puedes descargar en: dibus01.zip

Etiquetas: , ,

Una respuesta to “Dibujando con el ratón”

  1. Miguel Valle Says:

    No podrías subir de nuevo el código fuente? :D necesito analizarlo , apenas voy empezando en esto :)

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s


A %d blogueros les gusta esto: