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    }