PintaLlop - Programa de dibujo

Llop Site Home > JAVA > PintaLlop > Lienzos y demás

Lienzos y demás :

Clase ColeccionLienzos 

package pintallop;

import javax.swing.*;

/** Título: PintaLlop
    Descripción: Prácticas capítulo 11, ejercicio 3 
    Copyright: Copyright (c) 2005
    Empresa: Casa_Llop
    @author Albert_Lobo
    @version 1.0 */

/** Clase que agrupa los lienzos de 'PintaLlop'. Soporta hasta diez 'Lienzo's. */
public class ColeccionLienzos
{
  // La colección de herramientas para pintar en los lienzos.
  private ColeccionHerramientas coleccionHerramientas;
  // El array de 'Lienzo's.
  private Lienzo lienzos [];
  // Cuántos 'Lienzo's se han añadido a la colección.
  private byte totalLienzos;
  // Número del 'Lienzo' que se muestra en pantalla.
  private byte lienzoActual;

  /** Constructor. Crea una colección de lienzos vacía.
      Utilizará las herramientas especificadas para pintar los lienzos que se vayan añadiendo.
      @param nuevaColeccionHerramientas ColeccionHerramientas  Herramientas para pintar en los 'Lienzo's. */
  public ColeccionLienzos (ColeccionHerramientas nuevaColeccionHerramientas)
  {
    coleccionHerramientas = nuevaColeccionHerramientas;
    lienzos = new Lienzo [10];
    totalLienzos = -1;
  }

  /** Destruye el lienzo en pantalla. */
  public void destruyeLienzo ()
  {
    lienzos [lienzoActual] = null;
    // Avanzamos una posición los lienzos en posiciones posteriores del array.
    for (byte i = lienzoActual; i < totalLienzos; i++)
      lienzos [i] = lienzos [i + 1];
    lienzos [totalLienzos--] = null;
  }

  /** Crea un nuevo lienzo para la colección.
      No puede haber más de 10 'Lienzo's.
      @return boolean  'true' si ha sido posible crear el 'Lienzo'; 'false' si no. */
  public boolean creanuevoLienzo ()
  {
    if (totalLienzos == 9)
    {
      JLabel etiqueta = new JLabel ("<HTML> No puede crear un nuevo lienzo,<BR>"
                                    + "para eso, debe cerrar otros lienzos.");
      JOptionPane.showMessageDialog (lienzos [0], etiqueta);
      return false;
    }

    lienzos [++totalLienzos] = new Lienzo (coleccionHerramientas);
    return true;
  }

  /** Prepara el último lienzo para que se pueda comenzar a pintar en él. */
  public void preparaNuevoLienzo ()
  {
    lienzos [totalLienzos].preparaLienzo ();
  }

  /** Devuelve el índice del lienzo en pantalla.
      @return byte  Índice del lienzo en pantalla. */
  public byte getNumeroLienzoActual ()
  {
    return lienzoActual;
  }

  /** Establece el lienzo especificado como el que debe mostrarse en pantalla.
      @param nuevoLienzoActual byte  Índice del nuevo lienzo visible. */
  public void setLienzoActual (byte nuevoLienzoActual)
  {
    lienzoActual = nuevoLienzoActual;
  }

  /** Devuelve cuántos lienzos pintables hay en la colección.
      @return byte  Número de lienzos pintables en la colección. */
  public byte getNumeroLienzos ()
  {
    return totalLienzos;
  }

  /** Devuelve el último 'Lienzo' de la colección.
      @return Lienzo  El último 'Lienzo' de la colección. */
  public Lienzo getLienzoFinal ()
  {
    return lienzos [totalLienzos];
  }

  /** Devuelve el lienzo visible en pantalla.
      @return Lienzo  El 'Lienzo' visible en pantalla. */
  public Lienzo getLienzoActual ()
  {
    return lienzos [lienzoActual];
  }
}



Clase Lienzo

package pintallop;

import java.io.*;
import java.lang.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.datatransfer.*;

/** Clase que representa un lienzo -extiende 'JPanel'. Sobre éste se puede pintar utilizando
    el ratón y/o el teclado 'Listeners'. La interfaz 'Transferable' está para permitir
    el intercambio de datos entre la aplicación y el portapapeles. */
