1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
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
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() {
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 }
264
265 private void shortcutKeyPressed(java.awt.event.KeyEvent evt) {
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 }
280
281 private void forwardButtonActionPerformed(java.awt.event.ActionEvent evt) {
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
290 }
291 }
292
293 private void backButtonActionPerformed(java.awt.event.ActionEvent evt) {
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
308 }
309 }
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
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
326
327
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 }
345
346 catch (InterruptedException ie) {
347 ie.printStackTrace();
348 }
349 }
350 }
351
352 private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {
353 doClose(RET_OK);
354 }
355
356 /*** Closes the dialog */
357 private void closeDialog(java.awt.event.WindowEvent evt) {
358 doClose(RET_CANCEL);
359 }
360
361 private void doClose(int retStatus) {
362 returnStatus = retStatus;
363 setVisible(false);
364 dispose();
365 }
366
367
368
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
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