/* * 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; import tufts.vue.LWComponent.Key; import tufts.vue.gui.ColorMenuButton; import tufts.vue.gui.DockWindow; import tufts.vue.gui.GUI; import tufts.vue.gui.TextRow; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Font; import java.awt.Color; import java.awt.BasicStroke; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.geom.Point2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.Enumeration; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.JComboBox; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.*; import javax.swing.text.html.CSS; import javax.swing.text.html.HTML; import javax.swing.text.html.HTMLDocument; import com.lightdev.app.shtm.SHTMLDocument; import com.lightdev.app.shtm.SHTMLEditorKit; import com.lightdev.app.shtm.Util; //import java.awt.font.LineBreakMeasurer; //import java.awt.font.TextAttribute; /** * A multi-line editable text object that supports left/center/right * aligment for its lines of text. * * Used in two modes: (1) "normal" mode -- used to paint multi-line * text objects (labels, notes, etc) and (2) "edit". In normal mode, * this JComponent has no parent -- it isn't added to any AWT * hierarchy -- it's only used to paint as part of the * LWMap/LWContainer paint tree (via the draw(Graphics2D)) method. In * edit mode, it's temporarily added to the canvas so it can receive * user input. Only one instance of these is ever added and active in * AWT at the same time. We have to do some funky wrangling to deal * with zoom, because the JComponent can't paint and interact on a * zoomed (scaled) graphics context (unless we were to implement mouse * event retargeting, which is a future possibility). So if there is * a scale active on the currently displayed map, we manually derive a * new font for the whole text object (the Document) and set it to * that temporarily while it's active in edit mode, and then re-set it * upon removal. Note that users of this class (e.g., LWNode) should * not bother to paint it (call draw()) if it's in edit mode * (getParent() != null) as the AWT/Swing tree is dealing with that * while it's in its activated edit state. * * We use a JTextPane because it supports a StyledDocument, which is * what we need to be able to set left/center/right aligment for all * the paragraphs in the document. This is a bit heavy weight for our * uses right now as we only make use of one font at a time for the whole * document (this is the heaviest weight text component in Swing). * JTextArea would have worked for us, except it only supports its * fixed default of left-aligned text. However, eventually we're * probably going to want to suport intra-string formatting (fonts, * colors, etc) and so we'll be ready for that, with the exception of * the hack mentioned above to handle zooming (tho we could * theoretically iterate through the whole document, individually * deriving zoomed fonts for every font found in the Document.) * * Note that you can get center/variable alignment even with * a JLabel if it's text starts with <HTML>, and you * put in a center tag. Unfortunately, JTextArea's don't * do HTML w/out setting up a StyledDocument manually. * * * @author Scott Fraize * @version $Revision: 1.44 $ / $Date: 2010-02-03 19:17:40 $ / $Author: mike $ * */ public class RichTextBox extends com.lightdev.app.shtm.SHTMLEditorPane implements VueConstants , FocusListener , KeyListener , DocumentListener , MouseListener , ActionListener { /** * */ private static final long serialVersionUID = 1L; // todo: duplicate not working[? for wrap only? ] private static final Color SelectionColor = GUI.getTextHighlightColor();//VueResources.getColor("mapViewer.textBox.selection.color"); private boolean revert = false; private static boolean TestDebug = false; private static boolean TestHarness = false; private LWComponent lwc; /** bounds: generally used by the component as local coordinates (relative to the coordinate 0,0) * The width/height are set here in TextBox */ private final Rectangle2D.Float mBounds = new Rectangle2D.Float(); private boolean wasOpaque; /** were we opaque before we started an edit? */ private float mMaxCharWidth; private float mMaxWordWidth; // private RichTextHighlighter vueHighlighter = null; RichTextBox(LWComponent lwc) { this(lwc, null); } RichTextBox(LWComponent lwc, String text) { if (DEBUG.TEXT && DEBUG.LAYOUT) tufts.Util.printClassTrace("tufts.vue.", "NEW RichTextBox, txt=" + text); if (TestDebug||DEBUG.TEXT) out("NEW [" + text + "] " + lwc); // vueHighlighter = new RichTextHighlighter(this); // this.setHighlighter(vueHighlighter); SHTMLEditorKit kit = new SHTMLEditorKit(/* renderMode */); //kit.resetStyleSheet(); setEditorKit(kit); this.lwc = lwc; setDragEnabled(false); setBorder(null); if (text != null) setText(text); setMargin(null); setOpaque(false); // don't bother to paint background setVisible(true); addMouseListener(this); addKeyListener(this); addFocusListener(this); getDocument().addDocumentListener(this); if (VueUtil.isWindowsPlatform() && SelectionColor != null) setSelectionColor(SelectionColor); if (VueUtil.isWindowsPlatform() && SelectionColor != null) setSelectedTextColor(Color.black); mBounds.x = Float.NaN; // mark as uninitialized mBounds.y = Float.NaN; // mark as uninitialized if (TestDebug||DEBUG.TEXT) out("constructed " + getSize()); } LWComponent getLWC() { return this.lwc; } /* * When activated for editing, draw an appropriate background color * for the node -- the we need to do because if it's a small on-screen * font at the moment (depending on zoom level), we make the * text box always appear at the 100% zoomed font (because we're * not managing scaled repaint of the added object or retargeting * scaled mouse events, etc). Also, when it's transparent, the * whole map has to be repainted each cursor blink just in case * there is some LWComponent under the transparent text item. * (Tho with a very small clip region). */ private Font preZoomFont = null; private String mUnchangedText; private Dimension mUnchangedSize; private boolean keyWasPressed = false; /** called by MapViewer before we go active on the map */ void saveCurrentState() { mUnchangedText = getText(); mUnchangedSize = getSize(); } /** deprecated */ void saveCurrentText() { saveCurrentState(); } public double getScaledWidth() { java.awt.Container parent = getParent(); double zoom = ((MapViewer)parent).getZoomFactor(); zoom *= lwc.getMapScale(); return getWidth() * zoom; } public double getScaledHeight() { java.awt.Container parent = getParent(); double zoom = ((MapViewer)parent).getZoomFactor(); zoom *= lwc.getMapScale(); return getHeight() * zoom; } public double getUnscaledWidth() { java.awt.Container parent = getParent(); double zoom = ((MapViewer)parent).getZoomFactor(); zoom *= lwc.getMapScale(); return getSize().width / zoom; } public double getUnscaledHeight() { java.awt.Container parent = getParent(); double zoom = ((MapViewer)parent).getZoomFactor(); zoom *= lwc.getMapScale(); return getSize().height / zoom; } Dimension preAddDimension= null; @Override public void addNotify() { if (TestDebug||DEBUG.TEXT) out("*** ADDNOTIFY ***"); if (getText().length() < 1) setText("<label>"); keyWasPressed = false; //Dimension size = getPreferredSize(); ((SHTMLDocument)getDocument()).setEditing(true); // System.out.println("ZOOM : " + VUE.getActiveViewer().getZoomFactor()); java.awt.Container parent = getParent(); double zoom = ((MapViewer)parent).getZoomFactor(); zoom *= lwc.getMapScale(); ((SHTMLDocument)getDocument()).setZoomFactor(zoom); super.addNotify(); preAddDimension= new Dimension((int)getWidth(),(int)getHeight()); // System.out.println(preAddDimension.toString()); // System.out.println(getPreferredSize().toString()); this.setSize(new Dimension((int)getScaledWidth(),(int)getScaledHeight())); wasOpaque = isOpaque(); Color background = lwc.getRenderFillColor(null); //if (c == null && lwc.getParent() != null && lwc.getParent() instanceof LWNode) final LWContainer nodeParent = lwc.getParent(); if (background == null && nodeParent != null) { background = nodeParent.getRenderFillColor(null); // todo: only handles 1 level transparent embed! } // todo: could also consider using map background if the node itself // is transpatent (has no fill color) // TODO: this workaround until we can recursively find a real fill color // node that for SLIDES, we'd have to get awfully fancy and // usually pull the color of the master slide (unless the slide // happened to have it's own special color). Only super clean // way to do this would be to have established some kind of // rendering pipeline record... (yeah, right) if (background == null) background = Color.gray; //out("BACKGROUND COLOR " + background); // TODO: the *selection* color always appears to be gray in for edits // in the slide viewer on the mac, even if we manually set the selection // color (which works in the main MapViewer) -- this is an oddity... if (background != null) { // note that if we set opaque to false, interaction speed is // noticably slowed down in edit mode because it has to consider // repainting the entire map each cursor blink as the object // is transparent, and thus it's background is the displayed // map. So if we can guess at a reasonable fill color in edit mode, // we temporarily set us to opaque. setOpaque(true); setBackground(background); } //// setSize(getPreferredSize()); } /* * Return to the regular transparent state. */ @Override public void removeNotify() { if (TestDebug||DEBUG.TEXT) out("*** REMOVENOTIFY ***"); //------------------------------------------------------------------ // We need to clear any text selection here as a workaround // for an obscure bug where sometimes if the focus change is // to a pop-up menu, the edit properly goes inactive, but the // selection within it is still drawn with it's highlighted // background. clearSelection(); //------------------------------------------------------------------ super.removeNotify(); ((SHTMLDocument)getDocument()).setEditing(false); java.awt.Container parent = getParent(); double zoom = ((MapViewer)parent).getZoomFactor(); zoom *= lwc.getMapScale(); ((SHTMLDocument)getDocument()).setZoomFactor(zoom); // System.out.println("pref Height : " + getHeight()); // System.out.println("pref : " + getHeight()/zoomFactor); preAddDimension.height = (int)(getHeight() / zoom); preAddDimension.width = (int)(getWidth() / zoom); this.setSize(preAddDimension); if (mFirstAfterAddNotify == false) { // if cleared, it was used out("restoring expanded width"); // setSize(new Dimension(getWidth(), getHeight())); //out("SKPPING restoring expanded width"); } else mFirstAfterAddNotify = false; setBorder(null); if (preZoomFont != null) { setDocumentFont(preZoomFont); preZoomFont = null; // if (WrapText) { // adjustSizeDynamically(); //} else { setSize(getPreferredSize()); // WE MUST DO THIS A SECOND TIME TO MAKE SURE THIS WORKS: // JTextPane can actually produce inconsistent results // when getPreferredSize() is called, especially if it's // results were just use to set the size of the object. // A second get/set produces more reliable results. //// setSize(getPreferredSize()); // } } if (wasOpaque != isOpaque()) setOpaque(wasOpaque); if (TestDebug||DEBUG.TEXT) out("*** REMOVENOTIFY end: insets="+getInsets()); } public void clearSelection() { // this set's the "mark to the point" -- sets them to the same // location, thus clearing the selection. setCaretPosition(getCaretPosition()); } @Override public void setText(String text) { super.setText(text); //// setSize(getPreferredSize()); // if (lwc.getParent() !=null) lwc.getParent().layoutChildren(); //setSize(getPreferredSize()); } public void setXMLText(String text) { super.setText(text); } private void setDocumentFont(Font f) { } private void setDocumentColor(Color c) { } private static void setFontAttributes(MutableAttributeSet a, Font f) { } /*********** *******NOTES FROM FONT EDITOR PANEL TO HELP WITH COPY/PASTE OF STYLES. globalSizeListener = new ActionListener(){ public void actionPerformed(ActionEvent fe) { if (suspendItemListeners) return; if (lwtext == null) return; lwtext.richLabelBox.selectAll(); final String textSize = mSizeField.getSelectedItem().toString(); SHTMLDocument doc = (SHTMLDocument)lwtext.richLabelBox.getDocument(); SimpleAttributeSet set = new SimpleAttributeSet(); Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_SIZE, textSize); set.addAttribute(HTML.Attribute.SIZE, textSize); lwtext.richLabelBox.applyAttributes(set, false); lwtext.richLabelBox.select(0,0); //lwtext.setLabel0(lwtext.richLabelBox.getRichText(), false); lwtext.richLabelBox.setSize(lwtext.richLabelBox.getPreferredSize()); if (lwtext.getParent() !=null) lwtext.getParent().layout(); lwtext.notify(lwtext.richLabelBox, LWKey.Repaint); } }; globalFaceListener = new ActionListener(){ public void actionPerformed(ActionEvent fe) { if (suspendItemListeners) return; if (lwtext == null) return; lwtext.richLabelBox.selectAll(); SHTMLDocument doc = (SHTMLDocument)lwtext.richLabelBox.getDocument(); SimpleAttributeSet set = new SimpleAttributeSet(); Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_FAMILY, mFontCombo.getSelectedItem().toString()); set.addAttribute(HTML.Attribute.FACE, mFontCombo.getSelectedItem().toString()); lwtext.richLabelBox.applyAttributes(set, false); lwtext.richLabelBox.select(0,0); lwtext.richLabelBox.setSize(lwtext.richLabelBox.getPreferredSize()); if (lwtext.getParent() !=null) lwtext.getParent().layout(); lwtext.notify(lwtext.richLabelBox, LWKey.Repaint); } }; private final PropertyChangeListener RichTextColorChangeListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { if (e instanceof LWPropertyChangeEvent == false) return; final RichTextBox activeRTB = (RichTextBox) VUE.getActive(RichTextBox.class); if (activeRTB == null && lwtext == null) { // no problem: just ignore if there's no active edit //tufts.Util.printStackTrace("FEP propertyChange: no active RichTextBox: " + e); return; } if (lwtext !=null) lwtext.richLabelBox.selectAll(); final Color color = (Color) e.getNewValue(); final SimpleAttributeSet set = new SimpleAttributeSet(); final String colorString = "#" + Integer.toHexString(color.getRGB()).substring(2); toggleBulletsAction.setColor(colorString); toggleNumbersAction.setColor(colorString); Util.styleSheet().addCSSAttribute(set, CSS.Attribute.COLOR, colorString); set.addAttribute(HTML.Attribute.COLOR, colorString); if (activeRTB != null) activeRTB.applyAttributes(set, false); else lwtext.richLabelBox.applyAttributes(set, false); if (lwtext !=null) { lwtext.richLabelBox.select(0,0); lwtext.richLabelBox.select(0,0); //lwtext.setLabel0(lwtext.richLabelBox.getRichText(), false); lwtext.richLabelBox.setSize(lwtext.richLabelBox.getPreferredSize()); if (lwtext.getParent() !=null) lwtext.getParent().layout(); lwtext.notify(lwtext.richLabelBox, LWKey.Repaint); } } }; ************/ // this called every time setText is called to ensure we get // the font style encoded in our owning LWComponent void copyStyle(LWComponent c) { if (DEBUG.TEXT) out("copyStyle " + c); //Basic Setup LWText srcText = (LWText)c; RichTextBox srcBox = srcText.getRichLabelBox(); selectAll(); SHTMLDocument srcDoc = (SHTMLDocument)srcBox.getDocument(); SHTMLDocument doc = (SHTMLDocument)getDocument(); SimpleAttributeSet set = new SimpleAttributeSet(); SimpleAttributeSet alignSet = new SimpleAttributeSet(); //Gather source information Element paragraphElement = srcDoc.getParagraphElement(1); if (paragraphElement.getName().equals("p-implied")) //we're in a list item paragraphElement = paragraphElement.getParentElement(); AttributeSet paragraphAttributeSet = paragraphElement.getAttributes(); Element charElem = srcDoc.getCharacterElement(1); AttributeSet charSet = charElem.getAttributes(); Enumeration characterAttributeEnum = charSet.getAttributeNames(); Enumeration elementEnum = paragraphAttributeSet.getAttributeNames(); //Apply some attributes // System.out.println(paragraphElement.toString()); while (elementEnum.hasMoreElements()) { Object o = elementEnum.nextElement(); boolean isAlignSet = false; //System.out.println("P :: " +o.toString()); if (o.toString().equals("text-align") && paragraphAttributeSet.getAttribute(o).toString().equals("left") && !isAlignSet) { //Left Align Util.styleSheet().addCSSAttribute(alignSet, CSS.Attribute.TEXT_ALIGN, paragraphAttributeSet.getAttribute(o).toString()); alignSet.addAttribute(HTML.Attribute.ALIGN, paragraphAttributeSet.getAttribute(o).toString()); } else if (o.toString().equals("text-align") && paragraphAttributeSet.getAttribute(o).toString().equals("center")&& !isAlignSet) { //Center Align Util.styleSheet().addCSSAttribute(alignSet, CSS.Attribute.TEXT_ALIGN, paragraphAttributeSet.getAttribute(o).toString()); alignSet.addAttribute(HTML.Attribute.ALIGN, paragraphAttributeSet.getAttribute(o).toString()); } else if (o.toString().equals("text-align") && paragraphAttributeSet.getAttribute(o).toString().equals("right")&& !isAlignSet) { //Right Align Util.styleSheet().addCSSAttribute(alignSet, CSS.Attribute.TEXT_ALIGN, paragraphAttributeSet.getAttribute(o).toString()); alignSet.addAttribute(HTML.Attribute.ALIGN, paragraphAttributeSet.getAttribute(o).toString()); } if ((o.toString().equals("font-size")) ||(o.toString().equals("size"))) { //Font Size Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_SIZE, paragraphAttributeSet.getAttribute(o).toString()); set.addAttribute(HTML.Attribute.SIZE, paragraphAttributeSet.getAttribute(o).toString()); } else if ((o.toString().equals("font-family")) || (o.toString().equals("font-face")) || (o.toString().equals("face"))) { //Font Face Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_FAMILY, paragraphAttributeSet.getAttribute(o).toString()); set.addAttribute(HTML.Attribute.FACE, paragraphAttributeSet.getAttribute(o).toString()); } } boolean isBold = false; boolean isItalic = false; boolean isUnderline = false; while (characterAttributeEnum.hasMoreElements()) { Object o = characterAttributeEnum.nextElement(); //System.out.println("C :: " +o.toString() + "XXX " + charSet.getAttribute(o).toString()); //System.out.println("Character element : " + o.toString() + " , " + charSet.getAttribute(o)); if ((o.toString().equals("color"))) { //Color Util.styleSheet().addCSSAttribute(set, CSS.Attribute.COLOR, charSet.getAttribute(o).toString()); set.addAttribute(HTML.Attribute.COLOR, charSet.getAttribute(o).toString()); } if ((o.toString().equals("font-size")) ||(o.toString().equals("size"))) { //Font Size Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_SIZE, charSet.getAttribute(o).toString()); set.addAttribute(HTML.Attribute.SIZE, charSet.getAttribute(o).toString()); } if ((o.toString().equals("font-family")) || (o.toString().equals("font-face")) || (o.toString().equals("face"))) { //Font Face Util.styleSheet().addCSSAttribute(set, CSS.Attribute.FONT_FAMILY, charSet.getAttribute(o).toString()); set.addAttribute(HTML.Attribute.FACE, charSet.getAttribute(o).toString()); } if ((o.toString().equals("font-weight") && charSet.getAttribute(o).toString().equals("bold")) || o.toString().equals("b")) { Util.styleSheet().addCSSAttribute(set,CSS.Attribute.FONT_WEIGHT,charSet.getAttribute(o).toString()); } if ((o.toString().equals("font-style") && charSet.getAttribute(o).toString().equals("italic")) || o.toString().equals("i")) { Util.styleSheet().addCSSAttribute(set,CSS.Attribute.FONT_STYLE,charSet.getAttribute(o).toString()); } if ((o.toString().equals("text-decoration") && charSet.getAttribute(o).toString().equals("underline")) || o.toString().equals("u")) { Util.styleSheet().addCSSAttribute(set,CSS.Attribute.TEXT_DECORATION,charSet.getAttribute(o).toString()); } }//done looking at character attributes applyAttributes(set, false); lwc.notify(this, LWKey.Repaint); applyAttributes(alignSet, true); lwc.notify(this, LWKey.Repaint); clearSelection(); try{ setSize(getPreferredSize()); } catch(NullPointerException npe){} /* set = new SimpleAttributeSet(); final String colorString = "#" + Integer.toHexString(color.getRGB()).substring(2); toggleBulletsAction.setColor(colorString); toggleNumbersAction.setColor(colorString); Util.styleSheet().addCSSAttribute(set, CSS.Attribute.COLOR, colorString); set.addAttribute(HTML.Attribute.COLOR, colorString); if (activeRTB != null) activeRTB.applyAttributes(set, false); else lwtext.richLabelBox.applyAttributes(set, false); if (lwtext !=null) { lwtext.richLabelBox.select(0,0); lwtext.richLabelBox.select(0,0); //lwtext.setLabel0(lwtext.richLabelBox.getRichText(), false); lwtext.richLabelBox.setSize(lwtext.richLabelBox.getPreferredSize()); if (lwtext.getParent() !=null) lwtext.getParent().layout(); lwtext.notify(lwtext.richLabelBox, LWKey.Repaint); }*/ } /** override Container.doLayout: only called when we've been added to a map for interactive editing. * Is called during interactive edit's after each modification. */ public void doLayout() { if (getParent() instanceof MapViewer) { // Automatic layout (e.g. FlowLayout) // produces two layout passes -- perhaps // this is why we need to call this TWICE // here so that the box size doesn't // temporarily flash bigger on every update. if (TestDebug || DEBUG.LAYOUT) out("doLayout w/adjustSizeDynamically"); Dimension d = getPreferredSize(); setSize(d); // super.doLayout(); } else { if (!TestHarness) new Throwable(this + " UNPARENTED doLayout").printStackTrace(); } } private boolean mFirstAfterAddNotify = false; public void keyReleased(KeyEvent e) { e.consume(); } public void keyTyped(KeyEvent e) { // todo: would be nice if centered labels stayed center as you typed them //setLocation((int)lwc.getLabelX(), (int)lwc.getLabelY()); // needs something else, plus can't work at zoom because // width isn't updated till the end (because width at + zoom // is overstated based on temporarily scaled font) // Man, it would be REALLY nice if we could paint the // real component in a scaled GC w/out the font tweaking -- // problems like this would go away. } private static boolean isFinishEditKeyPress(KeyEvent e) { // if we hit return key either on numpad ("enter" key), or // with any modifier down except a shift alone (in case of // caps lock) complete the edit. return e.getKeyCode() == KeyEvent.VK_ENTER && ( e.getKeyLocation() == KeyEvent.KEY_LOCATION_NUMPAD || (e.getModifiersEx() != 0 && !e.isShiftDown()) ) == true; //== false; // reversed logic of below description } private Container removeAsEdit() { Container parent = getParent(); if (parent != null) parent.remove(this); else out("FAILED TO FIND PARENT ATTEMPTING TO REMOVE SELF"); return parent; } public void keyPressed(KeyEvent e) { if (DEBUG.KEYS) out(e.toString()); //System.out.println("TextBox: " + e); //if (VueUtil.isAbortKey(e)) // check for ESCAPE for CTRL-Z or OPTION-Z if on mac if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { e.consume(); // System.out.println(mUnchangedText); //setText(mUnchangedText); revert = true; getParent().remove(this); // will trigger a save (via focusLost) return; // setSize(mUnchangedSize); // todo: won't be good enough if we ever resize the actual node as we type } else if (isFinishEditKeyPress(e)) { keyWasPressed = true; e.consume(); getParent().remove(this); // will trigger a save (via focusLost) VUE.getFormattingPanel().getTextPropsPane().getFontEditorPanel().updateFormatControlsTB(this); } else if (e.getKeyCode() == KeyEvent.VK_U && e.isMetaDown()) { e.consume(); String t = getText(); if (e.isShiftDown()) setText(t.toUpperCase()); // upper whole string else setText(Character.toTitleCase(t.charAt(0)) + t.substring(1)); // upper first char } else keyWasPressed = true; // Dimension d = preAddDimension; // d.height = this.getPreferredSize().height; // setSize(getPreferredSize()); // action keys will be ignored if we consume this here! // (e.g., "enter" does nothing) //e.consume(); } /** * This is what triggers the final save of the new text value to the LWComponent, * and notify's the UndoManager that a user action was completed. */ public void focusLost(FocusEvent e) { final java.awt.Component opposite = e.getOppositeComponent(); if (opposite != null) { if ((opposite.getName() != null && opposite.getName().equals(FontEditorPanel.SIZE_FIELD_NAME)) || opposite.getClass() == ColorMenuButton.class) return; else if (opposite.getClass() == FontEditorPanel.class || DockWindow.isDockWindow(opposite) || // todo: something more generic than this getName check: set a property on the JComponent tagging it as a tool/editor? opposite.getClass() == JComboBox.class || (opposite.getName() != null && opposite.getName().equals(tufts.vue.gui.ColorMenuButton.COLOR_POPUP_NAME)) || //quaqua makes this a bit awkard, this is for quaqua's color chooser. (opposite.getName() != null && opposite.getName().equals("dialog0"))) { //Earlier i was just returning here, but this creates a problem //because the component has already lost the focus...and so it doesn't //get another focusLost the next time....so re-request the focus if you've lost //it so that we get the event again when we really want to get rid of the focus //so we can properly remove the edit control. requestFocus(); return; } } else if (opposite == null) { if (DEBUG.FOCUS) outc("Focus not lost because opposite component = null"); requestFocus(); return; } //System.out.println(e.getComponent().toString()); //System.out.println(e.getOppositeComponent().toString()); if (TestDebug||DEBUG.FOCUS) outc("focusLost to " + e.getOppositeComponent() + " " + opposite.getName()); if (TestHarness == false && getParent() != null) { getParent().remove(this); // VUE.getFormattingPanel().getTextPropsPane().getFontEditorPanel().updateFormatControlsTB(this); } if (keyWasPressed || !keyWasPressed) { // TODO: as per VueTextField, need to handle drag & drop detect // only do this if they typed something (so we don't wind up with "label" // for the label on an accidental edit activation) if (TestDebug||DEBUG.FOCUS) out("key was pressed; setting label to: [" + getText() + "]"); String text = getText(); if (revert) { text = mUnchangedText; revert =false; // setText(text); } lwc.setLabel0(text, false); VUE.getUndoManager().mark(); } //// setSize(getPreferredSize()); // lwc.setSize(mBounds.width, mBounds.height); if (lwc.getParent() !=null && lwc.getParent() instanceof LWNode) lwc.getParent().layout(); lwc.notify(this, LWKey.Repaint); } public void focusGained(FocusEvent e) { if (TestDebug||DEBUG.FOCUS) outc("focusGained from " + e.getOppositeComponent()); } /** do not use, or won't be able to get out actual text height */ @Override public void setPreferredSize(Dimension preferredSize) { if (true||TestDebug) out("setPreferred " + preferredSize); super.setPreferredSize(preferredSize); } /*@Override public Dimension getPreferredSize() { Dimension s = super.//getPreferredSize(); //getMinimumSize();//debug //s.width = (int) lwc.getWidth(); //System.out.println("MTP lwcWidth " + lwc.getWidth()); if (getParent() != null) s.width += 1; // leave room for cursor, which at least on mac gets clipped if at EOL //if (getParent() == null) // s.width += 10;//fudge factor for understated string widths (don't do this here -- need best accuracy here) if (TestDebug) out("getPrefer", s); //if (TestDebug && DEBUG.META) new Throwable("getPreferredSize").printStackTrace(); return s; } */ /* * Style style = ((HTMLDocument) getDocument()).getStyleSheet().getStyle("body"); Object a = style.getAttribute(javax.swing.text.html.CSS.Attribute.FONT_SIZE); if (a !=null) { if (DEBUG.TEXT) out("got style"); int diff =0; Integer i = new Integer(a.toString()); diff = i.intValue(); minS.height = minS.height - diff; } */ public Dimension getPreferredSize() { Dimension s = null; // Dimension s = super.getPreferredSize(); Dimension minS = getMinimumSize(); //System.out.println(javax.swing.SwingUtilities.getLocalBounds(this)); //if (TestDebug||DEBUG.TEXT) out("getPrefer", s); // System.out.println("GetPrefSize : " + mBounds.width + " "+ s.width); // System.out.println("Required Lines : " + s.width/mBounds.width); Caret c = this.getCaret(); Point position = c.getMagicCaretPosition(); // if (position != null) // System.out.println("magic caret : " + position.getX()); //HTMLDocument builds a hierarchical Element structure where each Element //represents a structural block of HTML, and not just a line of text. so I'm not sure //how to figure out what line you're on. Dimension min = new Dimension(); final Dimension text = getMinimumSize(); min.width = text.width; int EdgePadY = 0; // Was 3 in VUE 1.5 int LabelPadLeft = 8; // Was 6 in VUE 1.5; fixed //System.out.println("Text.height : " + text.height); // *** set icon Y position in all cases to a centered vertical // position, but never such that baseline is below bottom of // first icon -- this is tricky tho, as first icon can move // down a bit to be centered with the label! min.width += LabelPadLeft; min.width = Math.max(min.width,minS.width); /* System.out.println("Min.Width =" + min.width); System.out.println("MinS.Width =" + minS.width); System.out.println("S.Width =" + s.width); System.out.println("MBounds.Width = " + mBounds.width); System.out.println("GetSize.Width = " + getSize().width); System.out.println("Bounds : " + this.getBounds().width); System.out.println("LWC width : " + lwc.width); System.out.println("LWC bounds : " + lwc.getBounds().width); System.out.println("LWC layoutbounds : " + lwc.getLayoutBounds().width); System.out.println("LWC min size : " + lwc.getMinimumSize().width); System.out.println("Get super width : " + super.getWidth()); System.out.println("Selection Eend : " + this.getSelectionEnd()); System.out.println("Selection Start : " + this.getSelectionStart()); System.out.println("Bounds Box Width : " +this.getBoxBounds().getBounds().width); System.out.println("Visible Rect : " +this.getVisibleRect().width);*/ //** Rectangle p2 = null; //if (this.getGraphics() != null) //{ // System.out.println("GRAPHICS : " + this.getGraphics().toString()); // if (this.getGraphics().getClipBounds() != null) // System.out.println("GRAPHICS : " + this.getGraphics().getClipBounds().width); // this.getPreferredScrollableViewportSize().width // // View ui = getUI().getRootView(this); ui = ui.createFragment(this.getSelectionStart(), this.getSelectionEnd()); //System.out.println("min span: " + ui.getMinimumSpan(View.X_AXIS)); //** int start = getSelectionStart(); //** int end = getSelectionEnd(); //** float f = ui.getPreferredSpan(View.X_AXIS); //System.out.println("Preferred span : " + f); //** f = ui.getPreferredSpan(View.Y_AXIS); //System.out.println("Preferred span y : " + f); //** f = ui.getPreferredSpan(View.X_AXIS); //System.out.println("{referred span X : " + f); //ui.breakView(View.X_AXIS, offset, pos, len) //((HTMLEditorKit)this.getEditorKit()).getViewFactory().create(this.getDocument().getDefaultRootElement()). // System.out.println("BREAK WEIGHT : " + ui.getBreakWeight(View.X_AXIS, this.getCaretPosition(), this.getSelectionEnd())); // System.out.println("RESIZE WEIGHT : " + ui.getResizeWeight(View.X_AXIS)); // System.out.println("RESIZE WEIGHTY : " + ui.getResizeWeight(View.Y_AXIS)); //** float align = ui.getAlignment(View.X_AXIS); // System.out.println("align span : " + align); //** try { //** p2 = this.modelToView(this.getSelectionEnd()); //** //** } catch (BadLocationException e) { //** //Nothing we can do really. //** } //** //if (p2 != null) //** //System.out.println("Selection end rect : " + p2.x); //** if (mBounds.width > 0) { //** if (p2 != null && p2.x > mBounds.width) //** mBounds.width = p2.x; //int height = 48; /*if (getFont() != null &&this.getFontMetrics(getFont())!=null) { System.out.println("A:"+this.getFontMetrics(getFont()).getMaxAscent()); System.out.println("B:"+this.getFontMetrics(getFont()).getMaxDescent()); }*/ int height = super.getPreferredSize().height;//s.height;//Math.max(s.height, 48); Style style = ((HTMLDocument) getDocument()).getStyleSheet().getStyle("body"); Object a = style.getAttribute(javax.swing.text.html.CSS.Attribute.FONT_SIZE); if (a !=null) { if (DEBUG.TEXT) out("got style"); int diff =0; Integer i = new Integer(a.toString()); diff = i.intValue(); //minS.height = minS.height - diff; if (VUE.getActiveViewer() !=null) { java.awt.Container parent = getParent(); double zoom = 1.0; if (parent !=null) zoom = ((MapViewer)parent).getZoomFactor(); else { //parent is null during duplicate. zoom = VUE.getActiveViewer().getZoomFactor(); } zoom *= lwc.getMapScale(); diff *= zoom; } height = height - diff; } if (position !=null) { float minSpan = ui.getMinimumSpan(View.X_AXIS); float mins2 = Math.max(minSpan,(float)position.getX()); s = new Dimension((int)(mBounds.width > mins2 ? mBounds.width : mins2),(int)height); } else { float minSpan = ui.getMinimumSpan(View.X_AXIS); s = new Dimension((int)(mBounds.width > minSpan ? mBounds.width : minSpan),(int)height); } //float widthRatio = ((float)s.width /(float)lwc.width); // float heightRatio =((float)s.height/(float)lwc.height); /* if ((widthRatio > 0) && (heightRatio > 0) && (widthRatio != heightRatio)) { s.height = (int)(lwc.height * widthRatio); // System.out.print("NEW SHEIGHT : " + s.height); // prevDim = s; // } System.out.println("width ratio : " + widthRatio); System.out.println("height ratio : " + heightRatio); */ /*if (this.getGraphics() != null) System.out.println("FONT HEIGHT : " +this.getGraphics().getFontMetrics().getMaxAscent()); System.out.println("==================================="); System.out.println("super pref height : " + s.height); System.out.println("Min Vertical Span : " +ui.getMinimumSpan(View.Y_AXIS)); System.out.println("MBounds height : " + mBounds.height); System.out.println("super pref height : " + s.height); System.out.println("Min Vertical Span : " +ui.getMinimumSpan(View.Y_AXIS)); System.out.println("MBounds height : " + mBounds.height); System.out.println("Mins height : " + min.height); System.out.println("Bounds Y : " + this.getBounds().y); System.out.println("Bounds height :" +this.getBounds().height); System.out.println("Bounds2D Y : " +this.getBoxBounds().getY()); System.out.println("Bounds2D height : " +this.getBoxBounds().getHeight()); if (this.getCaret().getMagicCaretPosition() != null) System.out.println("Cursor : " + this.getCaret().getMagicCaretPosition().toString()); //this.getCaret().getMagicCaretPosition(). System.out.println("===================================");*/ /*if (position != null) { System.out.println("Y Pos : " + position.getY()); try { Rectangle r = modelToView(this.getCaretPosition()); System.out.println("model pos : " + r.y); System.out.println("Model height : " + r.height); } catch (BadLocationException e) { // TODO Auto-generated catch block e.printStackTrace(); } }*/ } // s.width = (int)(s.width * VUE.getActiveViewer().getZoomFactor()); // s.height = (int)(s.height * VUE.getActiveViewer().getZoomFactor()); // s.height=(int) this.getBoxBounds().getHeight(); if (this.getBoxBounds() !=null && s.height < this.getBoxBounds().getHeight()) { s.height = (int)this.getBoxBounds().getHeight(); } return s; } public void setSize(Size s) { setSize(s.dim()); } public void setSize(Dimension s) { if (TestDebug||DEBUG.TEXT) out("setSize", s); mBounds.width = s.width; mBounds.height = s.height; super.setSize(s); } /** * Set the size to the given size, increasing or decreasing height as * needed to provide a fit around our text */ public void setSizeFlexHeight(Size newSize) { //------------------------------------------------------------------ // Tell the JTextPane to take on the size requested. It will // then set the preferred height to the minimum height able to // contain the given text at that width. //------------------------------------------------------------------ setSize(newSize); //------------------------------------------------------------------ // Now adjust our height to the new preferred height, which should // just contain our text. //------------------------------------------------------------------ final Dimension s = getPreferredSize(); s.width = getWidth(); if (TestDebug||DEBUG.TEXT) out("flexHeigt", s); super.setSize(s.width, s.height); } public void setSize(float w, float h) { setSize(new Dimension((int)w, (int)h)); } public void setPreferredSize(float w, float h) { setPreferredSize(new Dimension((int)w, (int)h)); } public Dimension getSize() { Dimension s = super.getSize(); //s.width = (int) lwc.getWidth(); if (TestDebug) out("getSize", s); //if (DEBUG.TEXT&&DEBUG.META) new Throwable("getSize").printStackTrace(); return s; } public void setMaximumSize(Dimension s) { if (true||TestDebug) out("setMaximumSize", s); super.setMaximumSize(s); } public Dimension getMaximumSize() { Dimension s = super.getMaximumSize(); if (TestDebug||DEBUG.TEXT) out("getMaximumSize", s); return s; } public void setMinimumSize(Dimension s) { if (true||TestDebug) out("setMinimumSize", s); super.setMinimumSize(s); } public Dimension getMinimumSize() { Dimension s = super.getMinimumSize(); Style style = ((HTMLDocument) getDocument()).getStyleSheet().getStyle("body"); Object a = style.getAttribute(javax.swing.text.html.CSS.Attribute.FONT_SIZE); if (a !=null) { if (DEBUG.TEXT) out("got style"); int diff =0; Integer i = new Integer(a.toString()); diff = i.intValue(); if (VUE.getActiveViewer()!=null) { java.awt.Container parent = getParent(); double zoom = 1.0; // System.out.println("SHEIGHT : " + s.height); if (parent !=null) { zoom = ((MapViewer)parent).getZoomFactor(); zoom *= lwc.getMapScale(); diff *= zoom; // System.out.println("if ::: " + diff); } else { if (VUE.getActiveViewer()!=null) diff *= VUE.getActiveViewer().getZoomFactor(); diff *=lwc.getMapScale(); // System.out.println("else ::: " + diff); } } /*if (a !=null) { if (DEBUG.TEXT) out("got style"); int diff =0; Integer i = new Integer(a.toString()); diff = i.intValue(); if (VUE.getActiveViewer()!=null) diff *= VUE.getActiveViewer().getZoomFactor(); s.height = s.height - diff; }*/ s.height = s.height - diff; } if (TestDebug||DEBUG.TEXT) out("getMinimumSize", s); return s; } @Override public void reshape(int x, int y, int w, int h) { if (TestDebug||DEBUG.TEXT) { boolean change = getX() != x || getY() != y || getWidth() != w || getHeight() != h; if (change) { out(" oldshape " + tufts.Util.out(getBounds())); out(" reshape " + w + "x" + h + " " + x + "," + y); } //out(" reshape " + w + "x" + h + " " + x + "," + y + (change ? "" : " (no change)")); if (DEBUG.META && change) new Throwable("reshape").printStackTrace(); } super.reshape(x, y, w, h); if (TestDebug||DEBUG.TEXT) { Rectangle b = getBounds(); if (b.x != x || b.y != y || b.width != w || b.height != h) out("BADBOUNDS " + tufts.Util.out(b)); } } public Rectangle2D getBoxBounds() { return mBounds; } public boolean boxContains(float x, float y) { return x >= mBounds.x && y >= mBounds.y && x <= mBounds.x + mBounds.width && y <= mBounds.y + mBounds.height; } public boolean boxIntersects(Rectangle2D rect) { return rect.intersects(mBounds); } public void setBoxLocation(float x, float y) { mBounds.x = x; mBounds.y = y; } public void setBoxLocation(Point2D p) { setBoxLocation((float) p.getX(), (float) p.getY()); } public void setBoxCenter(float x, float y) { setBoxLocation(x - getBoxWidth() / 2, y - getBoxHeight() / 2); } public Point2D.Float getBoxPoint() { return new Point2D.Float(mBounds.x, mBounds.y); } public float getBoxWidth() { return mBounds.width; }; public float getBoxHeight() { return mBounds.height; } public float getBoxX() { return mBounds.x; }; public float getBoxY() { return mBounds.y; } /* void resizeToWidth(float w) { int width = (int) (w + 0.5f); setSize(new Dimension(width, 999)); // can set height to 1 as we're ignore the set-size // now the preferred height will be set to the real // total text height at that width -- pull it back out and set actual size to same Dimension ps = getPreferredSize(); setSize(new Dimension(width, (int)ps.getHeight())); } */ public void Xpaint(Graphics g) { super.paint(g); g.setColor(Color.gray); g.setClip(null); g.drawRect(0,0, getWidth(), getHeight()); } /* public Rectangle modelToView(int pos) throws BadLocationException { SHTMLDocument doc = (SHTMLDocument)getDocument(); double zoomFactor = doc.getZoomFactor(); Rectangle alloc; Rectangle s = super.modelToView(pos); alloc = s.getBounds(); alloc.x*=zoomFactor; alloc.y*=zoomFactor; alloc.width*=zoomFactor; alloc.height*=zoomFactor; return alloc; } public int viewToModel(Point p) { SHTMLDocument doc = (SHTMLDocument)getDocument(); double zoomFactor = doc.getZoomFactor(); Point alloc = p; alloc.x/=zoomFactor; alloc.y/=zoomFactor; return super.viewToModel(new Point(alloc)); } */ /* public Rectangle getVisibleRect() { SHTMLDocument doc = (SHTMLDocument)getDocument(); double zoomFactor = doc.getZoomFactor(); Rectangle r = this.getBounds(); r.width =(int)(r.width * zoomFactor); r.height = (int)(r.height * zoomFactor); return r; }*/ public void paintComponent(Graphics g) { if (TestDebug||DEBUG.TEXT) out("paintComponent @ " + getX() + "," + getY() + " parent=" + getParent()); final MapViewer viewer = (MapViewer) javax.swing.SwingUtilities.getAncestorOfClass(MapViewer.class, this); Graphics2D g2d = (Graphics2D)g; if (viewer != null) { g2d.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, viewer.AA_ON); g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); } // turn on anti-aliasing -- the cursor repaint loop doesn't // set anti-aliasing, so text goes jiggy around cursor/in selection if we don't do this //// g.clipRect(0, 0,getWidth(), getAdjustedHeight()); super.paintComponent(g2d); //super.paintComponent(g); if (true) { // draw a border (we don't want to add one because that changes the preferred size at a bad time) // g.setColor(Color.gray); g.setClip(null); final int xpad = 1; final int ypad = 1; g.drawRect(-xpad,-ypad, (int)((getWidth())+xpad*2-1), (int)((getHeight())+ypad*2-1)); } } private static final BasicStroke MinStroke = new BasicStroke(1/8f); private static final BasicStroke MinStroke2 = new BasicStroke(1/24f); /** @return true if hue value of Color is black, ignoring any alpha */ private boolean isBlack(Color c) { return c != null && (c.getRGB() & 0xFFFFFF) == 0; } public void draw(DrawContext dc) { if (TestDebug) out("draw"); if (getParent() != null) System.err.println("Warning: 2nd draw of an AWT drawn component!"); //todo: could try saving current translation or coordinates here, // so have EXACT last position painted at. Tho we really should // be able to compute it... problem is may not be at integer // boundry at current translation, but have to be when we add it // to the map -- tho hey, LWNode could force integer boundry // when setting the translation before painting us. if (DEBUG.BOXES && DEBUG.META) { if (lwc.getLabel().indexOf('\n') < 0) { TextRow r = new TextRow(lwc.getLabel(), lwc.getFont(), dc.g.getFontRenderContext()); dc.g.setColor(Color.lightGray); r.draw(dc, 0, 0); } } boolean restoreTextColor = false; // if (dc.isBlackWhiteReversed() && // (dc.isPresenting() || lwc.isTransparent() /*|| isBlack(lwc.getFillColor())*/) && // isBlack(lwc.getTextColor())) { // //System.out.println("reversing color to white for " + this); // setDocumentColor(Color.white); // inverted = true; // } else // inverted = false; if (dc.isPresenting() && lwc.isTransparent()) { // if the text color equals the background color when in a presentation // (e.g. the master slide has a black background), and the text box // has to fill of it's own for contrast, then temporarily swap // the text color to white or black so it can be seen. if (lwc.mTextColor.equals(dc.getBackgroundFill())) { restoreTextColor = true; if (lwc.mTextColor.brightness() > 0.5) { setDocumentColor(DEBUG.Enabled ? Color.blue : Color.black); } else { setDocumentColor(DEBUG.Enabled ? Color.green : Color.white); } } } //super.paintBorder(g); // // As of least Mac OS X 10.4.10 w/JVM 1.5.0_07 on 2007-08-13, // // it appears there's no way to NOT render anti-aliased text, // // unless there's some other way to override it in JTextPane/JTextComponent // // Not a big deal -- we'd only like the option for a slight speed up // // during animations. // dc.g.setRenderingHint(java.awt.RenderingHints.KEY_TEXT_ANTIALIASING, // java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); // dc.g.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING, // java.awt.RenderingHints.VALUE_ANTIALIAS_OFF); // // Even this doesn't appear to help: // putClientProperty(com.sun.java.swing.SwingUtilities2.AA_TEXT_PROPERTY_KEY, Boolean.FALSE); super.paintComponent(dc.g); //super.paint(g); if (restoreTextColor) { // return document color to black setDocumentColor(lwc.mTextColor.get()); } // draw a border for links -- why? // and even if, better to handle in LWLink /* if (lwc instanceof LWLink) { Dimension s = getSize(); if (lwc.isSelected()) g.setColor(COLOR_SELECTION); else g.setColor(Color.gray); g.setStroke(MinStroke); g.drawRect(0,0, s.width-1, s.height-2); } */ Graphics2D g = dc.g; if (DEBUG.BOXES) { Dimension s = getPreferredSize(); g.setColor(Color.red); dc.setAbsoluteStroke(0.5); //g.setStroke(MinStroke); g.drawRect(0,0, s.width, s.height); //g.drawRect(0,0, s.width-1, s.height); } //s = getMinimumSize(); //g.setColor(Color.red); //g.setStroke(new BasicStroke(1/8f)); //g.drawRect(0,0, s.width, s.height); if (DEBUG.BOXES || getParent() != null) { Dimension s = getSize(); g.setColor(Color.blue); dc.setAbsoluteStroke(0.5); //g.setStroke(MinStroke); g.drawRect(0,0, s.width, s.height); //g.drawRect(0,0, s.width-1, s.height); } } private void handleChange() { // appears to be happening too late for dynamic size adjust -- current character isnt include } public void removeUpdate(DocumentEvent de) { if (TestDebug||DEBUG.TEXT) out("removeUpdate " + de); handleChange(); } public void changedUpdate(DocumentEvent de) { if (TestDebug||DEBUG.TEXT) out("changeUpdate " + de.getType() + " len=" + de.getLength()); handleChange(); } public void insertUpdate(DocumentEvent de) { if (TestDebug||DEBUG.TEXT) out("insertUpdate " + de); handleChange(); } public String toString() { return "RichTextBox[" + lwc + "]"; } @Override public int getHeight() { return (int)super.getHeight(); } private String id() { return Integer.toHexString(System.identityHashCode(this)); } private void out(String s) { System.out.println("TextBox@" + id() + " [" + getText() + "] " + s); //System.out.println("TextBox@" + id() + " " + s); } private void out(String s, Dimension d) { out(VueUtil.pad(' ', 9, s, true) + " " + tufts.Util.out(d)); } private void out(String s, Dimension d, String s2) { out(VueUtil.pad(' ', 9, s, true) + " " + tufts.Util.out(d) + " " + s2); } private void outc(String s) { System.out.println(this + " " + id() + " " + s); } public String getRichText() { String html = super.getText(); String patternStr = "size=\"(\\d*)\""; String replacementStr = "size=\"$1\" style=\"font-size:$1;\""; // Compile regular expression Pattern pattern = Pattern.compile(patternStr); // Replace all occurrences of pattern in input Matcher matcher = pattern.matcher(html); String output = matcher.replaceAll(replacementStr); return output; // return html; } public String getText() { return stripHTMLTags(super.getText()); } private String stripHTMLTags( String message ) { StringBuffer returnMessage = new StringBuffer(message); int startPosition = message.indexOf("<"); // encountered the first opening brace int endPosition = message.indexOf(">"); // encountered the first closing braces while( startPosition != -1 ) { returnMessage.delete( startPosition, endPosition+1 ); // remove the tag startPosition = (returnMessage.toString()).indexOf("<"); // look for the next opening brace endPosition = (returnMessage.toString()).indexOf(">"); // look for the next closing brace } return org.apache.commons.lang.StringEscapeUtils.unescapeHtml(returnMessage.toString().trim()); } public void overrideTextColor(Color c) { //System.out.println("OVERRIDE TEXT COLOR : " + c.toString()); SimpleAttributeSet set = new SimpleAttributeSet(); String colorString = "#" + Integer.toHexString( c.getRGB()).substring(2); com.lightdev.app.shtm.Util.styleSheet().addCSSAttribute(set, CSS.Attribute.COLOR, colorString); set.addAttribute(HTML.Attribute.COLOR, colorString); this.applyAttributesGlobally(set, true,false); } public void mouseClicked(MouseEvent e) { // TODO Auto-generated method stub } public void mouseEntered(MouseEvent e) { // TODO Auto-generated method stub } public void mouseExited(MouseEvent e) { // TODO Auto-generated method stub } public void mousePressed(MouseEvent e) { if (GUI.isMenuPopup(e)) { displayContextMenu(e); return; } } private void displayContextMenu(MouseEvent e) { getPopup(e).show(e.getComponent(), e.getX(), e.getY()); } private JPopupMenu m = null; private final JMenuItem copyItem = new JMenuItem(VueResources.getString("richtextbox.menu.copy")); private final JMenuItem pasteItem = new JMenuItem(VueResources.getString("richtextbox.menu.paste")); private JPopupMenu getPopup(MouseEvent e) { if (m == null) { m = new JPopupMenu(VueResources.getString("richtextbox.menu.textboxmenu")); //copyItem.addActionListener(this); //pasteItem.addActionListener(this); //If you let this be focusable you'll loose the text box when //the menu gets raised. m.setFocusable(false); m.add(copyItem); m.add(pasteItem); copyItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { RichTextBox.this.copy(); } }); pasteItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { RichTextBox.this.paste(); setSize(getPreferredSize()); } }); } return m; } public void mouseReleased(MouseEvent e) { // TODO Auto-generated method stub } public void actionPerformed(ActionEvent e) { // TODO Auto-generated method stub //if (e.getSource().equals(copyItem)) } }