public class Lienzo extends JPanel implements KeyListener, MouseListener, MouseMotionListener, Transferable
{
  // Un archivo que representa la imagen del lienzo en el disco, y su extensión ('.jpg' o '.png').
  private File archivo;
  private String extension;

  // Variables que indican si estamos arrastrando el ratón (mientras tenemos un botón pulsado),
  // si estamos en situación de escribir texto, y si queda memoria volátil disponible para la aplicación.
  private boolean arrastrando;
  private boolean escribiendo;
  private boolean quedaMemoria;

  // Variables para almacenar las coordenadas de un punto del lienzo.
  // Esas coordenadas indican dónde se originó el nuevo óvalo/línea/rectángulo...
  private int xInicial;
  private int yInicial;

  // La clase que gestiona lo que se pinta y lo que se ha pintado en el lienzo.
  private GestorGraficos gestorGraficos;
  // La colección de herramientas utilizadas para pintar, y la que se puede utilizar actualmente.
  private ColeccionHerramientas herramientas;
  private Herramienta herramientaEnUso;

  /** Constructor. Sólo necesita un puntero a la colección de herramientas de 'PintaLlop'.
      @param nuevasHerramientas ColeccionHerramientas  Las herramientas para pintar sobre el lienzo. */
  public Lienzo (ColeccionHerramientas nuevasHerramientas)
  {
    // Construimos un 'JPanel'.
    super ();
    // Asumimos que no ha habido tiempo de guardar el lienzo al disco, y por tanto, no tiene archivo.
    archivo = null;
    // Establecemos las herramientas.
    herramientas = nuevasHerramientas;
    // Inicializamos el gestor de los gráficos.
    gestorGraficos = new GestorGraficos (this);
    // Asumimos que aún no se está arrastrando ni escribiendo nada.
    arrastrando = false;
    escribiendo = false;
  }

  /** Permite conocer el archivo en el disco en el que se guarda la imagen del lienzo.
      Devuelve 'null' si aún no se ha guardado la imagen.
      @return File  El archivo de la imagen. */
  public File getArchivo ()
  {
    return archivo;
  }

  /** Define el archivo en disco en el que se guardará la imagen del lienzo.
      @param nuevoArchivo File  Archivo destinado a la imagen de este lienzo. */
  public void setArchivo (File nuevoArchivo)
  {
    archivo = nuevoArchivo;
  }

  /** Permite conocer la extensión de la imagen del lienzo en el disco.
      'PintaLlop' soporta las extensiones 'png' y 'jpg', aunque también puede leer 'gif'.
      @return String  La extensión del archivo con la imagen. */
  public String getExtension ()
  {
    return extension;
  }

  /** Define la extensión que tendrá el la imagen cuando se grabe, en un archivo, al disco.
      @param nuevaExtension String  La extensión del archivo con la imagen. */
  public void setExtension (String nuevaExtension)
  {
    extension = nuevaExtension;
  }

  /** Prepara el lienzo para que se pueda dibujar sobre él. */
  public void preparaLienzo ()
  {
    // Le pone tamaño, e inicializa su gestor de los gráficos.
    setSize (438, 312);
    gestorGraficos.inicializaGestorGraficos ();
    // Ahora los oyentes.
    addKeyListener (this);
    addMouseListener (this);
    addMouseMotionListener (this);
  }

  /** Deshace el último cambio en el lienzo -si puede; si no, nada. */
  public void deshacer ()
  {
    if (gestorGraficos.puedeDeshacer ())
      gestorGraficos.deshacer ();
  }

  /** Rehace el último 'Deshacer' en el lienzo -si puede; si no, nada. */
  public void rehacer ()
  {
    if (gestorGraficos.puedeRehacer ())
      gestorGraficos.rehacer ();
  }

  /** Enclasta en la esquina superior izquierda del lienzo una imagen.
      @param nuevaImagen Image  La imagen. */
  public void pegaImagen (Image nuevaImagen)
  {
    gestorGraficos.actualiza ();
    gestorGraficos.getGraficosEnPantalla ().drawImage (nuevaImagen, 0, 0, null);
    repaint ();
  }

  /** Devuelve la imagen que actualmente muestra el lienzo.
      @return Image  La imagen del lienzo. */
  public Image getDibujo ()
  {
    return gestorGraficos.getImagenEnPantalla ();
  }

  /** Método de la interfaz 'MouseListener'. Se invoca al pulsar un botón del ratón en cualquier punto del
      lienzo. Indica que vamos a comenzar a pintar un nuevo trazo o figura,
      o que hemos terminado de escribir un texto, e intentaremos escribir otro.
      @param evento MouseEvent  Evento que ha invocado este método. */
  public void mousePressed (MouseEvent evento)
  {
    // Damos el 'foco' al lienzo, por si acaso no lo tenía.
    requestFocus ();

    // Puede que el usuario pulse dos botones a la vez -nos aseguramos que la segunda pulsación
    // no cuenta.
    if (arrastrando)
      return;

    // Es momento de tomar constancia de las coordenadas del ratón.
    xInicial = evento.getX ();
    yInicial = evento.getY ();

    // La siguiente comprobación hace algo parecido a 'terminaTexto ()', de la clase 'PintaLlop'.
    // La idea es no ensuciar el lienzo si pulsamos dos veces
    // con la herramienta de texto sin haber escrito nada.
    if ((herramientaEnUso.getTipo () == ColeccionHerramientas.TEXTO) && (escribiendo))
    {
      escribiendo = false;
      if (herramientaEnUso.getCadena ().equals ("|"))
      {
        herramientaEnUso.setCadena ("");
        pintaTexto ();
      }
    }

    // Intentamos actualizar el gestor de gráficos; si no ha sido posible, es que ya no queda
    // memoria disponible para 'PintaLlop', y en la variable 'quedaMemoria' se deja constancia.
    quedaMemoria = gestorGraficos.actualiza ();

    // Si queda memoria, podemos comenzar a pintar; si no, informamos al usuario.
    if (quedaMemoria)
      arrastrando = true;
    else
    {
      JLabel etiqueta = new JLabel ("<HTML>No puede seguir pintando:<BR>"
                                    + "está a punto de consumir la memoria disponible.<BR>"
                                    + "Puede cerrar otros lienzos para seguir pintando.");
      JOptionPane.showMessageDialog (Lienzo.this, etiqueta);
    }
  }

  /** Método de la interfaz 'MouseListener'. Se invoca al soltar un botón del ratón sobre el lienzo.
      Indica que hemos terminado de dibujar un trazo/figura, o que vamos a comenzar a escribir texto.
      @param evento MouseEvent  Evento que ha invocado este método. */
  public void mouseReleased (MouseEvent evento)
  {
    // El usuario no estaba dibujando, por tanto el método no necesita hacer nada.
    if (!arrastrando)
      return;

    // La variable 'arrastrando' vuelve a 'false' para indicar que ya no estamos pintando.
    arrastrando = false;

    // Si la herramienta en uso es la de texto, es que el usuario está listo para escribir algo.
    // Ajustamos las coordenadas en las que se pintará el texto; indicamos que ya estamos escribiendo,
    // ponemos una barra "|" como texto de la herramienta para que el usuario vea dónde va a
    // empezar a escribir, y repintamos.
    if (herramientaEnUso.getTipo () == ColeccionHerramientas.TEXTO)
    {
      xInicial = evento.getX ();
      yInicial = evento.getY ();
      escribiendo = true;
      herramientaEnUso.setCadena ("|");
      pintaTexto ();
    }
  }

