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

Last change on this file since 631 was 631, checked in by csl14, 11 years ago

fixed javafx browser copy text bug

  • Property svn:executable set to *
File size: 21.5 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 // Do nothing if no text is selected
357 if(!selection.equals("")) {
358 // Copy text and attach to cursor
359 Text t = new Text(selection);
360 t.setParent(DisplayIO.getCurrentFrame());
361 t.setXY(FrameMouseActions.getX(), FrameMouseActions.getY());
362 FrameMouseActions.pickup(t);
363 } else { System.out.println("no text selected"); }
364 }
365 }
366
367 return null;
368 }
369 }));
370
371 // Disables JavaFX webview's right click menu
372 JavaFX.WebViewSetContextMenuEnabled.invoke(this.webview, false);
373
374 // webEngine.getLoadWorker().stateProperty().addListener(
375 // new ChangeListener<State>() {
376 // public void changed(ObservableValue ov, State oldState, State newState) {
377 // if (newState == State.SUCCEEDED) {
378 // stage.setTitle(webEngine.getLocation());
379 // }
380 // }
381 // });
382
383 JavaFX.WebEngineLoad.invoke(JavaFX.WebViewGetEngine.invoke(this.webview), url);
384 } catch (Exception e) {
385 e.printStackTrace();
386 }
387 }
388
389 @Override
390 public void componentHidden(ComponentEvent e) {
391 }
392
393 @Override
394 public void componentMoved(ComponentEvent e) {
395 }
396
397 @Override
398 public void componentResized(ComponentEvent e) {
399 // BorderLayout on the JPanel removes the need for explicitly resizing anything here
400 }
401
402 @Override
403 public void componentShown(ComponentEvent e) {
404 }
405 }
406
407 public JfxBrowser(Text source, String[] args) {
408 // Initial page is either the page stored in the arguments (if there is one stored) or the homepage
409 super(source, new WebBrowserPanel((args != null && args.length > 0) ? args[0] : NetworkSettings.homePage), -1, 500, -1, -1, 300, -1);
410 _browser = (WebBrowserPanel) _swingComponent;
411 _browser.owner = this;
412 }
413
414 public void navigate(String url) {
415 final String actualURL;
416 String urlLower = url.toLowerCase();
417 // check if protocol is missing
418 if (!(urlLower.startsWith("http://") || url.startsWith("https://") || urlLower.startsWith("ftp://") || urlLower.startsWith("file://"))) {
419 // check if it's a search
420 int firstSpace = url.indexOf(" ");
421 int firstDot = url.indexOf(".");
422 int firstSlash = url.indexOf('/');
423 int firstQuestion = url.indexOf('?');
424 int firstSQ;
425 if(firstSlash == -1) {
426 firstSQ = firstQuestion;
427 } else if(firstQuestion == -1) {
428 firstSQ = firstSlash;
429 } else {
430 firstSQ = -1;
431 }
432 if(firstDot <= 0 || // no '.' or starts with '.' -> search
433 (firstSpace != -1 && firstSpace < firstDot + 1) || // ' ' before '.' -> search
434 (firstSpace != -1 && firstSpace < firstSQ)) { // no '/' or '?' -> search
435 // make it a search
436 actualURL = NetworkSettings.searchEngine + url;
437 } else {
438 // add the missing protocol
439 actualURL = "http://" + url;
440 }
441 } else {
442 actualURL = url;
443 }
444 System.out.println(actualURL);
445 try {
446 JavaFX.PlatformRunLater.invoke(null, new Runnable() {
447 @Override
448 public void run() {
449 try {
450 JavaFX.WebEngineLoad.invoke(JavaFX.WebViewGetEngine.invoke(JfxBrowser.this._browser.webview), actualURL);
451 } catch (Exception e) {
452 e.printStackTrace();
453 }
454 }
455 });
456 } catch (Exception e) {
457 e.printStackTrace();
458 }
459 }
460
461 /**
462 * Navigates JfxBrowser back through history. If end of history reached the user is notified via the MessageBay.
463 * Max size of history is 100 by default.
464 */
465 public void navigateBack() {
466 try {
467 final Object webEngine = JavaFX.WebViewGetEngine.invoke(this._browser.webview);
468 try {
469 JavaFX.PlatformRunLater.invoke(null, new Runnable() {
470 @Override
471 public void run() {
472 try {
473 JavaFX.WebHistoryGo.invoke(JavaFX.WebEngineGetHistory.invoke(webEngine), -1);
474 FreeItems.getInstance().clear();
475 } catch (InvocationTargetException e) {
476 MessageBay.displayMessage("Start of History");
477 } catch (Exception e) {
478 e.printStackTrace();
479 }
480 }
481 });
482 } catch (Exception e) {
483 e.printStackTrace();
484 }
485 } catch (Exception e) {
486 e.printStackTrace();
487 }
488 }
489
490 /**
491 * Navigates JfxBrowser forward through history. If end of history reached the user is notified via the MessageBay.
492 * Max size of history is 100 by default.
493 */
494 public void navigateForward() {
495 try {
496 final Object webEngine = JavaFX.WebViewGetEngine.invoke(this._browser.webview);
497 try {
498 JavaFX.PlatformRunLater.invoke(null, new Runnable() {
499 @Override
500 public void run() {
501 try {
502 JavaFX.WebHistoryGo.invoke(JavaFX.WebEngineGetHistory.invoke(webEngine), 1);
503 FreeItems.getInstance().clear();
504 } catch (InvocationTargetException e) {
505 MessageBay.displayMessage("End of History");
506 } catch (Exception e) {
507 e.printStackTrace();
508 }
509 }
510 });
511 } catch (Exception e) {
512 e.printStackTrace();
513 }
514 } catch (Exception e) {
515 e.printStackTrace();
516 }
517 }
518
519 /**
520 * Refreshes webview by reloading the page.
521 */
522 public void refresh() {
523 try {
524 final Object webEngine = JavaFX.WebViewGetEngine.invoke(this._browser.webview);
525 try {
526 JavaFX.PlatformRunLater.invoke(null, new Runnable() {
527 @Override
528 public void run() {
529 try {
530 JavaFX.WebEngineReload.invoke(webEngine);
531 FreeItems.getInstance().clear();
532 MessageBay.displayMessage("Page Refreshing");
533 } catch (Exception e) {
534 e.printStackTrace();
535 }
536 }
537 });
538 } catch (Exception e) {
539 e.printStackTrace();
540 }
541 } catch (Exception e) {
542 e.printStackTrace();
543 }
544 }
545
546 /**
547 * Traverses DOM an turns elements into expeditee items.
548 */
549 public void getFrame() {
550 try {
551 WebParser.parsePage(JavaFX.WebViewGetEngine.invoke(this._browser.webview), DisplayIO.getCurrentFrame());
552 } catch (Exception e) {
553 e.printStackTrace();
554 }
555 }
556
557 /**
558 * Used to drop text items onto JfxBrowser widget. Does nothing if a text item is not attached to cursor. <br>
559 * "back" -> navigates back a page in browser's session history <br>
560 * "forward" -> navigates forward a page in browser's session history <br>
561 * "refresh" -> reloads current page <br>
562 * "getFrame" -> attempts to parse page into an expeditee frame <br>
563 * url -> all other text is assumed to be a url which browser attempts to navigate to
564 *
565 * @return Whether a JfxBrowser specific event is run.
566 *
567 */
568 @Override
569 public boolean ItemsLeftClickDropped() {
570 Text carried = null;
571 if ((carried = FreeItems.getTextAttachedToCursor()) == null) { // fails if no text is attached to cursor.
572 return false;
573 }
574
575 if(carried.getText().toLowerCase().equals(BACK)) {
576 navigateBack();
577 } else if(carried.getText().toLowerCase().equals(FORWARD)) {
578 navigateForward();
579 } else if(carried.getText().toLowerCase().equals(REFRESH)) {
580 refresh();
581 } else if(carried.getText().toLowerCase().equals(GETFRAME)) {
582 getFrame();
583 } else {
584 String text = carried.getText().trim();
585 this.navigate(text);
586 FreeItems.getInstance().clear();
587 }
588
589 return true;
590 }
591
592 /**
593 * Used to enable expeditee like text-widget interaction for middle mouse clicks. Does nothing if a text item is not attached to cursor.
594 * @return false if a text-widget interaction did not occur, true if a text-widget interaction did occur.
595 */
596 @Override
597 public boolean ItemsMiddleClickDropped() {
598 Text t = null;
599 if((t = FreeItems.getTextAttachedToCursor()) == null) { // fails if no text item is attached to the cursor.
600 return false;
601 }
602
603 Point p = new Point(FrameMouseActions.getX() - this.getX(), FrameMouseActions.getY() - this.getY());
604
605 if(!_browser.urlField.contains(p)) { // fails if not clicking on urlField
606 return false;
607 }
608
609 // Inserts text in text item into urlField at the position of the mouse.
610 String s = _browser.urlField.getText();
611 int index = _browser.urlField.viewToModel(new Point(p.x - _browser.urlField.getX(), p.y - _browser.urlField.getY()));
612 s = s.substring(0, index) + t.getText() + s.substring(index);
613 _browser.urlField.setText(s);
614
615 FreeItems.getInstance().clear(); // removed held text item - like normal expeditee middle click behaviour.
616
617 return true;
618 }
619
620 /**
621 * Used to enable expeditee like text-widget interaction for right mouse clicks. Does nothing if a text item is not attached to cursor.
622 * @return false if a text-widget interaction did not occur, true if a text-widget interaction did occur.
623 */
624 @Override
625 public boolean ItemsRightClickDropped() {
626 Text t = null;
627 if((t = FreeItems.getTextAttachedToCursor()) == null) { // fails if no text item is attached to the cursor.
628 return false;
629 }
630
631 Point p = new Point(FrameMouseActions.getX() - this.getX(), FrameMouseActions.getY() - this.getY());
632
633 if(!_browser.urlField.contains(p)) { // fails if not clicking on urlField
634 return false;
635 }
636
637 // Inserts text in text item into urlField at the position of the mouse.
638 String s = _browser.urlField.getText();
639 int index = _browser.urlField.viewToModel(new Point(p.x - _browser.urlField.getX(), p.y - _browser.urlField.getY()));
640 s = s.substring(0, index) + t.getText() + s.substring(index);
641 _browser.urlField.setText(s);
642
643 return true;
644 }
645
646 @Override
647 protected String[] getArgs() {
648 String[] r = null;
649 if (this._browser.webview != null) {
650 try {
651 r = new String[] { (String) JavaFX.WebEngineGetLocation.invoke(JavaFX.WebViewGetEngine.invoke(this._browser.webview)) };
652 } catch (Exception e) {
653 e.printStackTrace();
654 }
655 }
656 return r;
657 }
658}
Note: See TracBrowser for help on using the repository browser.