source: trunk/src/org/expeditee/items/widgets/JfxBrowser.java@ 627

Last change on this file since 627 was 627, checked in by jts21, 11 years ago

Add error message if JfxBrowser fails to load.

  • Property svn:executable set to *
File size: 21.4 KB
Line 
1package org.expeditee.items.widgets;
2
3import java.awt.BorderLayout;
4import java.awt.Color;
5import java.awt.Component;
6import java.awt.Font;
7import java.awt.Point;
8import java.awt.event.ActionEvent;
9import java.awt.event.ActionListener;
10import java.awt.event.ComponentEvent;
11import java.awt.event.ComponentListener;
12import java.awt.event.FocusEvent;
13import java.awt.event.FocusListener;
14import java.awt.event.KeyEvent;
15import java.awt.event.KeyListener;
16import java.awt.event.MouseEvent;
17import java.awt.event.MouseListener;
18import java.awt.event.MouseMotionListener;
19import java.lang.reflect.InvocationTargetException;
20
21import javax.swing.JButton;
22import javax.swing.JLabel;
23import javax.swing.JPanel;
24import javax.swing.JTextField;
25import javax.swing.JToolBar;
26import javax.swing.SwingConstants;
27import javax.swing.text.BadLocationException;
28
29import org.expeditee.gui.DisplayIO;
30import org.expeditee.gui.FrameMouseActions;
31import org.expeditee.gui.FreeItems;
32import org.expeditee.gui.MessageBay;
33import org.expeditee.io.WebParser;
34import org.expeditee.items.Item;
35import org.expeditee.items.Text;
36import org.expeditee.reflection.JavaFX;
37import org.expeditee.settings.network.NetworkSettings;
38
39/**
40 * Web browser using a JavaFX WebView.
41 *
42 * @author ngw8
43 * @author jts21
44 */
45public class JfxBrowser extends DataFrameWidget {
46
47 private static final String BACK = "back";
48 private static final String FORWARD = "forward";
49 private static final String REFRESH = "refresh";
50 private static final String GETFRAME = "getframe";
51
52 public static final int VERT_OFFSET = 50;
53 public static final int VERT_CROP = 88;
54 public static final int HORZ_CROP = 15;
55
56 private WebBrowserPanel _browser;
57
58 /**
59 * ID of the mouse button that is currently pressed. Uses {@link MouseEvent} constants
60 */
61 private int buttonDownId;
62
63 private static class WebBrowserPanel extends JPanel implements ComponentListener {
64
65 private static final long serialVersionUID = 1L;
66
67 Object webview;
68 Object jfxPanel;
69 JfxBrowser owner;
70
71 /**
72 * ID of the mouse button that is currently pressed.
73 * <p>
74 * Uses {@link MouseEvent} constants
75 */
76 private int buttonDownId;
77
78 private JTextField urlField;
79
80 /**
81 * @return A JPanel that either contains a JavaFX Webview or an empty panel if the JFX panel can't be initialized
82 */
83 public WebBrowserPanel(final String url) {
84
85 super(new BorderLayout());
86 this.addComponentListener(this);
87 try {
88 jfxPanel = JavaFX.JFXPanel.newInstance();
89
90 // Toolbar (that can't be dragged) to hold the nav buttons, url bar, etc
91 JToolBar toolBar = new JToolBar();
92 toolBar.setFloatable(false);
93
94
95 JButton backButton = new JButton("Back");
96 toolBar.add(backButton);
97
98 JButton forwardButton = new JButton("Forward");
99 toolBar.add(forwardButton);
100
101 urlField = new JTextField(url);
102 toolBar.add(urlField);
103
104 JButton convertButton = new JButton("Convert");
105 toolBar.add(convertButton);
106
107 backButton.addActionListener(new ActionListener() {
108 @Override
109 public void actionPerformed(ActionEvent arg0) {
110 owner.navigateBack();
111 }
112 });
113
114 forwardButton.addActionListener(new ActionListener() {
115 @Override
116 public void actionPerformed(ActionEvent arg0) {
117 owner.navigateForward();
118 }
119 });
120
121 convertButton.addActionListener(new ActionListener() {
122 @Override
123 public void actionPerformed(ActionEvent arg0) {
124 owner.getFrame();
125 }
126 });
127
128 urlField.addActionListener(new ActionListener() {
129 @Override
130 public void actionPerformed(ActionEvent e) {
131 owner.navigate(e.getActionCommand());
132 }
133 });
134
135 urlField.addKeyListener(new KeyListener() {
136
137 @Override
138 public void keyTyped(KeyEvent arg0) {
139 // Hiding the cursor when typing, to be more Expeditee-like
140 DisplayIO.setCursor(org.expeditee.items.Item.HIDDEN_CURSOR);
141 }
142
143 @Override
144 public void keyReleased(KeyEvent arg0) { // TODO: remove if not used
145 }
146
147 @Override
148 public void keyPressed(KeyEvent arg0) { // TODO: remove if not used
149 }
150 });
151
152 urlField.addMouseMotionListener(new MouseMotionListener() {
153
154 @Override
155 public void mouseMoved(MouseEvent arg0) {
156 // Checking if the user has been typing - if so, move the cursor to the caret position
157 if (DisplayIO.getCursor() == Item.HIDDEN_CURSOR) {
158 DisplayIO.setCursor(org.expeditee.items.Item.TEXT_CURSOR);
159 try {
160 DisplayIO.setCursorPosition(
161 // Expeditee gives coords relative to the window, Swing gives it relative to the
162 // screen
163 urlField.getLocationOnScreen().x + urlField.modelToView(urlField.getCaretPosition() + 1).x - org.expeditee.gui.Browser._theBrowser.getOrigin().x,
164 urlField.getLocationOnScreen().y + urlField.modelToView(urlField.getCaretPosition()).y - org.expeditee.gui.Browser._theBrowser.getOrigin().y);
165
166 } catch (BadLocationException e) {}
167 } else {
168 // Otherwise, move the caret to the cursor location
169 urlField.setCaretPosition(urlField.viewToModel(arg0.getPoint()));
170 }
171 }
172
173 @Override
174 public void mouseDragged(MouseEvent arg0) {
175 // Selecting text when the mouse is dragged with the middle or right button pressed. If-else
176 // determines which direction the user is selecting in
177 if (buttonDownId == MouseEvent.BUTTON2 || buttonDownId == MouseEvent.BUTTON3) {
178 if (urlField.viewToModel(arg0.getPoint()) < urlField.getSelectionEnd()) {
179 urlField.select(urlField.viewToModel(arg0.getPoint()), urlField.getSelectionEnd());
180 } else {
181 urlField.select(urlField.getSelectionStart(), urlField.viewToModel(arg0.getPoint()));
182 }
183 }
184 }
185 });
186
187 urlField.addFocusListener(new FocusListener() {
188
189 @Override
190 public void focusGained(FocusEvent arg0) {
191 DisplayIO.setCursor(org.expeditee.items.Item.TEXT_CURSOR);
192 }
193
194 @Override
195 public void focusLost(FocusEvent arg0) {
196 // Restoring the standard cursor, since it is changed to a text cursor when focus is gained
197 DisplayIO.setCursor(org.expeditee.items.Item.DEFAULT_CURSOR);
198 }
199 });
200
201 urlField.addMouseListener(new MouseListener() {
202
203 @Override
204 public void mouseReleased(MouseEvent arg0) {
205 buttonDownId = MouseEvent.NOBUTTON;
206
207 Text item;
208
209 // If nothing is selected, then select all the text so that it will be copied/moved
210 if (urlField.getSelectedText() == null) {
211 urlField.selectAll();
212 }
213
214 if (arg0.getButton() == MouseEvent.BUTTON3) {
215 // Right mouse button released, so copy the selection (i.e. don't remove the original)
216 item = DisplayIO.getCurrentFrame().createNewText(urlField.getSelectedText());
217 FrameMouseActions.pickup(item);
218 } else if (arg0.getButton() == MouseEvent.BUTTON2) {
219 // Middle mouse button released, so copy the selection then remove it from the URL field
220 item = DisplayIO.getCurrentFrame().createNewText(urlField.getSelectedText());
221 urlField.setText(urlField.getText().substring(0, urlField.getSelectionStart()) + urlField.getText().substring(urlField.getSelectionEnd(), urlField.getText().length()));
222
223 FrameMouseActions.pickup(item);
224 }
225 }
226
227 @Override
228 public void mousePressed(MouseEvent arg0) {
229 buttonDownId = arg0.getButton();
230
231 }
232
233 @Override
234 public void mouseExited(MouseEvent arg0) {
235 }
236
237 @Override
238 public void mouseEntered(MouseEvent arg0) {
239 }
240
241 @Override
242 public void mouseClicked(MouseEvent arg0) {
243 }
244 });
245
246 add(toolBar, BorderLayout.PAGE_START);
247
248 this.add((Component) jfxPanel);
249
250 JavaFX.PlatformRunLater.invoke(null, new Runnable() {
251 @Override
252 public void run() {
253 initFx(url);
254 }
255 });
256 } catch (Exception e) {
257 e.printStackTrace();
258 // if this occurs JavaFX was probably missing
259 this.removeAll();
260 JLabel label = new JLabel("<html><p style=\"text-align: center;\">Failed to load<br/>Most likely JavaFX could not be found<br/>Make sure you are running at least Java 7<br/>(Preferably Java 8)</p></html>");
261 label.setHorizontalAlignment(SwingConstants.CENTER);
262 label.setForeground(Color.RED);
263 label.setFont(new Font("sans-serif", 0, 20));
264 this.add(label);
265 }
266
267 // Setting bg color to be the same as bg color of widgets when they're being dragged/resized
268 this.setBackground(new Color(100, 100, 100));
269 }
270
271 /**
272 * Sets up the browser frame. JFX requires this to be run on a new thread.
273 *
274 * @param url
275 * The URL to be loaded when the browser is created
276 */
277 private void initFx(String url) {
278 try {
279 this.webview = JavaFX.WebView.newInstance();
280
281 Object scene = JavaFX.SceneConstructor.newInstance(this.webview);
282
283 JavaFX.JFXPanelSetScene.invoke(jfxPanel, scene);
284
285 JavaFX.ReadOnlyObjectPropertyAddListener.invoke(JavaFX.WorkerStateProperty.invoke(JavaFX.WebEngineGetLoadWorker
286 .invoke(JavaFX.WebViewGetEngine.invoke(this.webview))), java.lang.reflect.Proxy.newProxyInstance(
287 JavaFX.ChangeListener.getClassLoader(), new java.lang.Class[] { JavaFX.ChangeListener },
288 new java.lang.reflect.InvocationHandler() {
289 @Override
290 public Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args)
291 throws java.lang.Throwable {
292 String method_name = method.getName();
293 // Class<?>[] classes = method.getParameterTypes();
294 // public void changed(ObservableValue ov, State oldState, State newState)
295 if (method_name.equals("changed")) {
296 // changed takes 3 args
297 if (args == null || args.length != 3) {
298 return null;
299 }
300 // args[0] is the ObservableValue
301 System.out.println(args[0]);
302 System.out.println(args[0].getClass());
303 // args[2] is the new State
304 if (args[2].getClass() == JavaFX.State) {
305 int id = JavaFX.StateConstants.indexOf(args[2]);
306 switch (id) {
307 case 0: // READY
308 MessageBay.displayMessage("WebEngine ready");
309 break;
310 case 1: // SCHEDULED
311 MessageBay.displayMessage("Scheduled page load");
312 break;
313 case 2: // RUNNING
314 MessageBay.displayMessage("WebEngine running");
315
316 // Updating the URL bar to display the URL of the page being loaded
317 WebBrowserPanel.this.urlField.setText((String) JavaFX.WebEngineGetLocation.invoke(JavaFX.WebViewGetEngine.invoke(WebBrowserPanel.this.webview)));
318 break;
319 case 3: // SUCCEEDED
320 MessageBay.displayMessage("Finished loading page");
321 break;
322 case 4: // CANCELLED
323 MessageBay.displayMessage("Cancelled loading page");
324 break;
325 case 5: // FAILED
326 MessageBay.displayMessage("Failed to load page");
327 break;
328 }
329 }
330 System.out.println("\n");
331 }
332 return null;
333 }
334 }));
335
336 // Captures mouse click events on webview to enable expeditee like behavior for JavaFX browser.
337 JavaFX.NodeSetOnMouseClicked.invoke(this.webview, java.lang.reflect.Proxy.newProxyInstance(
338 JavaFX.EventHandler.getClassLoader(), new java.lang.Class[] { JavaFX.EventHandler },
339 new java.lang.reflect.InvocationHandler() {
340 @Override
341 public Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args)
342 throws java.lang.Throwable {
343
344 if(method.getName().equals("handle")) {
345 // Gets button clicked enum values
346 Object[] buttonEnum = JavaFX.MouseButton.getEnumConstants();
347
348 if(JavaFX.MouseEventGetButton.invoke(args[0]).equals(buttonEnum[1])) /* Left Mouse Button */ {
349
350 } else if(JavaFX.MouseEventGetButton.invoke(args[0]).equals(buttonEnum[2])) /* Middle Mouse Button */ {
351
352 } else if(JavaFX.MouseEventGetButton.invoke(args[0]).equals(buttonEnum[3])) /* Right Mouse Button */ {
353 // Gets text currently selected in webview
354 String selection = (String) JavaFX.WebEngineExecuteScript.invoke(JavaFX.WebViewGetEngine.invoke(webview), "window.getSelection().toString()");
355
356 // Copy text and attach to cursor
357 Text t = new Text(selection);
358 t.setParent(DisplayIO.getCurrentFrame());
359 t.setXY(FrameMouseActions.getX(), FrameMouseActions.getY());
360 FrameMouseActions.pickup(t);
361 }
362 }
363
364 return null;
365 }
366 }));
367
368 // Disables JavaFX webview's right click menu
369 JavaFX.WebViewSetContextMenuEnabled.invoke(this.webview, false);
370
371 // webEngine.getLoadWorker().stateProperty().addListener(
372 // new ChangeListener<State>() {
373 // public void changed(ObservableValue ov, State oldState, State newState) {
374 // if (newState == State.SUCCEEDED) {
375 // stage.setTitle(webEngine.getLocation());
376 // }
377 // }
378 // });
379
380 JavaFX.WebEngineLoad.invoke(JavaFX.WebViewGetEngine.invoke(this.webview), url);
381 } catch (Exception e) {
382 e.printStackTrace();
383 }
384 }
385
386 @Override
387 public void componentHidden(ComponentEvent e) {
388 }
389
390 @Override
391 public void componentMoved(ComponentEvent e) {
392 }
393
394 @Override
395 public void componentResized(ComponentEvent e) {
396 // BorderLayout on the JPanel removes the need for explicitly resizing anything here
397 }
398
399 @Override
400 public void componentShown(ComponentEvent e) {
401 }
402 }
403
404 public JfxBrowser(Text source, String[] args) {
405 // Initial page is either the page stored in the arguments (if there is one stored) or the homepage
406 super(source, new WebBrowserPanel((args != null && args.length > 0) ? args[0] : NetworkSettings.homePage), -1, 500, -1, -1, 300, -1);
407 _browser = (WebBrowserPanel) _swingComponent;
408 _browser.owner = this;
409 }
410
411 public void navigate(String url) {
412 final String actualURL;
413 String urlLower = url.toLowerCase();
414 // check if protocol is missing
415 if (!(urlLower.startsWith("http://") || url.startsWith("https://") || urlLower.startsWith("ftp://") || urlLower.startsWith("file://"))) {
416 // check if it's a search
417 int firstSpace = url.indexOf(" ");
418 int firstDot = url.indexOf(".");
419 int firstSlash = url.indexOf('/');
420 int firstQuestion = url.indexOf('?');
421 int firstSQ;
422 if(firstSlash == -1) {
423 firstSQ = firstQuestion;
424 } else if(firstQuestion == -1) {
425 firstSQ = firstSlash;
426 } else {
427 firstSQ = -1;
428 }
429 if(firstDot <= 0 || // no '.' or starts with '.' -> search
430 (firstSpace != -1 && firstSpace < firstDot + 1) || // ' ' before '.' -> search
431 (firstSpace != -1 && firstSpace < firstSQ)) { // no '/' or '?' -> search
432 // make it a search
433 actualURL = NetworkSettings.searchEngine + url;
434 } else {
435 // add the missing protocol
436 actualURL = "http://" + url;
437 }
438 } else {
439 actualURL = url;
440 }
441 System.out.println(actualURL);
442 try {
443 JavaFX.PlatformRunLater.invoke(null, new Runnable() {
444 @Override
445 public void run() {
446 try {
447 JavaFX.WebEngineLoad.invoke(JavaFX.WebViewGetEngine.invoke(JfxBrowser.this._browser.webview), actualURL);
448 } catch (Exception e) {
449 e.printStackTrace();
450 }
451 }
452 });
453 } catch (Exception e) {
454 e.printStackTrace();
455 }
456 }
457
458 /**
459 * Navigates JfxBrowser back through history. If end of history reached the user is notified via the MessageBay.
460 * Max size of history is 100 by default.
461 */
462 public void navigateBack() {
463 try {
464 final Object webEngine = JavaFX.WebViewGetEngine.invoke(this._browser.webview);
465 try {
466 JavaFX.PlatformRunLater.invoke(null, new Runnable() {
467 @Override
468 public void run() {
469 try {
470 JavaFX.WebHistoryGo.invoke(JavaFX.WebEngineGetHistory.invoke(webEngine), -1);
471 FreeItems.getInstance().clear();
472 } catch (InvocationTargetException e) {
473 MessageBay.displayMessage("Start of History");
474 } catch (Exception e) {
475 e.printStackTrace();
476 }
477 }
478 });
479 } catch (Exception e) {
480 e.printStackTrace();
481 }
482 } catch (Exception e) {
483 e.printStackTrace();
484 }
485 }
486
487 /**
488 * Navigates JfxBrowser forward through history. If end of history reached the user is notified via the MessageBay.
489 * Max size of history is 100 by default.
490 */
491 public void navigateForward() {
492 try {
493 final Object webEngine = JavaFX.WebViewGetEngine.invoke(this._browser.webview);
494 try {
495 JavaFX.PlatformRunLater.invoke(null, new Runnable() {
496 @Override
497 public void run() {
498 try {
499 JavaFX.WebHistoryGo.invoke(JavaFX.WebEngineGetHistory.invoke(webEngine), 1);
500 FreeItems.getInstance().clear();
501 } catch (InvocationTargetException e) {
502 MessageBay.displayMessage("End of History");
503 } catch (Exception e) {
504 e.printStackTrace();
505 }
506 }
507 });
508 } catch (Exception e) {
509 e.printStackTrace();
510 }
511 } catch (Exception e) {
512 e.printStackTrace();
513 }
514 }
515
516 /**
517 * Refreshes webview by reloading the page.
518 */
519 public void refresh() {
520 try {
521 final Object webEngine = JavaFX.WebViewGetEngine.invoke(this._browser.webview);
522 try {
523 JavaFX.PlatformRunLater.invoke(null, new Runnable() {
524 @Override
525 public void run() {
526 try {
527 JavaFX.WebEngineReload.invoke(webEngine);
528 FreeItems.getInstance().clear();
529 MessageBay.displayMessage("Page Refreshing");
530 } catch (Exception e) {
531 e.printStackTrace();
532 }
533 }
534 });
535 } catch (Exception e) {
536 e.printStackTrace();
537 }
538 } catch (Exception e) {
539 e.printStackTrace();
540 }
541 }
542
543 /**
544 * Traverses DOM an turns elements into expeditee items.
545 */
546 public void getFrame() {
547 try {
548 WebParser.parsePage(JavaFX.WebViewGetEngine.invoke(this._browser.webview), DisplayIO.getCurrentFrame());
549 } catch (Exception e) {
550 e.printStackTrace();
551 }
552 }
553
554 /**
555 * Used to drop text items onto JfxBrowser widget. Does nothing if a text item is not attached to cursor. <br>
556 * "back" -> navigates back a page in browser's session history <br>
557 * "forward" -> navigates forward a page in browser's session history <br>
558 * "refresh" -> reloads current page <br>
559 * "getFrame" -> attempts to parse page into an expeditee frame <br>
560 * url -> all other text is assumed to be a url which browser attempts to navigate to
561 *
562 * @return Whether a JfxBrowser specific event is run.
563 *
564 */
565 @Override
566 public boolean ItemsLeftClickDropped() {
567 Text carried = null;
568 if ((carried = FreeItems.getTextAttachedToCursor()) == null) { // fails if no text is attached to cursor.
569 return false;
570 }
571
572 if(carried.getText().toLowerCase().equals(BACK)) {
573 navigateBack();
574 } else if(carried.getText().toLowerCase().equals(FORWARD)) {
575 navigateForward();
576 } else if(carried.getText().toLowerCase().equals(REFRESH)) {
577 refresh();
578 } else if(carried.getText().toLowerCase().equals(GETFRAME)) {
579 getFrame();
580 } else {
581 String text = carried.getText().trim();
582 this.navigate(text);
583 FreeItems.getInstance().clear();
584 }
585
586 return true;
587 }
588
589 /**
590 * Used to enable expeditee like text-widget interaction for middle mouse clicks. Does nothing if a text item is not attached to cursor.
591 * @return false if a text-widget interaction did not occur, true if a text-widget interaction did occur.
592 */
593 @Override
594 public boolean ItemsMiddleClickDropped() {
595 Text t = null;
596 if((t = FreeItems.getTextAttachedToCursor()) == null) { // fails if no text item is attached to the cursor.
597 return false;
598 }
599
600 Point p = new Point(FrameMouseActions.getX() - this.getX(), FrameMouseActions.getY() - this.getY());
601
602 if(!_browser.urlField.contains(p)) { // fails if not clicking on urlField
603 return false;
604 }
605
606 // Inserts text in text item into urlField at the position of the mouse.
607 String s = _browser.urlField.getText();
608 int index = _browser.urlField.viewToModel(new Point(p.x - _browser.urlField.getX(), p.y - _browser.urlField.getY()));
609 s = s.substring(0, index) + t.getText() + s.substring(index);
610 _browser.urlField.setText(s);
611
612 FreeItems.getInstance().clear(); // removed held text item - like normal expeditee middle click behaviour.
613
614 return true;
615 }
616
617 /**
618 * Used to enable expeditee like text-widget interaction for right mouse clicks. Does nothing if a text item is not attached to cursor.
619 * @return false if a text-widget interaction did not occur, true if a text-widget interaction did occur.
620 */
621 @Override
622 public boolean ItemsRightClickDropped() {
623 Text t = null;
624 if((t = FreeItems.getTextAttachedToCursor()) == null) { // fails if no text item is attached to the cursor.
625 return false;
626 }
627
628 Point p = new Point(FrameMouseActions.getX() - this.getX(), FrameMouseActions.getY() - this.getY());
629
630 if(!_browser.urlField.contains(p)) { // fails if not clicking on urlField
631 return false;
632 }
633
634 // Inserts text in text item into urlField at the position of the mouse.
635 String s = _browser.urlField.getText();
636 int index = _browser.urlField.viewToModel(new Point(p.x - _browser.urlField.getX(), p.y - _browser.urlField.getY()));
637 s = s.substring(0, index) + t.getText() + s.substring(index);
638 _browser.urlField.setText(s);
639
640 return true;
641 }
642
643 @Override
644 protected String[] getArgs() {
645 String[] r = null;
646 if (this._browser.webview != null) {
647 try {
648 r = new String[] { (String) JavaFX.WebEngineGetLocation.invoke(JavaFX.WebViewGetEngine.invoke(this._browser.webview)) };
649 } catch (Exception e) {
650 e.printStackTrace();
651 }
652 }
653 return r;
654 }
655}
Note: See TracBrowser for help on using the repository browser.