  /** Método de la interfaz 'MouseMotionListener'. Se invoca al mover el ratón mientras
      hay uno de los botones pulsado. Según la herramienta en uso, pinta un trazo o una figura
      (no texto) en el lienzo.
      @param evento MouseEvent  Evento que ha invocado este método. */
  public void mouseDragged (MouseEvent evento)
  {
    // El usuario no está dibujando, por tanto el método no necesita hacer nada.
    if (!arrastrando)
      return;

    // Las coordenadas del puntero del ratón.
    int x = evento.getX();
    int y = evento.getY();

    // Ahora toca pintar sobre en lienzo; miramos qué herramienta se utiliza.
    switch (herramientaEnUso.getTipo ())
    {
      // El lápiz, la goma, y la línea tiran de la función 'fillPolygon ()', -deben dibujar un rectángulo.
      // Como utilizarían la misma pauta para calculas coordenadas de las esquinas,
      // las agrupamos en el mismo 'case'.
      case ColeccionHerramientas.LAPIZ: case ColeccionHerramientas.GOMA: case ColeccionHerramientas.LINEA:
        // Vamos a dibujar un rectángulo, así que debemos averiguar las coordenadas de sus 4 esquinas.
        // Separación entre el inicio y el final del trazo.
        float vector = (float) (Math.pow (Math.pow (xInicial - x, 2) + Math.pow (yInicial - y, 2), .5));
        // Ahora calculamos los componentes de un vector que sumaremos o restaremos de los puntos
        // inicial y final del trazo -así conoceremos las esquinas del rectángulo.
        // Este valor almacena la relación entre la longitud del trazo y la mitad de su grosor.
        float relacion = vector / (herramientaEnUso.getGrueso () / 2);
        // Ya tenemos los componentes del vector.
        float vectX = Math.abs (yInicial - y) / relacion;
        float vectY = Math.abs (xInicial - x) / relacion;

        // Arrays para las coordenadas del rectángulo.
        int ejesX [] = new int [4];
        int ejesY [] = new int [4];

        // Según hacia qué esquina del lienzo se proyecte el trazo, habrá que calcular de distinta forma
        // las esquinas.
        if ((xInicial - x < 0) && (yInicial - y < 0)
            || (xInicial - x >= 0) && (yInicial - y >= 0))
        {
          ejesX [0] = (int) (xInicial + vectX);
          ejesY [0] = (int) (yInicial - vectY);
          ejesX [1] = (int) (xInicial - vectX);
          ejesY [1] = (int) (yInicial + vectY);
          ejesX [2] = (int) (x - vectX);
          ejesY [2] = (int) (y + vectY);
          ejesX [3] = (int) (x + vectX);
          ejesY [3] = (int) (y - vectY);
        }
        else
        {
          ejesX [0] = (int) (xInicial + vectX);
          ejesY [0] = (int) (yInicial + vectY);
          ejesX [1] = (int) (xInicial - vectX);
          ejesY [1] = (int) (yInicial - vectY);
          ejesX [2] = (int) (x - vectX);
          ejesY [2] = (int) (y - vectY);
          ejesX [3] = (int) (x + vectX);
          ejesY [3] = (int) (y + vectY);
        }
        // Ahora, hay que puntualizar el color y otras cosas según la herramienta.
        if (herramientaEnUso.getTipo () == ColeccionHerramientas.LINEA)
        {
          // A cada llamada, pintamos la línea en el lienzo limpio.
          gestorGraficos.resetNuevaImagen ();
          gestorGraficos.getGraficosEnPantalla ().setColor (herramientaEnUso.getColorBorde ());
        }
        else
        {
          // Preparamos las coordenadas para el siguiente trazo.
          xInicial = x;
          yInicial = y;

          // Si la herramienta es la goma, el color del trazo será blanco.
          if (herramientaEnUso.getTipo () == ColeccionHerramientas.LAPIZ)
            gestorGraficos.getGraficosEnPantalla ().setColor (herramientaEnUso.getColorBorde ());
          else
            gestorGraficos.getGraficosEnPantalla ().setColor (Color.white);
        }
        // Pintar el polígono.
        gestorGraficos.getGraficosEnPantalla ().fillPolygon (ejesX, ejesY, 4);
        break;
      case ColeccionHerramientas.RECTANGULO: case ColeccionHerramientas.OVALO:
        // Agrupamos el rectángulo y el óvalo en el mismo 'case' porque, para pintar,
        // los dos necesitan saber las coordenadas de dos esquinas opuestas del rectángulo
        // que comprende la figura.
        // Limpiamos la imagen de la figura que acabamos de dibujar.
        gestorGraficos.resetNuevaImagen ();
        // Averiguamos las coordenadas de la esquina superior izquierda del rectángulo que comprenderá
        // la nueva figura.
        int xEsquinaSuperiorIzq = (xInicial > x) ? x : xInicial;
        int yEsquinaSuperiorIzq = (yInicial > y) ? y : yInicial;
        // Primero pintamos el rectángulo o el óvalo del color del borde.
        gestorGraficos.getGraficosEnPantalla ().setColor (herramientaEnUso.getColorBorde ());
        if (herramientaEnUso.getTipo () == ColeccionHerramientas.RECTANGULO)
          gestorGraficos.getGraficosEnPantalla ().fillRect
              (xEsquinaSuperiorIzq, yEsquinaSuperiorIzq,
               Math.abs (xInicial - x), Math.abs (yInicial - y));
        else
          gestorGraficos.getGraficosEnPantalla ().fillOval
              (xEsquinaSuperiorIzq, yEsquinaSuperiorIzq,
               Math.abs (xInicial - x), Math.abs (yInicial - y));
        // Ahora pintaremos el relleno de la figura.
        byte grosor = herramientaEnUso.getGrueso ();
        gestorGraficos.getGraficosEnPantalla ().setColor (herramientaEnUso.getColorRelleno ());
        if (herramientaEnUso.getTipo () == ColeccionHerramientas.RECTANGULO)
          gestorGraficos.getGraficosEnPantalla ().fillRect
              (xEsquinaSuperiorIzq + grosor, yEsquinaSuperiorIzq + grosor,
               Math.abs (xInicial - x) - grosor * 2, Math.abs (yInicial - y) - grosor * 2);
        else
          gestorGraficos.getGraficosEnPantalla ().fillOval
              (xEsquinaSuperiorIzq + grosor, yEsquinaSuperiorIzq + grosor,
               Math.abs (xInicial - x) - grosor * 2, Math.abs (yInicial - y) - grosor * 2);
    }

    // Repintar el lienzo para que se note el cambio.
    repaint ();
  }

  /** Método de la interfaz 'MouseListener'. Se invoca al poner el cursor del ratón sobre el lienzo.
      Sirve para ajustar la apariencia del ratón según la herramienta.
      @param evento MouseEvent  Evento que ha invocado este método. */
  public void mouseEntered (MouseEvent evento)
  {
    // Ajusta la herramienta en uso, para no equivocarse.
    herramientaEnUso = herramientas.getHerramientaEnUso ();

    switch (herramientaEnUso.getTipo ())
    {
      case ColeccionHerramientas.LAPIZ:
        setCursor (PintaLlop.cursorLapiz);
        break;
      case ColeccionHerramientas.GOMA:
        setCursor (PintaLlop.cursorGoma);
        break;
      case ColeccionHerramientas.TEXTO:
        setCursor (PintaLlop.cursorTexto);
        break;
      default:
        setCursor (PintaLlop.cursorCruz);
    }
  }
  /** Método de la interfaz 'MouseListener'. Invocado al sacar el cursor del ratón del lienzo. No hace nada.
      @param evento MouseEvent  Evento que ha invocado este método. */
  public void mouseExited(MouseEvent evento) {}
  /** Método de la interfaz 'MouseListener'. Invocado al pulsar y soltar un botón del ratón. No hace nada.
      @param evento MouseEvent  Evento que ha invocado este método. */
  public void mouseClicked(MouseEvent evento) {}
  /** Método de la interfaz 'MouseMotionListener'. Invocado al mover el ratón por el lienzo. No hace nada.
      @param evento MouseEvent  Evento que ha invocado este método. */
  public void mouseMoved(MouseEvent evento) {}

