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 }