/*
* Copyright 2003-2010 Tufts University Licensed under the
* Educational Community License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may
* obtain a copy of the License at
*
* http://www.osedu.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package tufts.vue.gui;
import tufts.vue.*;
import tufts.Util;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;
/**
* Provides an editable multi-line text panel for a named LWComponent property.
* Automatically saves the text upon focus loss if there was any change,
* and enters an undo entry.
*
* @author Scott Fraize
* @version $Revision: 1.20 $ / $Date: 2010-02-03 19:15:46 $ / $Author: mike $
*/
// todo: create an abstract class for handling property & undo code, and subclass this and VueTextField from it.
// or: a handler/listener that can be attached to any text field.
// todo: consume all key events
// todo: rename LWTextPane, as is specific to to LWComponent text properties
// todo: should just be an LWEditor (might create a fancier global mechanism for updating them)
public class VueTextPane extends JTextPane
implements LWComponent.Listener
{
private static final org.apache.log4j.Logger Log = org.apache.log4j.Logger.getLogger(VueTextPane.class);
private LWComponent lwc;
private Object propertyKey;
/** was a key pressed since we loaded the current text? */
private boolean keyWasPressed = false; // TODO: also need to know if cut or paste happened!
private boolean styledText = false;
private String undoName;
private String loadedText;
public VueTextPane(LWComponent c, Object propertyKey, String undoName)
{
addFocusListener(new FocusAdapter() {
public void focusLost(FocusEvent e) { saveText(e); }
});
if (c != null && propertyKey != null)
attachProperty(c, propertyKey);
setUndoName(undoName);
GUI.installBorder(this);
if (propertyKey != null)
setName(propertyKey.toString());
else if (undoName != null)
setName(undoName);
}
public VueTextPane(String undoName) {
this(null, null, undoName);
}
public VueTextPane() {
this(null, null, null);
}
private void debug(String s) {
Log.debug(String.format("%08X[%s/%s] %s", System.identityHashCode(this), getName(), propertyKey, s));;
}
/** We override this to do nothing, so that default focus traversal keys are left in
* place (and so you can't use TAB in this class. See java 1.4 JEditorPane constructor
* where it installs JComponent.getManagingFocus{Forward,Backward}TraversalKeys().
* This doesn't work for java 1.5 -- will have to override LookAndFeel.installProperty
* for that.
*/
@Override
public void setFocusTraversalKeys(int id, java.util.Set keystrokes) {
if (DEBUG.FOCUS) System.out.println(this + " ignoring setFocusTraversalKeys " + id + " " + keystrokes);
}
public void XsetName(String s) {
tufts.Util.printStackTrace("setName " + s);
super.setName(s);
}
@Override public void setText(String s) {
if (DEBUG.TEXT) debug("setText: curText=" + Util.tags(getText()) + " newText=" + Util.tags(s));
super.setText(s);
}
@Override
protected void processKeyEvent(KeyEvent e) {
if (DEBUG.KEYS && e.getID() == KeyEvent.KEY_PRESSED) Log.debug("processKeyEvent " + GUI.name(e) + "; " + this);
// if any key activity, assume it may have changed
// (to make sure we catch cut's and paste's as well newly input characters)
//if (DEBUG.TEXT) debug("curTextA " + Util.tags(getText()));
if (e.getKeyCode() == KeyEvent.VK_ENTER && e.getKeyLocation() == KeyEvent.KEY_LOCATION_NUMPAD) {
saveText("numpad-enter");
} else {
keyWasPressed = true;
super.processKeyEvent(e);
}
//if (DEBUG.TEXT) debug("curTextZ " + Util.tags(getText()));
}
public void attachProperty(LWComponent c, Object key) {
if (c == null || key == null)
throw new IllegalArgumentException("component=" + c + " propertyKey="+key + " neither can be null");
saveText("attach");
if (lwc == c && propertyKey == key)
return;
if (lwc != null)
lwc.removeLWCListener(this);
lwc = c;
propertyKey = key;
loadPropertyValue();
if (DEBUG.TEXT||DEBUG.EVENTS) debug("attachProperty " + Util.tags(key) + "; " + c);
lwc.addLWCListener(this, propertyKey, LWKey.Deleting);
keyWasPressed = false;
}
public void detachProperty() {
//saveText("detach"); // overkill & causing problems w/multi-label text edit (check note panel for any possible new problems) [BREAKS NOTES!]
saveText("detach");
if (lwc != null) {
if (DEBUG.TEXT||DEBUG.EVENTS) debug("detach from " + lwc);
//Util.printStackTrace("DETACH");
lwc.removeLWCListener(this);
lwc = null;
}
setText("");
//saveText("autoDetach");
}
/** an optional special undo name for this property */
public void setUndoName(String name) {
undoName = name;
}
// TODO: DROP OF TEXT (this is a paste, but with no keypress!)
protected void saveText(Object src) {
final String currentText = getText();
if (DEBUG.TEXT||DEBUG.EVENTS) debug("saveText;"
+ "\n\tsrc=" + tufts.Util.tags(src)
+ "\n\t" + this
+ "\n\tcurText=[" + currentText + "]"
);
// TODO: this is missing cases where text hasn't changed,
// causing multple needless text-set events (e.g., hit
// "enter", then focus-loss w/out changing text, and an apply
// cycle still takes place)
if (keyWasPressed || !currentText.equals(loadedText)) {
//if (lwc != null && (keyWasPressed || !currentText.equals(loadedText))) {
//if (DEBUG.KEYS||DEBUG.TEXT|DEBUG.EVENTS) Log.debug("saveText: APPLYING TEXT: " + this);
// rtfTestHack();
if (DEBUG.EVENTS) debug(String.format("saveText: apply %s -> %s", Util.tags(propertyKey), lwc));
applyText(currentText);
}
}
public void loadText(String text) {
setText(text);
loadedText = text;
}
protected void applyText(String text) {
if (lwc == null)
return;
if (DEBUG.EVENTS || DEBUG.TEXT) debug(String.format("applyText: '%.16s'... to: %s -> %s", text, Util.tags(propertyKey), lwc));
lwc.setProperty(propertyKey, text);
loadedText = text;
if (undoName != null)
VUE.markUndo(undoName);
else
VUE.markUndo();
}
// private void rtfTestHack() {
// Document doc = getDocument();
// String text = null;
// try {
// if (DEBUG.KEYS) System.out.println(this + " saveText [" + doc.getText(0, doc.getLength()) + "]");
// java.io.ByteArrayOutputStream buf = new java.io.ByteArrayOutputStream();
// //java.io.CharArrayWriter buf = new java.io.CharArrayWriter(); // RTFEditorKit won't write 16 bit characters.
// // But it turns out it still handles unicode via self-encoding the special chars.
// getEditorKit().write(buf, doc, 0, doc.getLength());
// text = buf.toString();
// if (DEBUG.KEYS) System.out.println(this + " EDITOR KIT OUTPUT [" + text + "]");
// } catch (Exception e) {
// e.printStackTrace();
// }
// lwc.setProperty(propertyKey, text);
// }
private void loadPropertyValue() {
String text = null;
if (lwc != null) {
try {
if (propertyKey == LWKey.Label && lwc.getLabelFormat() != null) {
text = lwc.getLabelFormat();
} else {
text = (String) lwc.getPropertyValue(propertyKey);
}
} catch (ClassCastException e) {
throw new IllegalArgumentException("VueTextPane only handles properties of type String");
}
setEnabled(lwc.supportsProperty(propertyKey));
} else {
setEnabled(false);
}
if (text == null) {
loadText("");
} else {
loadText(text);
}
}
public void LWCChanged(LWCEvent e)
{
if (e.getComponent() == lwc) {
if (e.key == LWKey.Deleting) {
lwc.removeLWCListener(this);
lwc = null;
propertyKey = null;
}
loadPropertyValue();
}
}
private void enableStyledText() {
if (styledText)
return;
styledText = true;
String text = getText();
System.out.println("text[" + text + "]");
setContentType("text/rtf");
try {
read(new java.io.StringReader(text), "description");
} catch (Exception e) {
e.printStackTrace();
}
//setText(text);
}
private class BoldAction extends StyledEditorKit.BoldAction {
public void actionPerformed(ActionEvent e) {
//enableStyledText();
super.actionPerformed(e);
}
}
public void addNotify() {
super.addNotify();
if (getContentType().equalsIgnoreCase("text/rtf")) {
int COMMAND = VueUtil.isMacPlatform() ? Event.META_MASK : Event.CTRL_MASK;
System.out.println("ADDING STYLED TEXT KEYMAP");
Keymap parentMap = getKeymap();
Keymap map = JTextComponent.addKeymap("MyFontStyleMap", parentMap);
// Add CTRL-B for Bold
KeyStroke boldStroke = KeyStroke.getKeyStroke(KeyEvent.VK_B, COMMAND, false);
map.addActionForKeyStroke(boldStroke, new StyledEditorKit.BoldAction());
// Add CTRL-I for Italic
KeyStroke italicStroke = KeyStroke.getKeyStroke(KeyEvent.VK_I, COMMAND, false);
map.addActionForKeyStroke(italicStroke, new StyledEditorKit.ItalicAction());
// Add CTRL-U for Underline
KeyStroke underlineStroke = KeyStroke.getKeyStroke(KeyEvent.VK_U, COMMAND, false);
map.addActionForKeyStroke(underlineStroke, new StyledEditorKit.UnderlineAction());
setKeymap(map);
}
}
public static void main(String args[]) {
VUE.init(args);
VueUtil.displayComponent(new VueTextField("some text"));
DockWindow w = GUI.createDockWindow(VueResources.getString("dockWindow.vueTextpanetest.title"));
javax.swing.JPanel panel = new javax.swing.JPanel();
VueTextPane tp1 = new VueTextPane();
VueTextPane tp2 = new VueTextPane();
VueTextPane tp3 = new VueTextPane();
panel.add(tp1);
panel.add(tp2);
panel.add(tp3);
//vtp.setEditable(true);
w.setContent(panel);
w.setVisible(true);
VueUtil.displayComponent(new VueTextPane(new LWMap("Test Map"), LWKey.Notes, null));
}
public String toString()
{
return "VueTextPane[" + propertyKey + "; " + lwc + "; keyWasPressed=" + keyWasPressed + "]";
}
}