  /** Método de la interfaz 'KeyListener'. Invocado al pulsar una tecla. No hace nada.
      @param evento KeyEvent  Evento del teclado que ha invocado este método. */
  public void keyPressed (KeyEvent evento) {}
  /** Método de la interfaz 'KeyListener'. Invocado al dejar de pulsar una tecla. No hace nada.
      @param evento KeyEvent  Evento del teclado que ha invocado este método. */
  public void keyReleased (KeyEvent evento) {}
  /** Método de la interfaz 'KeyListener'. Invocado al pulsar y soltar una tecla.
      Sirve para dibujar texto en el lienzo.
      @param evento KeyEvent  Evento del teclado que ha invocado este método. */
  public void keyTyped (KeyEvent evento)
  {
    // Si no estamos escribiendo, o no queda memoria, no necesitamos seguir.
    if (!escribiendo || !quedaMemoria)
      return;

    // La letra picada.
    char letra = evento.getKeyChar ();

    // Si se ha pulsado 'Enter', entendemos que se ha terminado de entrar texto.
    if (letra == '\n')
    {
      escribiendo = false;
      if (herramientaEnUso.getCadena ().equals ("|"))
        herramientaEnUso.setCadena ("");
      quedaMemoria = gestorGraficos.actualiza ();
      return;
    }

    // Filtramos los caracteres que no son número o letra.
    if (!Character.isLetterOrDigit (letra))
      return;

    // Añadimos en nuevo caracter a la cadena de texto, y pintamos ésta en el lienzo.
    if (herramientaEnUso.getCadena ().equals ("|"))
      herramientaEnUso.setCadena ("");
    herramientaEnUso.setCadena (herramientaEnUso.getCadena () + Character.toString (letra));
    pintaTexto ();
  }

  /** Método que pinta en el lienzo el texto que está picando el usuario. */
  public void pintaTexto ()
  {
    // Limpiar la imagen.
    gestorGraficos.resetNuevaImagen ();
    // Establecer el color y la fuente del texto.
    gestorGraficos.getGraficosEnPantalla ().setColor (herramientaEnUso.getColorBorde ());
    gestorGraficos.getGraficosEnPantalla ().setFont (herramientaEnUso.getFuente ());
    // Pintar el texto.
    gestorGraficos.getGraficosEnPantalla ().drawString
        (herramientaEnUso.getCadena (), xInicial, yInicial);

    // Repintar el lienzo.
    repaint ();
  }

  /** Función sobrecargada para pintar en el lienzo -la imagen se obtiene del gestor de los gráficos.
      También pinta un borde negro.
      @param graficos Graphics  Contexto 'Graphics' sobre el que pintamos. */
  public void paint (Graphics graficos)
  {
    super.paint (graficos);
    if (gestorGraficos.getImagenEnPantalla () != null)
      graficos.drawImage (gestorGraficos.getImagenEnPantalla (), 0, 0, null);
    graficos.setColor (Color.black);
    graficos.drawRect (0, 0, getSize ().width - 1, getSize ().height - 1);
    graficos.drawRect (1, 1, getSize ().width - 3, getSize ().height - 3);
  }

  /** Método de la interfaz 'Transferable'. Devuelve un objeto que representa los datos 'transferibles'
      de la clase.
      @param flavor DataFlavor  Determina la clase del objeto devuelto.
      @return Object            Imagen en el lienzo. */
  public Object getTransferData (DataFlavor flavor)
  {
    // Hay que mirar si el 'DataFlavour' es de los aceptados.
    if (!isDataFlavorSupported (flavor))
      return null;
    return getDibujo ();
  }
  /** Método de la interfaz 'Transferable'.
      Devuelve un array de objetos 'DataFlavor', que indican los 'sabores' -o clases contenedoras-
      en que se pueden proporcionar los datos transferibles.
      @return DataFlavor []  Los diferentes 'DataFlavors' en que se pueden devolver los datos. */
  public DataFlavor [] getTransferDataFlavors ()
  {
    return new DataFlavor [] { DataFlavor.imageFlavor };
  }
  /** Método de la interfaz 'Transferable'. Permite conocer si un 'DataFlavor' está permitido
      para este objeto. Esta clase sólo permitirá 'DataFlavor.imageFlavor's.
      @param flavor DataFlavor  El 'DataFlavor' sometido a prueba.
      @return boolean           'true' si el 'DataFlavor' está permitido; 'false' si no. */
  public boolean isDataFlavorSupported (DataFlavor flavor)
  {
    if (flavor == DataFlavor.imageFlavor)
      return true;
    return false;
  }
}



Clase GestorGraficos

package pintallop;

import java.awt.*;

