/*
* Beanfabrics Framework Copyright (C) by Michael Karneim, beanfabrics.org
* Use is subject to license terms. See license.txt.
*/
// TODO javadoc - remove this comment only when the class and all non-public
// methods and fields are documented
package org.beanfabrics.swing.internal;
import java.io.Serializable;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;
import javax.swing.undo.UndoManager;
import org.beanfabrics.View;
import org.beanfabrics.event.WeakPropertyChangeListener;
import org.beanfabrics.model.AbstractPM;
import org.beanfabrics.model.ITextPM;
import org.beanfabrics.model.PresentationModel;
import org.beanfabrics.model.TextPM;
import org.beanfabrics.util.ExceptionUtil;
/**
* The <code>BnPlainDocument</code> is a {@link PlainDocument} which is a
* {@link View} on a {@link ITextPM}.
*
* @author Michael Karneim
* @author Max Gensthaler
* @author Adrodoc55
*/
@SuppressWarnings("serial")
public class BnPlainDocument extends PlainDocument implements View<ITextPM> {
/**
* Value <code>true</code> avoids event cycles between the document and the
* {@link TextPM}.
*/
private boolean pending_modelChange = false;
/**
* Setting this to <code>true</code> disables the delegation (to the model)
* inside the {@link #remove(int, int)} method.
*/
private boolean suppressRemoveEvent = false;
protected ITextPM pModel;
private final WeakPropertyChangeListener propertyListener = new MyWeakPropertyChangeListener();
private class MyWeakPropertyChangeListener implements WeakPropertyChangeListener, Serializable {
public void propertyChange(java.beans.PropertyChangeEvent evt) {
if (pending_modelChange == false) { // avoid event cycle
try {
pending_modelChange = true;
BnPlainDocument.this.refresh();
} finally {
pending_modelChange = false;
}
}
}
}
/**
* This {@link DocumentListener} listenes for changes to this document, that are not caused by
* this document. For instance changes performed by an {@link UndoManager}.
*/
private final DocumentListener documentListener = new MyDocumentListener();
private class MyDocumentListener implements DocumentListener, Serializable {
@Override
public void removeUpdate(DocumentEvent e) {
if (pending_modelChange == false) {
updatePM();
}
}
@Override
public void insertUpdate(DocumentEvent e) {
if (pending_modelChange == false) {
updatePM();
}
}
@Override
public void changedUpdate(DocumentEvent e) {
if (pending_modelChange == false) {
updatePM();
}
}
};
public BnPlainDocument() {
super();
addDocumentListener(documentListener);
}
public BnPlainDocument(ITextPM pModel) {
this();
this.setPresentationModel(pModel);
}
/** {@inheritDoc} */
public ITextPM getPresentationModel() {
return this.pModel;
}
/** {@inheritDoc} */
public void setPresentationModel(ITextPM pModel) {
disconnect();
this.pModel = pModel;
connect();
try {
pending_modelChange = true;
this.refresh();
} finally {
pending_modelChange = false;
}
}
/**
* Returns whether this component is connected to the target
* {@link PresentationModel} to synchronize with. This is a convenience
* method.
*
* @return <code>true</code> when this component is connected, else
* <code>false</code>
*/
public boolean isConnected() {
return pModel != null;
}
private void disconnect() {
if (this.isConnected()) {
this.pModel.removePropertyChangeListener("text", this.propertyListener);
}
}
private void connect() {
if (this.pModel != null) {
this.pModel.addPropertyChangeListener("text", this.propertyListener);
}
}
/**
* Configures this component depending on the target {@link AbstractPM}s
* attributes.
*/
protected void refresh() {
try {
String edText = this.isConnected() ? this.pModel.getText() : "";
this.applyText(edText);
} catch (BadLocationException e) {
throw new RuntimeException(e.getMessage());
}
}
private void applyText(String text)
throws BadLocationException {
String oldText = this.getText(0, this.getLength());
if (oldText.equals(text) == false) {
this.suppressRemoveEvent = true; // do not synchronize
try {
// since'model.insertString' follows
this.remove(0, this.getLength());
} finally {
this.suppressRemoveEvent = false;
}
this.insertString(0, text, null);
}
}
public void remove(int offs, int len) throws BadLocationException {
try {
try {
pending_modelChange = true;
super.remove(offs, len);
} finally {
pending_modelChange = false;
}
if (suppressRemoveEvent == false) {
updatePM();
}
} catch (BadLocationException ex) {
throw ex;
} catch (Throwable t) {
ExceptionUtil.getInstance().handleException("Error during editing.", t);
}
}
public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
try {
pending_modelChange = true;
super.insertString(offs, str, a);
} finally {
pending_modelChange = false;
}
updatePM();
}
public void setSuppressRemoveEvent(boolean suppressRemoveEvent) {
this.suppressRemoveEvent = suppressRemoveEvent;
}
private void updatePM() {
try {
if (!isConnected()) {
return;
}
String modelText = this.pModel.getText();
String newText = this.getText(0, this.getLength());
if (modelText.equals(newText) == false) {
this.pModel.setText(newText);
}
} catch (Throwable t) {
ExceptionUtil.getInstance().handleException("Error during editing.", t);
}
}
}