001/*
002 * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
003 * Copyright (C) 2000-2011 The Gridarta Developers.
004 *
005 * This program is free software; you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation; either version 2 of the License, or
008 * (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License along
016 * with this program; if not, write to the Free Software Foundation, Inc.,
017 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
018 */
019
020package net.sf.gridarta.gui.dialog.help;
021
022import java.awt.Component;
023import java.awt.Cursor;
024import java.io.File;
025import java.io.IOException;
026import java.net.MalformedURLException;
027import java.net.URL;
028import java.util.logging.Level;
029import java.util.logging.Logger;
030import javax.swing.JEditorPane;
031import javax.swing.JScrollPane;
032import javax.swing.JViewport;
033import javax.swing.SwingUtilities;
034import javax.swing.event.HyperlinkEvent;
035import javax.swing.event.HyperlinkListener;
036import javax.swing.text.Document;
037import net.sf.gridarta.utils.CommonConstants;
038import org.jetbrains.annotations.NotNull;
039import org.jetbrains.annotations.Nullable;
040
041/**
042 * Pane for displaying HTML.
043 * @author <a href="mailto:cher@riedquat.de">Christian Hujer</a>
044 */
045class HtmlPane extends JScrollPane implements HyperlinkListener {
046
047    /**
048     * The Logger for printing log messages.
049     */
050    @NotNull
051    private static final Logger log = Logger.getLogger("HtmlPane.class");
052
053    /**
054     * Serial Version UID.
055     */
056    private static final long serialVersionUID = 1L;
057
058    /**
059     * The JEditorPane that displays the html page.
060     */
061    @NotNull
062    private final JEditorPane html;
063
064    /**
065     * Constructor to load the html-file &lt;fileName&gt; and display it's
066     * contents in this HtmlPane.
067     * @param fileName the name of the HTML-file
068     */
069    HtmlPane(@NotNull final String fileName) {
070        try {
071            // first looking for the html file in extracted form
072            final File file = new File(CommonConstants.HELP_DIR, fileName);
073            if (file.exists()) {
074                // file exists in expected directory
075                final String s = "file:" + file.getAbsolutePath();
076                html = new JEditorPane(s);
077            } else {
078                // file missing, so let's look if we can get it from the jar
079                final URL url1 = ClassLoader.getSystemResource(CommonConstants.HELP_DIR.replace('\\', '/') + '/' + fileName);
080
081                if (url1 != null) {
082                    html = new JEditorPane(url1);
083                } else {
084                    // let's try it again without first directory
085                    log.info("trying: HelpFiles/" + fileName);
086                    final URL url2 = ClassLoader.getSystemResource("HelpFiles/" + fileName);
087                    if (url2 != null) {
088                        html = new JEditorPane(url2);
089                    } else {
090                        log.info("Failed to open help file '" + fileName + "'!");
091                        throw new RuntimeException(); // FIXME
092                    }
093                }
094            }
095
096            html.setEditable(false);
097            html.addHyperlinkListener(this);
098            final JViewport vp = getViewport();
099
100            // under windows, the content of the panel get destroyed after scrolling
101            // this will avoid this problem!
102            // but it will make the scrolling slower
103            // so test this!
104            // FIXME: This problem should be fixed in the latest versions of the JRE.
105            getViewport().putClientProperty("EnableWindowBlit", Boolean.TRUE);
106            vp.setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
107
108            vp.add(html);
109            setFocusable(true);
110            requestFocus();
111            setAutoscrolls(true);
112        } catch (final NullPointerException e) {
113            // failed to open the html file
114            throw new RuntimeException(e); // FIXME
115        } catch (final MalformedURLException e) {
116            log.log(Level.WARNING, "Malformed URL: %s", e);
117            throw new RuntimeException(e); // FIXME
118        } catch (final IOException e) {
119            log.log(Level.WARNING, "IOException: %s", e);
120            throw new RuntimeException(e); // FIXME
121        }
122    }
123
124    /**
125     * Constructor to load the html-file &lt;fileName&gt; and display it's
126     * contents in this HtmlPane.
127     * @param type mime-type of the given text (e.g. "text/html")
128     * @param text text to display (can be html-text for example)
129     */
130    HtmlPane(@NotNull final String type, @NotNull final String text) {
131        // open new JEditorPane
132        html = new JEditorPane(type, text);
133        html.setEditable(false);
134        html.addHyperlinkListener(this);
135        final JViewport vp = getViewport();
136
137        // under windows, the content of the panel get destroyed after scrolling
138        // this will avoid this problem!
139        // but it will make the scrolling slower
140        // so test this!
141        // XXX The main issue probably is extending JScrollPane (Cher)
142        getViewport().putClientProperty("EnableWindowBlit", Boolean.TRUE);
143        vp.setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
144
145        vp.add(html);
146        //vp.setView(html);
147        //vp.setViewPosition(new Point(0, 0));
148
149        setAutoscrolls(true);
150    }
151
152    /**
153     * Notification of a change relative to a hyperlink.
154     * @param e occurred <code>HyperlinkEvent</code>
155     */
156    @Override
157    public void hyperlinkUpdate(@NotNull final HyperlinkEvent e) {
158        if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) {
159            linkActivated(e.getURL());
160        }
161    }
162
163    /**
164     * Follows the reference in an link.  The given url is the requested
165     * reference. By default this calls <a href="#setPage">setPage</a>, and if
166     * an exception is thrown the original previous document is restored and a
167     * beep sounded.  If an attempt was made to follow a link, but it
168     * represented a malformed url, this method will be called with a null
169     * argument.
170     * @param u the URL to follow
171     */
172    private void linkActivated(@NotNull final URL u) {
173        final Cursor cursor = html.getCursor();
174        final Cursor waitCursor = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR);
175        html.setCursor(waitCursor);
176        SwingUtilities.invokeLater(new PageLoader(u, cursor));
177    }
178
179    /**
180     * Synchronous page loader, loads a page and handles the cursor.
181     */
182    private class PageLoader implements Runnable {
183
184        /**
185         * URL to load.
186         */
187        @Nullable
188        private URL url;
189
190        /**
191         * Original cursor that should be restored once the document is loaded.
192         */
193        @NotNull
194        private final Cursor cursor;
195
196        /**
197         * Create a PageLoader.
198         * @param url the URL to load
199         * @param cursor the cursor to restore once the document is loaded
200         */
201        private PageLoader(@NotNull final URL url, @NotNull final Cursor cursor) {
202            this.url = url;
203            this.cursor = cursor;
204        }
205
206        @Override
207        public void run() {
208            if (url == null) {
209                // restore the original cursor
210                html.setCursor(cursor);
211
212                // PENDING(prinz) remove this hack when
213                // automatic validation is activated.
214                final Component parent = html.getParent();
215                parent.repaint();
216            } else {
217                final Document doc = html.getDocument();
218                try {
219                    html.setPage(url);
220                } catch (final IOException ioe) {
221                    html.setDocument(doc);
222                    getToolkit().beep();
223                    // TODO not just beep but display an error message as well.
224                } finally {
225                    // schedule the cursor to revert after
226                    // the paint has happened.
227                    url = null;
228                    SwingUtilities.invokeLater(this);
229                }
230            }
231        }
232
233    }
234
235}