View Javadoc

1   /*   Copyright (C) 2003 Finalist IT Group
2    *
3    *   This file is part of JAG - the Java J2EE Application Generator
4    *
5    *   JAG is free software; you can redistribute it and/or modify
6    *   it under the terms of the GNU General Public License as published by
7    *   the Free Software Foundation; either version 2 of the License, or
8    *   (at your option) any later version.
9    *   JAG is distributed in the hope that it will be useful,
10   *   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   *   GNU General Public License for more details.
13   *   You should have received a copy of the GNU General Public License
14   *   along with JAG; if not, write to the Free Software
15   *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16   */
17  
18  package com.finalist.jaggenerator;
19  
20  import com.finalist.jaggenerator.JagGenerator;
21  
22  import javax.swing.text.html.HTMLDocument;
23  import javax.swing.*;
24  import javax.swing.event.HyperlinkListener;
25  import javax.swing.event.HyperlinkEvent;
26  import javax.swing.event.DocumentListener;
27  import javax.swing.event.DocumentEvent;
28  import java.net.URL;
29  import java.io.IOException;
30  import java.awt.*;
31  import java.awt.event.KeyEvent;
32  import java.util.*;
33  
34  /***
35   * A general purpose popup window for displaying hyperlinked HTML pages.  The window may be initialised
36   * with either a URL or with a String containing HTML (but a HtmlContentPopUp initialised with a String does
37   * not support hyperlinks).
38   * <p>
39   * The HtmlContentPopup allows hyperlinks to be followed and maintains a page history, which is navigatable
40   * with a back and forward button.  The back and forward buttons also may be triggered by the familiar
41   * keyboard shortcuts (Backspace or ALT-left_arrow for 'back', and ALT+right_arrow for 'forward'), and
42   * the popup may be dismissed with the ESCAPE key.
43   * <p>
44   * The page history maintained by this component includes scrollbar position information. For example if you
45   * hyperlink to document#2 from the <b>end</b> of document#1, and then navigate back to document#1:  you will
46   * find yourself back at the very same place in document#1 where you left it (the end, in this case).
47   * <p>
48   * Any hyperlinked URLs that end in <code>!!!EXTERNAL!!!</code> (<code>HtmlContentPopup.EXTERNAL_TAG</code>)
49   * will attempt to launch the link in an external browser - this is defaulted to Internet Explorer (sorry!).
50   *
51   * @author Michael O'Connor - Finalist IT Group
52   */
53  public class HtmlContentPopUp extends javax.swing.JDialog {
54     /*** A return status code - returned if Cancel button has been pressed */
55     public static final int RET_CANCEL = 0;
56     /*** A return status code - returned if OK button has been pressed */
57     public static final int RET_OK = 1;
58     private String externalBrowserCommand = "\"C://Program Files//Internet Explorer//IEXPLORE.EXE\" ";
59     private final ArrayList pageHistory = new ArrayList();
60     private int historyPos = 0;
61     private String initialContent;
62     private static final String EXTERNAL_TAG = "!!!EXTERNAL!!!";
63     private static final int WIDTH = 600;
64     private static final int HEIGHT = 700;
65  
66  
67     /***
68      * Creates new form HtmlAboutPopUp with the specified content.
69      *
70      * @param parent the parent frame.
71      * @param title the title for the popup.
72      * @param modal whether or not the popup is modal.
73      * @param html the HTML content for the popup.
74      * @param navigation set to <code>true</code> if 'back' and 'forward' navigation buttons are required.
75      */
76     public HtmlContentPopUp(java.awt.Frame parent, String title, boolean modal, String html, boolean navigation) {
77        this(parent, title, modal, html, null, navigation);
78     }
79  
80     /***
81      * Creates new form HtmlAboutPopUp with the specified content.
82      *
83      * @param parent the parent frame.
84      * @param title the title for the popup.
85      * @param modal whether or not the popup is modal.
86      * @param html the HTML content for the popup.
87      */
88     public HtmlContentPopUp(java.awt.Frame parent, String title, boolean modal, String html) {
89        this(parent, title, modal, html, null, true);
90     }
91  
92     /***
93      * Creates new form HtmlAboutPopUp with a specified URL.
94      *
95      * @param parent the parent frame.
96      * @param title the title for the popup.
97      * @param modal whether or not the popup is modal.
98      * @param url the URL of the HTML content.
99      */
100    public HtmlContentPopUp(java.awt.Frame parent, String title, boolean modal, URL url) {
101       this(parent, title, modal, null, url, true);
102    }
103 
104    private HtmlContentPopUp(java.awt.Frame parent, String title, boolean modal,
105                             String html, URL url, boolean navigation) {
106       super(parent, modal);
107       this.initialContent = html;
108       initComponents();
109       if (!navigation) {
110          buttonPanel.remove(navigationPanel);
111          //navigationPanel.setVisible(false);
112       }
113 
114       jTextPane1.addHyperlinkListener(new HyperlinkListener() {
115          public void hyperlinkUpdate(HyperlinkEvent e) {
116             if (initialContent == null && e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
117                try {
118                   URL link = e.getURL();
119                   int externalTagPos = link.toString().indexOf(EXTERNAL_TAG);
120                   if (externalTagPos != -1) {
121                      String realLink = link.toString().substring(0, externalTagPos);
122                      JagGenerator.logToConsole("Launching external browser for " + realLink);
123                      Runtime.getRuntime().exec(externalBrowserCommand + realLink);
124                      return;
125                   }
126                   int size = pageHistory.size();
127                   if (historyPos == size) {
128                      pageHistory.add(new Bookmark());
129                   } else {
130                      updateCurrentBookmark();
131                      for (int i = size - 1; i > historyPos; i--) {
132                         pageHistory.remove(i);
133                      }
134                   }
135                   jTextPane1.setPage(link);
136                   historyPos++;
137                   forwardButton.setEnabled(false);
138                   backButton.setEnabled(true);
139                } catch (IOException e1) {
140                   //dead link - do nothing.
141                }
142             }
143          }
144       });
145 
146       setSize(WIDTH, HEIGHT);
147       Dimension screenSize = (parent == null) ?
148             new JFrame().getToolkit().getScreenSize() : parent.getToolkit().getScreenSize();
149       this.setLocation(
150             (int) ((screenSize.getWidth() / 2) - (WIDTH / 2)),
151             (int) ((screenSize.getHeight() / 2) - (HEIGHT / 2)));
152 
153       setTitle(title);
154       jTextPane1.setEditable(false);
155       if (html == null) {
156          try {
157             jTextPane1.setPage(url);
158             //pageHistory.add(new Bookmark());
159 
160          } catch (IOException e) {
161             html = "Bad URL: " + url;
162             JagGenerator.logToConsole(html);
163             jTextPane1.setText(html);
164          }
165       } else {
166          jTextPane1.setContentType("text/html");
167          jTextPane1.setText(html);
168 
169       }
170       jTextPane1.setCaretPosition(0);
171    }
172 
173 
174    /*** @return the return status of this dialog - one of RET_OK or RET_CANCEL */
175    public int getReturnStatus() {
176       return returnStatus;
177    }
178 
179    public String getExternalBrowserCommand() {
180       return externalBrowserCommand;
181    }
182 
183    public void setExternalBrowserCommand(String externalBrowserCommand) {
184       this.externalBrowserCommand = externalBrowserCommand;
185    }
186 
187    /*** This method is called from within the constructor to
188     * initialize the form.
189     * WARNING: Do NOT modify this code. The content of this method is
190     * always regenerated by the Form Editor.
191     */
192    private void initComponents() {//GEN-BEGIN:initComponents
193       buttonPanel = new javax.swing.JPanel();
194       navigationPanel = new javax.swing.JPanel();
195       backButton = new javax.swing.JButton();
196       forwardButton = new javax.swing.JButton();
197       okButtonPanel = new javax.swing.JPanel();
198       okButton = new javax.swing.JButton();
199       jScrollPane1 = new javax.swing.JScrollPane();
200       jTextPane1 = new javax.swing.JTextPane();
201 
202       setTitle("");
203       addWindowListener(new java.awt.event.WindowAdapter() {
204          public void windowClosing(java.awt.event.WindowEvent evt) {
205             closeDialog(evt);
206          }
207       });
208 
209       buttonPanel.setLayout(new java.awt.BorderLayout());
210 
211       backButton.setText("<<");
212       backButton.setToolTipText("back");
213       backButton.setEnabled(false);
214       backButton.addActionListener(new java.awt.event.ActionListener() {
215          public void actionPerformed(java.awt.event.ActionEvent evt) {
216             backButtonActionPerformed(evt);
217          }
218       });
219 
220       navigationPanel.add(backButton);
221 
222       forwardButton.setText(">>");
223       forwardButton.setToolTipText("forward");
224       forwardButton.setEnabled(false);
225       forwardButton.addActionListener(new java.awt.event.ActionListener() {
226          public void actionPerformed(java.awt.event.ActionEvent evt) {
227             forwardButtonActionPerformed(evt);
228          }
229       });
230 
231       navigationPanel.add(forwardButton);
232 
233       buttonPanel.add(navigationPanel, java.awt.BorderLayout.WEST);
234 
235       okButton.setText("OK");
236       okButton.setSelected(true);
237       okButton.addActionListener(new java.awt.event.ActionListener() {
238          public void actionPerformed(java.awt.event.ActionEvent evt) {
239             okButtonActionPerformed(evt);
240          }
241       });
242 
243       okButtonPanel.add(okButton);
244 
245       buttonPanel.add(okButtonPanel, java.awt.BorderLayout.EAST);
246 
247       getContentPane().add(buttonPanel, java.awt.BorderLayout.SOUTH);
248 
249       jTextPane1.setDocument(new HTMLDocument());
250       jTextPane1.setEditable(false);
251       jTextPane1.setFont(new java.awt.Font("Serif", 1, 14));
252       jTextPane1.addKeyListener(new java.awt.event.KeyAdapter() {
253          public void keyPressed(java.awt.event.KeyEvent evt) {
254             shortcutKeyPressed(evt);
255          }
256       });
257 
258       jScrollPane1.setViewportView(jTextPane1);
259 
260       getContentPane().add(jScrollPane1, java.awt.BorderLayout.CENTER);
261 
262       pack();
263    }//GEN-END:initComponents
264 
265    private void shortcutKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_shortcutKeyPressed
266       if (backButton.isEnabled() &&
267             (evt.getKeyCode() == KeyEvent.VK_BACK_SPACE ||
268             (((evt.getModifiersEx() & KeyEvent.ALT_DOWN_MASK) == KeyEvent.ALT_DOWN_MASK) &&
269             evt.getKeyCode() == KeyEvent.VK_LEFT))) {
270          backButtonActionPerformed(null);
271       } else if (forwardButton.isEnabled() &&
272             (((evt.getModifiersEx() & KeyEvent.ALT_DOWN_MASK) == KeyEvent.ALT_DOWN_MASK) &&
273             evt.getKeyCode() == KeyEvent.VK_RIGHT)) {
274          forwardButtonActionPerformed(null);
275       } else if (evt.getKeyCode() == KeyEvent.VK_ESCAPE) {
276          doClose(RET_CANCEL);
277       }
278 
279    }//GEN-LAST:event_shortcutKeyPressed
280 
281    private void forwardButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_forwardButtonActionPerformed
282       try {
283          updateCurrentBookmark();
284          Bookmark next = (Bookmark) pageHistory.get(++historyPos);
285          gotoBookmark(next);
286          forwardButton.setEnabled(historyPos != (pageHistory.size() - 1));
287          backButton.setEnabled(true);
288       } catch (IOException e1) {
289          //dead link - do nothing.
290       }
291    }//GEN-LAST:event_forwardButtonActionPerformed
292 
293    private void backButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_backButtonActionPerformed
294       try {
295          if (historyPos != pageHistory.size()) {
296             updateCurrentBookmark();
297          } else {
298             pageHistory.add(new Bookmark());
299          }
300 
301          Bookmark previous = (Bookmark) pageHistory.get(--historyPos);
302          gotoBookmark(previous);
303          forwardButton.setEnabled(true);
304          backButton.setEnabled(historyPos != 0);
305 
306       } catch (IOException e1) {
307          //dead link - do nothing.
308       }
309    }//GEN-LAST:event_backButtonActionPerformed
310 
311    private void gotoBookmark(final Bookmark next) throws IOException {
312       jTextPane1.setPage(next.url);
313       waitForSetPageDone(jTextPane1);
314       jScrollPane1.getViewport().setViewPosition(next.pos);
315       //jTextPane1.scrollRectToVisible(next.pos);
316 
317    }
318 
319    private void updateCurrentBookmark() {
320       pageHistory.remove(historyPos);
321       pageHistory.add(historyPos, new Bookmark());
322    }
323 
324    private void waitForSetPageDone(final JTextPane pane) {
325       /* After JTextPane.setPage(url), you need to wait until page is
326        * fully loaded since setPage is asynchronous and returns
327        * immediately! */
328       synchronized (pane) {
329          pane.getDocument().addDocumentListener(new DocumentListener() {
330             public void insertUpdate(DocumentEvent e) {
331             }
332 
333             public void removeUpdate(DocumentEvent e) {
334             }
335 
336             public void changedUpdate(DocumentEvent evt) {
337                synchronized(pane) {
338                   pane.notify();
339                }
340             }
341          });
342          try {
343             pane.wait(1000);
344          }  // release lock, wait for notify
345                // unblock after 1 sec in case hung
346          catch (InterruptedException ie) {
347             ie.printStackTrace();
348          }
349       }
350    }
351 
352    private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
353       doClose(RET_OK);
354    }//GEN-LAST:event_okButtonActionPerformed
355 
356    /*** Closes the dialog */
357    private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog
358       doClose(RET_CANCEL);
359    }//GEN-LAST:event_closeDialog
360 
361    private void doClose(int retStatus) {
362       returnStatus = retStatus;
363       setVisible(false);
364       dispose();
365    }
366 
367 
368    // Variables declaration - do not modify//GEN-BEGIN:variables
369    private javax.swing.JButton backButton;
370    private javax.swing.JPanel buttonPanel;
371    private javax.swing.JButton forwardButton;
372    private javax.swing.JScrollPane jScrollPane1;
373    private javax.swing.JTextPane jTextPane1;
374    private javax.swing.JPanel navigationPanel;
375    private javax.swing.JButton okButton;
376    private javax.swing.JPanel okButtonPanel;
377    // End of variables declaration//GEN-END:variables
378 
379    private int returnStatus = RET_CANCEL;
380 
381 
382    class Bookmark {
383       /***
384        * A Bookmark is a URL of a HTML page, along with a record of the viewport upper-left corner.
385        * This enables navigation to and from <i>a particular position</i> within a page.
386        */
387       public Bookmark() {
388          this.url = jTextPane1.getPage();
389          this.pos = jScrollPane1.getViewport().getViewPosition();
390       }
391 
392       private URL url;
393       private Point pos;
394 
395       public String toString() {
396          return pos + "@" + url.toString().substring(url.toString().lastIndexOf('/'));
397       }
398    }
399 
400 }
401 
402