001 /* 002 * Copyright 2002-2004 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 005 * use this file except in compliance with the License. You may obtain a copy of 006 * the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 * License for the specific language governing permissions and limitations under 014 * the License. 015 */ 016 package org.springframework.richclient.text; 017 018 import java.awt.Font; 019 import java.awt.Graphics; 020 import java.awt.Graphics2D; 021 import java.awt.RenderingHints; 022 import java.awt.event.MouseAdapter; 023 import java.awt.event.MouseEvent; 024 import java.io.IOException; 025 import java.io.StringReader; 026 027 import javax.swing.JTextPane; 028 import javax.swing.UIManager; 029 import javax.swing.event.HyperlinkEvent; 030 import javax.swing.event.HyperlinkListener; 031 import javax.swing.text.Caret; 032 import javax.swing.text.DefaultCaret; 033 import javax.swing.text.html.HTMLDocument; 034 035 /** 036 * An extension of JTextPane for displaying HTML with the system LaF. 037 * 038 * @author Oliver Hutchison 039 */ 040 public class HtmlPane extends JTextPane { 041 042 private boolean antiAlias; 043 044 private Caret caret; 045 046 private boolean allowSelection; 047 048 /** 049 * Creates a new HtmlPane. A default hyperlink activation handler will be 050 * installed. 051 */ 052 public HtmlPane() { 053 this(true); 054 } 055 056 /** 057 * Creates a new HtmlPane. 058 * 059 * @param installHyperlinkActivationHandler 060 * whether to install a default hyperlink activation handler. 061 */ 062 public HtmlPane(boolean installHyperlinkActivationHandler) { 063 setEditorKit(new SynchronousHTMLEditorKit()); 064 setEditable(false); 065 installLaFStyleSheet(); 066 HyperlinkEnterExitBugFixer bugFixer = new HyperlinkEnterExitBugFixer(); 067 addMouseListener(bugFixer); 068 addHyperlinkListener(bugFixer); 069 if (installHyperlinkActivationHandler) { 070 DefaultHyperlinkActivationHandler hyperlinkActivationHandler = new DefaultHyperlinkActivationHandler(); 071 addHyperlinkListener(hyperlinkActivationHandler); 072 } 073 } 074 075 /** 076 * Is the HTML rendered with anti-aliasing. 077 */ 078 public boolean getAntiAlias() { 079 return antiAlias; 080 } 081 082 /** 083 * Set whether the pane should render the HTML using anti-aliasing. 084 */ 085 public void setAntiAlias(boolean antiAlias) { 086 if (this.antiAlias == antiAlias) { 087 return; 088 } 089 this.antiAlias = antiAlias; 090 firePropertyChange("antiAlias", !antiAlias, antiAlias); 091 repaint(); 092 } 093 094 /** 095 * Is selection allowed in this pane. 096 */ 097 public boolean getAllowSelection() { 098 return allowSelection; 099 } 100 101 /** 102 * Set whether or not selection should be allowed in this pane. 103 */ 104 public void setAllowSelection(boolean allowSelection) { 105 if (this.allowSelection == allowSelection) { 106 return; 107 } 108 this.allowSelection = allowSelection; 109 setCaretInternal(); 110 firePropertyChange("allowSelection", !allowSelection, allowSelection); 111 } 112 113 public void setCaret(Caret caret) { 114 this.caret = caret; 115 setCaretInternal(); 116 } 117 118 public Caret getCaret() { 119 return caret; 120 } 121 122 private void setCaretInternal() { 123 if (allowSelection) { 124 super.setCaret(caret); 125 } 126 else { 127 super.setCaret(new NoSelectionCaret()); 128 } 129 } 130 131 /** 132 * Applies the current LaF font setting to the document. 133 */ 134 protected void installLaFStyleSheet() { 135 Font defaultFont = UIManager.getFont("Button.font"); 136 String stylesheet = "body { font-family: " + defaultFont.getName() + "; font-size: " + defaultFont.getSize() 137 + "pt; }" + "a, p, li { font-family: " + defaultFont.getName() + "; font-size: " 138 + defaultFont.getSize() + "pt; }"; 139 try { 140 ((HTMLDocument)getDocument()).getStyleSheet().loadRules(new StringReader(stylesheet), null); 141 } 142 catch (IOException e) { 143 } 144 } 145 146 public void paintComponent(Graphics g) { 147 if (antiAlias) { 148 Graphics2D g2 = (Graphics2D)g; 149 g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 150 g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); 151 } 152 super.paintComponent(g); 153 } 154 155 private static class NoSelectionCaret extends DefaultCaret { 156 public void mouseDragged(MouseEvent e) { 157 } 158 159 public void mouseMoved(MouseEvent e) { 160 } 161 162 public void mouseClicked(MouseEvent e) { 163 } 164 } 165 166 /* 167 * Fixes a bug in the handling of HyperlinkEvents in JEditorPane. If a 168 * hyperlink is active when the mouse exits the pane no Hyperlink EXITED 169 * event is fired. 170 */ 171 private class HyperlinkEnterExitBugFixer extends MouseAdapter implements HyperlinkListener { 172 private boolean hyperlinkActive; 173 174 public void mouseExited(MouseEvent e) { 175 if (hyperlinkActive) { 176 fireHyperlinkUpdate(new HyperlinkEvent(HtmlPane.this, HyperlinkEvent.EventType.EXITED, null)); 177 hyperlinkActive = true; 178 } 179 } 180 181 public void mouseEntered(MouseEvent e) { 182 if (hyperlinkActive) { 183 fireHyperlinkUpdate(new HyperlinkEvent(HtmlPane.this, HyperlinkEvent.EventType.ENTERED, null)); 184 } 185 } 186 187 public void hyperlinkUpdate(HyperlinkEvent e) { 188 if (e.getEventType().equals(HyperlinkEvent.EventType.ENTERED)) { 189 hyperlinkActive = true; 190 } 191 else if (e.getEventType().equals(HyperlinkEvent.EventType.EXITED)) { 192 hyperlinkActive = false; 193 } 194 } 195 } 196 }