/*
ItsNat Java Web Application Framework
Copyright (C) 2007-2011 Jose Maria Arranz Santamaria, Spanish citizen
This software is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 3 of
the License, or (at your option) any later version.
This software is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details. You should have received
a copy of the GNU Lesser General Public License along with this program.
If not, see <http://www.gnu.org/licenses/>.
*/
package org.itsnat.impl.comp.text;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import org.itsnat.impl.comp.ItsNatHTMLFormCompValueBasedImpl;
import org.itsnat.impl.comp.ItsNatHTMLFormCompChangeBasedSharedImpl;
import org.itsnat.core.ItsNatException;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.itsnat.core.event.CustomParamTransport;
import org.itsnat.core.event.ItsNatEvent;
import org.itsnat.core.event.ParamTransport;
import org.itsnat.impl.core.clientdoc.ClientDocumentImpl;
import org.w3c.dom.events.Event;
/**
*
* @author jmarranz
*/
public class ItsNatHTMLFormTextCompSharedImpl extends ItsNatHTMLFormCompChangeBasedSharedImpl
{
/**
* Creates a new instance of ItsNatTextBasedSharedImpl
*/
public ItsNatHTMLFormTextCompSharedImpl(ItsNatHTMLFormTextComponentInternal comp)
{
super(comp);
}
public void writeListeners(ObjectOutputStream out) throws IOException
{
// Este c�digo es debido a que hay un bug al de-serializar AbstractDocument, el atributo
// "EventListenerList listenerList" que es serializable se pierde al hacer absurdamente
// un: listenerList = new EventListenerList(); tras deserializarlo, perdiendo los listeners
// Este error est� confirmado en 1.4.2_16 y en 1.6.0_18
// Sabemos que se insertan en esta colecci�n los DocumentListener y UndoableEditListener
// los DocumentListener se usan en ItsNat pero incluimos UndoableEditListener por gentileza
// al programador.
DocumentListener[] docListeners = null;
UndoableEditListener[] undoListeners = null;
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
javax.swing.text.Document dataModel = comp.getDocument();
if (dataModel instanceof AbstractDocument)
{
docListeners = ((AbstractDocument)dataModel).getDocumentListeners();
if (docListeners.length == 0)
docListeners = null;
undoListeners = ((AbstractDocument)dataModel).getUndoableEditListeners();
if (undoListeners.length == 0)
undoListeners = null;
}
out.writeObject(docListeners);
out.writeObject(undoListeners);
}
public void readListeners(ObjectInputStream in) throws IOException, ClassNotFoundException
{
DocumentListener[] docListeners = (DocumentListener[])in.readObject();
if (docListeners != null && docListeners.length > 0)
{
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
AbstractDocument dataModel = (AbstractDocument)comp.getDocument();
if (dataModel.getDocumentListeners().length == 0)
{
// Persiste el bug, no ha sido arreglado pues hemos salvado alg�n listener
for(int i = 0; i < docListeners.length; i++)
{
DocumentListener listener = docListeners[i];
dataModel.addDocumentListener(listener);
}
}
}
UndoableEditListener[] undoListeners = (UndoableEditListener[])in.readObject();
if (undoListeners != null && undoListeners.length > 0)
{
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
AbstractDocument dataModel = (AbstractDocument)comp.getDocument();
if (dataModel.getUndoableEditListeners().length == 0)
{
// Persiste el bug, no ha sido arreglado pues hemos salvado alg�n listener
for(int i = 0; i < undoListeners.length; i++)
{
UndoableEditListener listener = undoListeners[i];
dataModel.addUndoableEditListener(listener);
}
}
}
}
public ItsNatHTMLFormTextComponentInternal getItsNatHTMLFormTextComponentInternal()
{
return (ItsNatHTMLFormTextComponentInternal)comp;
}
public boolean isIgnoreChangeEvent(ClientDocumentImpl clientDoc)
{
return false;
}
public void bindDataModel()
{
// A partir de ahora los cambios los repercutimos en el DOM por eventos
// No se debe cambiar el DOM por otra v�a que por el objeto dataModel
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
Document dataModel = comp.getDocument();
dataModel.addDocumentListener(comp);
}
public void unbindDataModel()
{
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
Document dataModel = comp.getDocument();
dataModel.removeDocumentListener(comp);
}
public void initialSyncUIWithDataModel()
{
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
ItsNatHTMLFormTextCompUIImpl compUI = comp.getItsNatHTMLFormTextCompUIImpl();
Document dataModel = comp.getDocument();
String str;
try
{
str = dataModel.getText(0,dataModel.getLength());
}
catch(BadLocationException ex)
{
throw new ItsNatException(ex,comp);
}
ItsNatHTMLFormCompValueBasedImpl compBase = (ItsNatHTMLFormCompValueBasedImpl)comp; // A d�a de hoy todos los componentes texto son elementos de formulario HTML, en el futuro ya veremos
// Sincronizamos con el DOM
boolean wasDisabled = compBase.disableSendCodeToRequesterIfServerUpdating();
try
{
compUI.setText(str);
}
finally
{
if (wasDisabled) compBase.enableSendCodeToRequester();
}
}
public void insertUpdate(DocumentEvent e)
{
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
ItsNatHTMLFormCompValueBasedImpl compBase = (ItsNatHTMLFormCompValueBasedImpl)comp; // A d�a de hoy todos los componentes texto son elementos de formulario HTML, en el futuro ya veremos
if (!compBase.isUIEnabled()) return;
ItsNatHTMLFormTextCompUIImpl compUI = comp.getItsNatHTMLFormTextCompUIImpl();
// Sincronizamos con el DOM
Document dataModel = e.getDocument();
int offset = e.getOffset();
int len = e.getLength();
String str;
try
{
str = dataModel.getText(offset,len);
}
catch(BadLocationException ex)
{
throw new ItsNatException(ex,comp);
}
// Sincronizamos con el DOM
boolean wasDisabled = compBase.disableSendCodeToRequesterIfServerUpdating();
try
{
compUI.insertString(offset,str);
}
finally
{
if (wasDisabled) compBase.enableSendCodeToRequester();
}
}
public void removeUpdate(DocumentEvent e)
{
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
ItsNatHTMLFormCompValueBasedImpl compBase = (ItsNatHTMLFormCompValueBasedImpl)comp; // A d�a de hoy todos los componentes texto son elementos de formulario HTML, en el futuro ya veremos
if (!compBase.isUIEnabled()) return;
ItsNatHTMLFormTextCompUIImpl compUI = comp.getItsNatHTMLFormTextCompUIImpl();
// Sincronizamos con el DOM
int offset = e.getOffset();
int len = e.getLength();
boolean wasDisabled = compBase.disableSendCodeToRequesterIfServerUpdating();
try
{
compUI.removeString(offset,len);
}
finally
{
if (wasDisabled) compBase.enableSendCodeToRequester();
}
}
public void changedUpdate(DocumentEvent e)
{
// No hacemos nada pues no se gestionan atributos, en un futuro...
}
public String getText()
{
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
Document dataModel = comp.getDocument();
return getText(0,dataModel.getLength());
}
public String getText(int offs, int len)
{
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
return getText(comp.getDocument(),offs,len);
}
public static String getText(Document dataModel,int offs, int len)
{
try
{
return dataModel.getText(offs, len);
}
catch(BadLocationException ex)
{
throw new ItsNatException(ex,dataModel);
}
}
public void setText(String t)
{
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
Document dataModel = comp.getDocument();
setText(dataModel,t);
}
public static void setText(Document dataModel,String t)
{
String old = getText(dataModel,0,dataModel.getLength());
if (t.equals(old))
return; // Evitamos llamar a replaceString el cual (AbstractDocument.replace) aunque no haya cambios elimina el contenido actual y a�ade el nuevo
replaceString(dataModel,t,0,dataModel.getLength());
}
public void appendString(String str)
{
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
Document dataModel = comp.getDocument();
insertString(str,dataModel.getLength());
}
public void replaceString(String str, int start, int end)
{
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
replaceString(comp.getDocument(),str,start,end);
}
public static void replaceString(Document dataModel,String str, int start, int end)
{
if (end < start)
throw new ItsNatException("end before start",dataModel);
try
{
if (dataModel instanceof AbstractDocument)
{
((AbstractDocument)dataModel).replace(start, end - start, str, null);
}
else
{
dataModel.remove(start, end - start);
dataModel.insertString(start, str, null);
}
}
catch (BadLocationException ex)
{
throw new ItsNatException(ex,dataModel);
}
}
public void insertString(String str, int pos)
{
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
Document dataModel = comp.getDocument();
try
{
dataModel.insertString(pos, str, null);
}
catch (BadLocationException ex)
{
throw new ItsNatException(ex,comp);
}
}
public void remove(int pos,int length)
{
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
Document dataModel = comp.getDocument();
try
{
dataModel.remove(pos,length);
}
catch (BadLocationException ex)
{
throw new ItsNatException(ex,comp);
}
}
public void fullChange(String newValue)
{
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
// Llamado por el evento "change"
String oldValue = comp.getText();
if (oldValue.equals(newValue))
return; // No ha habido cambio alguno, evitamos repercutir falsos eventos "change" que al parecer se producen
setText(newValue);
}
public void incrementalChange(String newValue)
{
// Es especialmente �til cuando es llamado por la pulsaci�n de una tecla, dicha
// tecla puede ser un cursor que no cambia el texto,
// un CTRL-V que pega un trozo de texto, un CTRL-X que quita un trozo, el texto seleccionado que
// es cambiado completamente al pegar otro
// etc, adem�s no sabemos donde se ha puesto el cursor inicialmente pues puede
// ponerse en el medio del texto box por ejemplo. Por tanto no es posible
// "a�adir" o "quitar" algo a partir de la tecla pues newValue puede ser muy diferente al valor actual no s�lo con un caracter cambiado.
// Por otra parte un keyup en FireFox por ejemplo env�a el keyCode que es la tecla pero no el charCode (a y A es el mimsmo keyCode)
// tendr�amos que andar viendo si el shift est� activado etc para generar el charCode.
// Por ello cada vez que se pulsa una tecla (keyup) se trae el valor actual del texto en el control,
// no usamos keydown porque dicho valor del control no incluye la posible "nueva" letra (el cambio en general) porque el evento keydown
// es cancelable por lo que hasta que no se procesa totalmente con �xito no se a�ade
// al control en el navegador.
// Suponemos que una sola parte ha cambiado: o bien eliminada, o bien
// insertada o bien substituida
// El objetivo es modificar el Document de forma incremental para que
// un posible listener sepa qu� ha cambiado exactamente
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
String oldValue = comp.getText();
// Si oldValue is igual a newValue se detecta
int lenOld = oldValue.length();
int lenNew = newValue.length();
if (lenOld > lenNew)
{
// Se ha borrado una parte de oldValue
int start; // Posici�n del comienzo de la zona quitada en oldValue
for(start = 0; start < lenNew; start++)
{
if (oldValue.charAt(start) != newValue.charAt(start))
break;
}
int length = lenOld - lenNew;
String newValueCalc = oldValue.substring(0,start) + oldValue.substring(start + length);
if (newValueCalc.equals(newValue))
remove(start,length);
else
fullChange(newValue); // Ha habido un cambio m�s complicado
}
else if (lenOld < lenNew)
{
// Se ha insertado una parte en oldValue
int start; // Posici�n del comienzo de la zona insertada en oldValue
for(start = 0; start < lenOld; start++)
{
if (oldValue.charAt(start) != newValue.charAt(start))
break;
}
int length = lenNew - lenOld;
String strIns = newValue.substring(start,start + length);
String newValueCalc = oldValue.substring(0,start) + strIns + oldValue.substring(start);
if (newValueCalc.equals(newValue))
insertString(strIns,start);
else
fullChange(newValue); // Ha habido un cambio m�s complicado
}
else // Se ha cambiado una parte en oldValue
{
int start; // Posici�n del comienzo de la zona insertada en oldValue
for(start = 0; start < lenOld; start++)
{
if (oldValue.charAt(start) != newValue.charAt(start))
break;
}
if (start >= lenOld)
return; // Son iguales, no ha cambiado nada
int end;
for(end = start; end < lenOld; end++)
{
if (oldValue.charAt(end) == newValue.charAt(end))
break;
}
String strReplace = newValue.substring(start,end);
String newValueCalc = oldValue.substring(0,start) + strReplace + oldValue.substring(start + end - start);
if (newValueCalc.equals(newValue))
replaceString(strReplace,start,end);
else
fullChange(newValue); // Ha habido un cambio m�s complicado
}
}
protected ParamTransport[] getInternalParamTransports(String type,ClientDocumentImpl clientDoc)
{
if (isChangeEvent(type,clientDoc) ||
type.equals("keyup"))
{
// Redefinimos porque es un poco m�s complicado que el c�digo por defecto
CustomParamTransport value = new CustomParamTransport("value","event.getCurrentTarget().value");
return new ParamTransport[]{value};
}
else
return null;
}
public void processDOMEvent(Event evt)
{
super.processDOMEvent(evt);
String type = evt.getType();
if (type.equals("keyup"))
{
// Ejecutado como respuesta al evento "keyup" en el navegador
// No usamos ni keydown ni keypress porque ambos son cancelable y en el cliente
// no se cambia el control hasta que el evento termina pues puede ser
// cancelado y la finalidad aqu� es el de cambiar el DOM para ir sincronizando
// respecto a los cambios del cliente, cuando se emite keyup el control ya ha sido
// cambiado y value tiene el valor actualizado aunque se cancele keyup
handleEventOnKeyUp(evt);
// Idem razones que el evento "change"
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
comp.postHandleEventOnKeyUp(evt);
}
}
public void handleEventOnChange(Event evt)
{
ItsNatEvent itsNatEvent = (ItsNatEvent)evt;
String newValue = (String)itsNatEvent.getExtraParam("value");
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
ItsNatHTMLFormCompValueBasedImpl compBase = (ItsNatHTMLFormCompValueBasedImpl)comp; // A d�a de hoy todos los componentes texto son elementos de formulario HTML, en el futuro ya veremos
compBase.setServerUpdatingFromClient(true); // Pues el evento viene del navegador y no se necesita enviar actualizaciones (salvo observers para que vean el cambio del cliente)
try
{
comp.setNewValueOnChange(newValue,evt);
}
finally
{
compBase.setServerUpdatingFromClient(false);
}
}
public void handleEventOnKeyUp(Event evt)
{
// Si se activ� el evento "keyup" el DOM se actualizar� para cada tecla
ItsNatEvent itsNatEvent = (ItsNatEvent)evt;
String newValue = (String)itsNatEvent.getExtraParam("value");
ItsNatHTMLFormCompValueBasedImpl compBase = (ItsNatHTMLFormCompValueBasedImpl)comp; // A d�a de hoy todos los componentes texto son elementos de formulario HTML, en el futuro ya veremos
compBase.setServerUpdatingFromClient(true); // Pues el evento viene del navegador y no se necesita enviar actualizaciones (salvo observers para que vean el cambio del cliente)
ItsNatHTMLFormTextComponentInternal comp = getItsNatHTMLFormTextComponentInternal();
try
{
comp.setNewValueOnKeyUp(newValue,evt);
}
finally
{
compBase.setServerUpdatingFromClient(false);
}
}
}