001: /* Copyright (C) 2003 Finalist IT Group
002: *
003: * This file is part of JAG - the Java J2EE Application Generator
004: *
005: * JAG 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: * JAG is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU General Public License for more details.
013: * You should have received a copy of the GNU General Public License
014: * along with JAG; if not, write to the Free Software
015: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
016: */
017:
018: package com.finalist.jaggenerator;
019:
020: import com.finalist.jaggenerator.JagGenerator;
021:
022: import javax.swing.text.html.HTMLDocument;
023: import javax.swing.*;
024: import javax.swing.event.HyperlinkListener;
025: import javax.swing.event.HyperlinkEvent;
026: import javax.swing.event.DocumentListener;
027: import javax.swing.event.DocumentEvent;
028: import java.net.URL;
029: import java.io.IOException;
030: import java.awt.*;
031: import java.awt.event.KeyEvent;
032: import java.util.*;
033:
034: /**
035: * A general purpose popup window for displaying hyperlinked HTML pages. The window may be initialised
036: * with either a URL or with a String containing HTML (but a HtmlContentPopUp initialised with a String does
037: * not support hyperlinks).
038: * <p>
039: * The HtmlContentPopup allows hyperlinks to be followed and maintains a page history, which is navigatable
040: * with a back and forward button. The back and forward buttons also may be triggered by the familiar
041: * keyboard shortcuts (Backspace or ALT-left_arrow for 'back', and ALT+right_arrow for 'forward'), and
042: * the popup may be dismissed with the ESCAPE key.
043: * <p>
044: * The page history maintained by this component includes scrollbar position information. For example if you
045: * hyperlink to document#2 from the <b>end</b> of document#1, and then navigate back to document#1: you will
046: * find yourself back at the very same place in document#1 where you left it (the end, in this case).
047: * <p>
048: * Any hyperlinked URLs that end in <code>!!!EXTERNAL!!!</code> (<code>HtmlContentPopup.EXTERNAL_TAG</code>)
049: * will attempt to launch the link in an external browser - this is defaulted to Internet Explorer (sorry!).
050: *
051: * @author Michael O'Connor - Finalist IT Group
052: */
053: public class HtmlContentPopUp extends javax.swing.JDialog {
054: /** A return status code - returned if Cancel button has been pressed */
055: public static final int RET_CANCEL = 0;
056: /** A return status code - returned if OK button has been pressed */
057: public static final int RET_OK = 1;
058: private String externalBrowserCommand = "\"C:\\Program Files\\Internet Explorer\\IEXPLORE.EXE\" ";
059: private final ArrayList pageHistory = new ArrayList();
060: private int historyPos = 0;
061: private String initialContent;
062: private static final String EXTERNAL_TAG = "!!!EXTERNAL!!!";
063: private static final int WIDTH = 600;
064: private static final int HEIGHT = 700;
065:
066: /**
067: * Creates new form HtmlAboutPopUp with the specified content.
068: *
069: * @param parent the parent frame.
070: * @param title the title for the popup.
071: * @param modal whether or not the popup is modal.
072: * @param html the HTML content for the popup.
073: * @param navigation set to <code>true</code> if 'back' and 'forward' navigation buttons are required.
074: */
075: public HtmlContentPopUp(java.awt.Frame parent, String title,
076: boolean modal, String html, boolean navigation) {
077: this (parent, title, modal, html, null, navigation);
078: }
079:
080: /**
081: * Creates new form HtmlAboutPopUp with the specified content.
082: *
083: * @param parent the parent frame.
084: * @param title the title for the popup.
085: * @param modal whether or not the popup is modal.
086: * @param html the HTML content for the popup.
087: */
088: public HtmlContentPopUp(java.awt.Frame parent, String title,
089: boolean modal, String html) {
090: this (parent, title, modal, html, null, true);
091: }
092:
093: /**
094: * Creates new form HtmlAboutPopUp with a specified URL.
095: *
096: * @param parent the parent frame.
097: * @param title the title for the popup.
098: * @param modal whether or not the popup is modal.
099: * @param url the URL of the HTML content.
100: */
101: public HtmlContentPopUp(java.awt.Frame parent, String title,
102: boolean modal, URL url) {
103: this (parent, title, modal, null, url, true);
104: }
105:
106: private HtmlContentPopUp(java.awt.Frame parent, String title,
107: boolean modal, String html, URL url, boolean navigation) {
108: super (parent, modal);
109: this .initialContent = html;
110: initComponents();
111: if (!navigation) {
112: buttonPanel.remove(navigationPanel);
113: //navigationPanel.setVisible(false);
114: }
115:
116: jTextPane1.addHyperlinkListener(new HyperlinkListener() {
117: public void hyperlinkUpdate(HyperlinkEvent e) {
118: if (initialContent == null
119: && e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
120: try {
121: URL link = e.getURL();
122: int externalTagPos = link.toString().indexOf(
123: EXTERNAL_TAG);
124: if (externalTagPos != -1) {
125: String realLink = link.toString()
126: .substring(0, externalTagPos);
127: JagGenerator
128: .logToConsole("Launching external browser for "
129: + realLink);
130: Runtime.getRuntime().exec(
131: externalBrowserCommand + realLink);
132: return;
133: }
134: int size = pageHistory.size();
135: if (historyPos == size) {
136: pageHistory.add(new Bookmark());
137: } else {
138: updateCurrentBookmark();
139: for (int i = size - 1; i > historyPos; i--) {
140: pageHistory.remove(i);
141: }
142: }
143: jTextPane1.setPage(link);
144: historyPos++;
145: forwardButton.setEnabled(false);
146: backButton.setEnabled(true);
147: } catch (IOException e1) {
148: //dead link - do nothing.
149: }
150: }
151: }
152: });
153:
154: setSize(WIDTH, HEIGHT);
155: Dimension screenSize = (parent == null) ? new JFrame()
156: .getToolkit().getScreenSize() : parent.getToolkit()
157: .getScreenSize();
158: this .setLocation(
159: (int) ((screenSize.getWidth() / 2) - (WIDTH / 2)),
160: (int) ((screenSize.getHeight() / 2) - (HEIGHT / 2)));
161:
162: setTitle(title);
163: jTextPane1.setEditable(false);
164: if (html == null) {
165: try {
166: jTextPane1.setPage(url);
167: //pageHistory.add(new Bookmark());
168:
169: } catch (IOException e) {
170: html = "Bad URL: " + url;
171: JagGenerator.logToConsole(html);
172: jTextPane1.setText(html);
173: }
174: } else {
175: jTextPane1.setContentType("text/html");
176: jTextPane1.setText(html);
177:
178: }
179: jTextPane1.setCaretPosition(0);
180: }
181:
182: /** @return the return status of this dialog - one of RET_OK or RET_CANCEL */
183: public int getReturnStatus() {
184: return returnStatus;
185: }
186:
187: public String getExternalBrowserCommand() {
188: return externalBrowserCommand;
189: }
190:
191: public void setExternalBrowserCommand(String externalBrowserCommand) {
192: this .externalBrowserCommand = externalBrowserCommand;
193: }
194:
195: /** This method is called from within the constructor to
196: * initialize the form.
197: * WARNING: Do NOT modify this code. The content of this method is
198: * always regenerated by the Form Editor.
199: */
200: private void initComponents() {//GEN-BEGIN:initComponents
201: buttonPanel = new javax.swing.JPanel();
202: navigationPanel = new javax.swing.JPanel();
203: backButton = new javax.swing.JButton();
204: forwardButton = new javax.swing.JButton();
205: okButtonPanel = new javax.swing.JPanel();
206: okButton = new javax.swing.JButton();
207: jScrollPane1 = new javax.swing.JScrollPane();
208: jTextPane1 = new javax.swing.JTextPane();
209:
210: setTitle("");
211: addWindowListener(new java.awt.event.WindowAdapter() {
212: public void windowClosing(java.awt.event.WindowEvent evt) {
213: closeDialog(evt);
214: }
215: });
216:
217: buttonPanel.setLayout(new java.awt.BorderLayout());
218:
219: backButton.setText("<<");
220: backButton.setToolTipText("back");
221: backButton.setEnabled(false);
222: backButton
223: .addActionListener(new java.awt.event.ActionListener() {
224: public void actionPerformed(
225: java.awt.event.ActionEvent evt) {
226: backButtonActionPerformed(evt);
227: }
228: });
229:
230: navigationPanel.add(backButton);
231:
232: forwardButton.setText(">>");
233: forwardButton.setToolTipText("forward");
234: forwardButton.setEnabled(false);
235: forwardButton
236: .addActionListener(new java.awt.event.ActionListener() {
237: public void actionPerformed(
238: java.awt.event.ActionEvent evt) {
239: forwardButtonActionPerformed(evt);
240: }
241: });
242:
243: navigationPanel.add(forwardButton);
244:
245: buttonPanel.add(navigationPanel, java.awt.BorderLayout.WEST);
246:
247: okButton.setText("OK");
248: okButton.setSelected(true);
249: okButton.addActionListener(new java.awt.event.ActionListener() {
250: public void actionPerformed(java.awt.event.ActionEvent evt) {
251: okButtonActionPerformed(evt);
252: }
253: });
254:
255: okButtonPanel.add(okButton);
256:
257: buttonPanel.add(okButtonPanel, java.awt.BorderLayout.EAST);
258:
259: getContentPane().add(buttonPanel, java.awt.BorderLayout.SOUTH);
260:
261: jTextPane1.setDocument(new HTMLDocument());
262: jTextPane1.setEditable(false);
263: jTextPane1.setFont(new java.awt.Font("Serif", 1, 14));
264: jTextPane1.addKeyListener(new java.awt.event.KeyAdapter() {
265: public void keyPressed(java.awt.event.KeyEvent evt) {
266: shortcutKeyPressed(evt);
267: }
268: });
269:
270: jScrollPane1.setViewportView(jTextPane1);
271:
272: getContentPane()
273: .add(jScrollPane1, java.awt.BorderLayout.CENTER);
274:
275: pack();
276: }//GEN-END:initComponents
277:
278: private void shortcutKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_shortcutKeyPressed
279: if (backButton.isEnabled()
280: && (evt.getKeyCode() == KeyEvent.VK_BACK_SPACE || (((evt
281: .getModifiersEx() & KeyEvent.ALT_DOWN_MASK) == KeyEvent.ALT_DOWN_MASK) && evt
282: .getKeyCode() == KeyEvent.VK_LEFT))) {
283: backButtonActionPerformed(null);
284: } else if (forwardButton.isEnabled()
285: && (((evt.getModifiersEx() & KeyEvent.ALT_DOWN_MASK) == KeyEvent.ALT_DOWN_MASK) && evt
286: .getKeyCode() == KeyEvent.VK_RIGHT)) {
287: forwardButtonActionPerformed(null);
288: } else if (evt.getKeyCode() == KeyEvent.VK_ESCAPE) {
289: doClose(RET_CANCEL);
290: }
291:
292: }//GEN-LAST:event_shortcutKeyPressed
293:
294: private void forwardButtonActionPerformed(
295: java.awt.event.ActionEvent evt) {//GEN-FIRST:event_forwardButtonActionPerformed
296: try {
297: updateCurrentBookmark();
298: Bookmark next = (Bookmark) pageHistory.get(++historyPos);
299: gotoBookmark(next);
300: forwardButton
301: .setEnabled(historyPos != (pageHistory.size() - 1));
302: backButton.setEnabled(true);
303: } catch (IOException e1) {
304: //dead link - do nothing.
305: }
306: }//GEN-LAST:event_forwardButtonActionPerformed
307:
308: private void backButtonActionPerformed(
309: java.awt.event.ActionEvent evt) {//GEN-FIRST:event_backButtonActionPerformed
310: try {
311: if (historyPos != pageHistory.size()) {
312: updateCurrentBookmark();
313: } else {
314: pageHistory.add(new Bookmark());
315: }
316:
317: Bookmark previous = (Bookmark) pageHistory
318: .get(--historyPos);
319: gotoBookmark(previous);
320: forwardButton.setEnabled(true);
321: backButton.setEnabled(historyPos != 0);
322:
323: } catch (IOException e1) {
324: //dead link - do nothing.
325: }
326: }//GEN-LAST:event_backButtonActionPerformed
327:
328: private void gotoBookmark(final Bookmark next) throws IOException {
329: jTextPane1.setPage(next.url);
330: waitForSetPageDone(jTextPane1);
331: jScrollPane1.getViewport().setViewPosition(next.pos);
332: //jTextPane1.scrollRectToVisible(next.pos);
333:
334: }
335:
336: private void updateCurrentBookmark() {
337: pageHistory.remove(historyPos);
338: pageHistory.add(historyPos, new Bookmark());
339: }
340:
341: private void waitForSetPageDone(final JTextPane pane) {
342: /* After JTextPane.setPage(url), you need to wait until page is
343: * fully loaded since setPage is asynchronous and returns
344: * immediately! */
345: synchronized (pane) {
346: pane.getDocument().addDocumentListener(
347: new DocumentListener() {
348: public void insertUpdate(DocumentEvent e) {
349: }
350:
351: public void removeUpdate(DocumentEvent e) {
352: }
353:
354: public void changedUpdate(DocumentEvent evt) {
355: synchronized (pane) {
356: pane.notify();
357: }
358: }
359: });
360: try {
361: pane.wait(1000);
362: } // release lock, wait for notify
363: // unblock after 1 sec in case hung
364: catch (InterruptedException ie) {
365: ie.printStackTrace();
366: }
367: }
368: }
369:
370: private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed
371: doClose(RET_OK);
372: }//GEN-LAST:event_okButtonActionPerformed
373:
374: /** Closes the dialog */
375: private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog
376: doClose(RET_CANCEL);
377: }//GEN-LAST:event_closeDialog
378:
379: private void doClose(int retStatus) {
380: returnStatus = retStatus;
381: setVisible(false);
382: dispose();
383: }
384:
385: // Variables declaration - do not modify//GEN-BEGIN:variables
386: private javax.swing.JButton backButton;
387: private javax.swing.JPanel buttonPanel;
388: private javax.swing.JButton forwardButton;
389: private javax.swing.JScrollPane jScrollPane1;
390: private javax.swing.JTextPane jTextPane1;
391: private javax.swing.JPanel navigationPanel;
392: private javax.swing.JButton okButton;
393: private javax.swing.JPanel okButtonPanel;
394: // End of variables declaration//GEN-END:variables
395:
396: private int returnStatus = RET_CANCEL;
397:
398: class Bookmark {
399: /**
400: * A Bookmark is a URL of a HTML page, along with a record of the viewport upper-left corner.
401: * This enables navigation to and from <i>a particular position</i> within a page.
402: */
403: public Bookmark() {
404: this .url = jTextPane1.getPage();
405: this .pos = jScrollPane1.getViewport().getViewPosition();
406: }
407:
408: private URL url;
409: private Point pos;
410:
411: public String toString() {
412: return pos
413: + "@"
414: + url.toString().substring(
415: url.toString().lastIndexOf('/'));
416: }
417: }
418:
419: }
|