/* * Copyright (c) 2008-2009, 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 { protected PCanvas canvas; protected JTextComponent editor; protected DocumentListener docListener; protected PStyledText editedText; /** * Basic constructor for PStyledTextEventHandler */ public PStyledTextEventHandler(final PCanvas canvas) { super(); 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 */ public PStyledTextEventHandler(final PCanvas canvas, final JTextComponent editor) { super(); this.canvas = canvas; initEditor(editor); } protected void initEditor(final JTextComponent newEditor) { editor = newEditor; canvas.setLayout(null); canvas.add(editor); editor.setVisible(false); docListener = createDocumentListener(); } protected JTextComponent createDefaultEditor() { final JTextPane tComp = new JTextPane() { /** * */ private static final long serialVersionUID = 1L; /** * 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 g) { final Graphics2D g2 = (Graphics2D) g; 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(g); } }; tComp.setBorder(new CompoundBorder(new LineBorder(Color.black), new EmptyBorder(3, 3, 3, 3))); return tComp; } 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(); } }; } 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); } public void mousePressed(final PInputEvent inputEvent) { final PNode pickedNode = inputEvent.getPickedNode(); stopEditing(inputEvent); if (inputEvent.getButton() != MouseEvent.BUTTON1) { return; } if (pickedNode instanceof PStyledText) { startEditing(inputEvent, (PStyledText) pickedNode); } else if (pickedNode instanceof PCamera) { final PStyledText newText = createText(); final Insets pInsets = newText.getInsets(); canvas.getLayer().addChild(newText); newText.translate(inputEvent.getPosition().getX() - pInsets.left, inputEvent.getPosition().getY() - pInsets.top); startEditing(inputEvent, newText); } } 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; } 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(); } editedText.setScale(1.0 / event.getCamera().getViewScale()); editor.setVisible(false); canvas.repaint(); editedText = null; } public void dispatchEventToEditor(final PInputEvent e) { // 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, e.getWhen(), e .getModifiers() | InputEvent.BUTTON1_MASK, (int) (e.getCanvasPosition().getX() - editor.getX()), (int) (e.getCanvasPosition().getY() - editor.getY()), 1, false); editor.dispatchEvent(me); } }); } }); } public void reshapeEditor() { if (editedText != null) { // Update the size to fit the new document - note that it is a 2 // stage process Dimension prefSize = editor.getPreferredSize(); final Insets pInsets = editedText.getInsets(); final Insets jInsets = editor.getInsets(); final int width = editedText.getConstrainWidthToTextWidth() ? (int) prefSize.getWidth() : (int) (editedText .getWidth() - pInsets.left - pInsets.right + jInsets.left + jInsets.right + 3.0); prefSize.setSize(width, prefSize.getHeight()); editor.setSize(prefSize); prefSize = editor.getPreferredSize(); final int height = editedText.getConstrainHeightToTextHeight() ? (int) prefSize.getHeight() : (int) (editedText.getHeight() - pInsets.top - pInsets.bottom + jInsets.top + jInsets.bottom + 3.0); 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(); } }); } }