/** Clase que gestiona lo que se ha dibujado, se dibuja, y se puede dibujar en un lienzo:
    va tomando 'instantáneas' del lienzo a medida que se va dibujando en él;
    esas instantáneas se almacenan en objetos 'InstanciaGrafica', una clase interna de 'GestorGraficos'.
    Así, puede cambiar la imagen del lienzo con sus funciones 'deshacer ()' y 'rehacer ()'.
    Eso sí, tiene un límite de 20 instantáneas por lienzo -no recordará más cambios. */
public class GestorGraficos
{
  // Lienzo cuyos dibujos gestiona esta clase.
  private Lienzo lienzo;
  // Array de instancias gráficas para llevar cuenta de los cambios que se van realizando en el lienzo.
  private InstanciaGrafica instanciaGrafica [];

  // Número de 'InstanciaGrafica's disponibles a través de las acciones 'deshacer' y 'rehacer'.
  private byte instanciasGraficasDisponibles;
  // La instancia gráfica visible en el lienzo.
  private byte instanciaGraficaEnPantalla;

  /** Constructor. Necesita saber el lienzo cuyos dibujos va a gestionar.
      @param nuevoLienzo Lienzo  El lienzo propietario de este gestor de gráficos. */
  public GestorGraficos (Lienzo nuevoLienzo)
  {
    lienzo = nuevoLienzo;
    // Inicializamos el resto variables a sus valores por defecto.
    instanciasGraficasDisponibles = 0;
    instanciaGraficaEnPantalla = 0;
    instanciaGrafica = new InstanciaGrafica [20];
    for (byte i = 0; i < 20; i++)
      instanciaGrafica [i] = new InstanciaGrafica ();
  }

  /** Crea una imagen por defecto para la instancia gráfica en pantalla.
      Se utiliza para la primera 'instanciaGrafica'. */
  public void inicializaGestorGraficos ()
  {
    instanciaGrafica [instanciaGraficaEnPantalla].setImagen
        (lienzo.createImage (lienzo.getSize ().width, lienzo.getSize ().height));
    lienzo.repaint ();
  }

  /** Registra un cambio en la imagen del lienzo, creando una nueva imagen para pintar en ella.
      Puede pasar que, si se han pintado ya muchos lienzos, se termine la memoria disponible;
      por eso, se comprueba si queda poca memoria antes de crear esa imagen.
      @return boolean  'true' si queda memoria y se ha podido actualizar el lienzo; 'false' si no. */
  public boolean actualiza ()
  {
    // Imaginemos esta situación:
    // El usuario dibuja 4 trazos, y se da cuenta de que el segundo no queda bien.
    // 'Deshace' tres veces para volver al lienzo antes del trazo malo.
    // Entonces, al dibujar ese trazo bien, las 'InstanciaGrafica's que iban después del
    // trazo malo no sirven, pues ya no tendría sentido 'rehacer' el último 'deshacer'.
    if (puedeRehacer ())
      for (byte i = (byte) (instanciaGraficaEnPantalla + 1); i <= instanciasGraficasDisponibles; i++)
      {
        instanciaGrafica [i] = null;
        instanciaGrafica [i] = new InstanciaGrafica ();
      }

    // Si 'instanciaGraficaEnPantalla' representa la última 'InstanciaGrafica' del array,
    // ya no es necesario ir creando más imágenes. En vez de eso, corremos todas las imágenes
    // una posición adelante para que se pueda pintar la última.
    if (instanciaGraficaEnPantalla == 19)
      for (byte i = 0; i < 19; i++)
        instanciaGrafica [i].graficosImagen.drawImage (instanciaGrafica [i + 1].imagen, 0, 0, null);
    else
    {
      // Llegado este punto, tenemos que crear una nueva imagen; pero primero nos aseguramos
      // que hay suficiente memória volátil disponible para ella.
      Runtime ejecucion = Runtime.getRuntime ();
      // El 'basurero' se da una vuelta para eliminar objetos no utilizados, y ganar espacio en memoria.
      ejecucion.gc ();
      // Las unidades para tratar la memoria son los 'Kbs' (1024 bytes).
      int memoriaUsada = (int) ((ejecucion.totalMemory () - ejecucion.freeMemory ()) / 1024);
      // Aquí comprobamos si queda poca memoria: mi criterio es dar un par de Megas de márgen.
      if (ejecucion.maxMemory () / 1024 - 2000 < memoriaUsada)
        return false;

      // Queda suficiente memoria: Ajustamos las variables contador, y creamos la nueva imagen
      // -que mostrará lo mismo que lo que acabamos de dibujar.
      instanciasGraficasDisponibles = ++instanciaGraficaEnPantalla;
      instanciaGrafica [instanciaGraficaEnPantalla].setImagen
          (lienzo.createImage (lienzo.getSize ().width, lienzo.getSize ().height));
      resetNuevaImagen ();
    }
    // Final feliz.
    return true;
  }

  /** Método que pinta en la instancia gráfica en pantalla la anterior imagen. */
  public void resetNuevaImagen ()
  {
    instanciaGrafica [instanciaGraficaEnPantalla].graficosImagen.drawImage
        (instanciaGrafica [instanciaGraficaEnPantalla - 1].imagen, 0, 0, null);
  }

  /** Devuelve la imagen que se puede ver en el lienzo.
      @return Image  La imagen en el lienzo. */
  public Image getImagenEnPantalla ()
  {
    return instanciaGrafica [instanciaGraficaEnPantalla].imagen;
  }

  /** Devuelve el conexto 'Graphics2D' para pintar la imagen en pantalla.
      @return Graphics2D  'Graphics2D' de la imagen en el lienzo. */
  public Graphics2D getGraficosEnPantalla ()
  {
    return instanciaGrafica [instanciaGraficaEnPantalla].graficosImagen;
  }

  /** Informa sobre la posiblilidad de deshacer el último cambio o no en el lienzo.
      @return boolean  'true' si es posible 'deshacer'; 'false' si no. */
  public boolean puedeDeshacer ()
  {
    // Se puede deshacer siempre que la imagen no sea la primera de todas.
    return instanciaGraficaEnPantalla > 0;
  }

  /** Informa sobre la posiblilidad de rehacer el último cambio o no en el lienzo.
      @return boolean  'true' si es posible 'rehacer'; 'false' si no. */
  public boolean puedeRehacer ()
  {
    // Si el último cambio ha sido un 'deshacer', se podrá 'rehacer'; si no, no.
    return instanciaGraficaEnPantalla < instanciasGraficasDisponibles;
  }

  /** Pinta en el lienzo la imagen de la anterior instancia gráfica. */
  public void deshacer ()
  {
    instanciaGraficaEnPantalla--;
    lienzo.repaint ();
  }

  /** Pinta en el lienzo la imagen de la siguiente instancia gráfica. */
  public void rehacer ()
  {
    instanciaGraficaEnPantalla++;
    lienzo.repaint ();
  }

  /** Clase que representa una 'instantánea' del lienzo, y su contexto 'Graphics2D' (que se
      encargará de pintarla).
      Internamente, es una imagen con método 'setter', y su contexto 'Graphics2D'.
      Todas sus variables son privadas porque esta clase es para uso exclusivo de 'GestorGraficos'. */
  class InstanciaGrafica
  {
    // La imagen.
    private Image imagen;
    // Gráficos para pintar sobre el lienzo.
    private Graphics2D graficosImagen;

    /** Constructor. La imagen, al principio, es 'null'. */
    private InstanciaGrafica ()
    {
      imagen = null;
      graficosImagen = null;
    }

    /** Método 'setter' para la imagen.
        @param nuevaImagen Image  La nueva imagen. */
    private void setImagen (Image nuevaImagen)
    {
      imagen = nuevaImagen;
      // Inicializamos los 'graficosImagen' que se utilizarán para pintar.
      graficosImagen = (Graphics2D) imagen.getGraphics ();
      graficosImagen.addRenderingHints (new RenderingHints (RenderingHints.KEY_ANTIALIASING,
          RenderingHints.VALUE_ANTIALIAS_ON));
    }
  }
}

¿Comentarios, sugerencias?: llopsite.at.yahoo.es | © 2005-07 Albert Lobo

Última actualización: 18-Feb-2007

Hosted by www.Geocities.ws

1