edu.umd.cs.piccolox.event.PStyledTextEventHandler Maven / Gradle / Ivy
/*
* Copyright (c) 2008-2011, Piccolo2D project, http://piccolo2d.org
* Copyright (c) 1998-2008, University of Maryland
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
* that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of conditions
* and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions
* and the following disclaimer in the documentation and/or other materials provided with the
* distribution.
*
* None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its
* contributors may be used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package edu.umd.cs.piccolox.event;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import edu.umd.cs.piccolo.PCamera;
import edu.umd.cs.piccolo.PCanvas;
import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.event.PBasicInputEventHandler;
import edu.umd.cs.piccolo.event.PInputEvent;
import edu.umd.cs.piccolo.event.PInputEventFilter;
import edu.umd.cs.piccolox.nodes.PStyledText;
/**
* @author Lance Good
*/
public class PStyledTextEventHandler extends PBasicInputEventHandler {
private static final int TEXT_EDIT_PADDING = 3;
/** Canvas onto which this event handler is attached. */
protected PCanvas canvas;
/** Editor used to edit a PStyledText's content when it is in edit mode. */
protected JTextComponent editor;
/**
* A listener that will handle programatic changes to the underlying
* document and update the view accordingly.
*/
protected DocumentListener docListener;
/** The Styled text being edited. */
protected PStyledText editedText;
/**
* Basic constructor for PStyledTextEventHandler.
*
* @param canvas canvas to which this handler will be attached
*/
public PStyledTextEventHandler(final PCanvas canvas) {
final PInputEventFilter filter = new PInputEventFilter();
filter.setOrMask(InputEvent.BUTTON1_MASK | InputEvent.BUTTON3_MASK);
setEventFilter(filter);
this.canvas = canvas;
initEditor(createDefaultEditor());
}
/**
* Constructor for PStyledTextEventHandler that allows an editor to be
* specified.
*
* @param canvas canvas to which this handler will be attached
* @param editor component to display when editing a PStyledText node
*/
public PStyledTextEventHandler(final PCanvas canvas, final JTextComponent editor) {
super();
this.canvas = canvas;
initEditor(editor);
}
/**
* Installs the editor onto the canvas. Making it the editor that will be
* used whenever a PStyledText node needs editing.
*
* @param newEditor component responsible for a PStyledText node while it is
* being edited.
*/
protected void initEditor(final JTextComponent newEditor) {
editor = newEditor;
canvas.setLayout(null);
canvas.add(editor);
editor.setVisible(false);
docListener = createDocumentListener();
}
/**
* Creates a default editor component to be used when editing a PStyledText
* node.
*
* @return a freshly created JTextComponent subclass that can be used to
* edit PStyledText nodes
*/
protected JTextComponent createDefaultEditor() {
return new DefaultTextEditor();
}
/**
* Returns a document listener that will reshape the editor whenever a
* change occurs to its attached document.
*
* @return a DocumentListener
*/
protected DocumentListener createDocumentListener() {
return new DocumentListener() {
public void removeUpdate(final DocumentEvent e) {
reshapeEditorLater();
}
public void insertUpdate(final DocumentEvent e) {
reshapeEditorLater();
}
public void changedUpdate(final DocumentEvent e) {
reshapeEditorLater();
}
};
}
/**
* Creates a PStyledText instance and attaches a simple document to it. If
* possible, it configures its font information too.
*
* @return a new PStyledText instance
*/
public PStyledText createText() {
final PStyledText newText = new PStyledText();
final Document doc = editor.getUI().getEditorKit(editor).createDefaultDocument();
if (doc instanceof StyledDocument && missingFontFamilyOrSize(doc)) {
final Font eFont = editor.getFont();
final SimpleAttributeSet sas = new SimpleAttributeSet();
sas.addAttribute(StyleConstants.FontFamily, eFont.getFamily());
sas.addAttribute(StyleConstants.FontSize, new Integer(eFont.getSize()));
((StyledDocument) doc).setParagraphAttributes(0, doc.getLength(), sas, false);
}
newText.setDocument(doc);
return newText;
}
private boolean missingFontFamilyOrSize(final Document doc) {
return !doc.getDefaultRootElement().getAttributes().isDefined(StyleConstants.FontFamily)
|| !doc.getDefaultRootElement().getAttributes().isDefined(StyleConstants.FontSize);
}
/**
* A callback that is invoked any time the mouse is pressed on the canvas.
* If the press occurs directly on the canvas, it create a new PStyledText
* instance and puts it in editing mode. If the click is on a node, it marks
* changes it to editing mode.
*
* @param event mouse click event that can be queried
*/
public void mousePressed(final PInputEvent event) {
final PNode pickedNode = event.getPickedNode();
stopEditing(event);
if (event.getButton() != MouseEvent.BUTTON1) {
return;
}
if (pickedNode instanceof PStyledText) {
startEditing(event, (PStyledText) pickedNode);
}
else if (pickedNode instanceof PCamera) {
final PStyledText newText = createText();
final Insets pInsets = newText.getInsets();
newText.translate(event.getPosition().getX() - pInsets.left, event.getPosition().getY() - pInsets.top);
startEditing(event, newText);
}
}
/**
* Begins editing the provided text node as a result of the provided event.
* Will swap out the text node for an editor.
*
* @param event the event responsible for starting the editing
* @param text text node being edited
*/
public void startEditing(final PInputEvent event, final PStyledText text) {
// Get the node's top right hand corner
final Insets pInsets = text.getInsets();
final Point2D nodePt = new Point2D.Double(text.getX() + pInsets.left, text.getY() + pInsets.top);
text.localToGlobal(nodePt);
event.getTopCamera().viewToLocal(nodePt);
// Update the editor to edit the specified node
editor.setDocument(text.getDocument());
editor.setVisible(true);
final Insets bInsets = editor.getBorder().getBorderInsets(editor);
editor.setLocation((int) nodePt.getX() - bInsets.left, (int) nodePt.getY() - bInsets.top);
reshapeEditorLater();
dispatchEventToEditor(event);
canvas.repaint();
text.setEditing(true);
text.getDocument().addDocumentListener(docListener);
editedText = text;
}
/**
* Stops editing the current text node.
*
* @param event the event responsible for stopping the editing
*/
public void stopEditing(final PInputEvent event) {
if (editedText == null) {
return;
}
editedText.getDocument().removeDocumentListener(docListener);
editedText.setEditing(false);
if (editedText.getDocument().getLength() == 0) {
editedText.removeFromParent();
}
else {
editedText.syncWithDocument();
}
if (editedText.getParent() == null) {
editedText.setScale(1.0 / event.getCamera().getViewScale());
canvas.getLayer().addChild(editedText);
}
editor.setVisible(false);
canvas.repaint();
editedText = null;
}
/**
* Intercepts Piccolo2D events and dispatches the underlying swing one to
* the current editor.
*
* @param event the swing event being intercepted
*/
public void dispatchEventToEditor(final PInputEvent event) {
// We have to nest the mouse press in two invoke laters so that it is
// fired so that the component has been completely validated at the new
// size and the mouse event has the correct offset
SwingUtilities.invokeLater(new Runnable() {
public void run() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final MouseEvent me = new MouseEvent(editor, MouseEvent.MOUSE_PRESSED, event.getWhen(), event
.getModifiers()
| InputEvent.BUTTON1_MASK, (int) (event.getCanvasPosition().getX() - editor.getX()),
(int) (event.getCanvasPosition().getY() - editor.getY()), 1, false);
editor.dispatchEvent(me);
}
});
}
});
}
/**
* Adjusts the shape of the editor to fit the current document.
*/
public void reshapeEditor() {
if (editedText != null) {
Dimension prefSize = editor.getPreferredSize();
final Insets textInsets = editedText.getInsets();
final Insets editorInsets = editor.getInsets();
final int width;
if (editedText.getConstrainWidthToTextWidth()) {
width = (int) prefSize.getWidth();
}
else {
width = (int) (editedText.getWidth() - textInsets.left - textInsets.right + editorInsets.left
+ editorInsets.right + TEXT_EDIT_PADDING);
}
prefSize.setSize(width, prefSize.getHeight());
editor.setSize(prefSize);
prefSize = editor.getPreferredSize();
final int height;
if (editedText.getConstrainHeightToTextHeight()) {
height = (int) prefSize.getHeight();
}
else {
height = (int) (editedText.getHeight() - textInsets.top - textInsets.bottom + editorInsets.top
+ editorInsets.bottom + TEXT_EDIT_PADDING);
}
prefSize.setSize(width, height);
editor.setSize(prefSize);
}
}
/**
* Sometimes we need to invoke this later because the document events seem
* to get fired before the text is actually incorporated into the document.
*/
protected void reshapeEditorLater() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
reshapeEditor();
}
});
}
private static final class DefaultTextEditor extends JTextPane {
private static final long serialVersionUID = 1L;
public DefaultTextEditor() {
EmptyBorder padding = new EmptyBorder(TEXT_EDIT_PADDING,
TEXT_EDIT_PADDING, TEXT_EDIT_PADDING, TEXT_EDIT_PADDING);
setBorder(new CompoundBorder(new LineBorder(Color.black), padding));
}
/**
* Set some rendering hints - if we don't then the rendering can be
* inconsistent. Also, Swing doesn't work correctly with fractional
* metrics.
*/
public void paint(final Graphics graphics) {
if (!(graphics instanceof Graphics2D)) {
throw new IllegalArgumentException("Provided graphics context is not a Graphics2D object");
}
final Graphics2D g2 = (Graphics2D) graphics;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
super.paint(graphics);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy