package bluej.editor.stride;

import bluej.Config;
import bluej.pkgmgr.Project;
import bluej.pkgmgr.target.EditableTarget;
import bluej.testmgr.TestDisplayFrame;
import bluej.utility.Utility;
import bluej.utility.javafx.JavaFXUtil;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableStringValue;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.web.WebView;
import javafx.stage.Popup;
import javafx.stage.Window;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.events.EventTarget;
import threadchecker.OnThread;
import threadchecker.Tag;

import javax.swing.*;
import java.util.Collections;
import java.util.List;
import java.util.WeakHashMap;


| A tab in the FXTabbedEditor which just contains a WebView. | @OnThread(Tag.FX) public class WebTab extends FXTab{ private final WebView browser; private final boolean tutorial; private FXTabbedEditor parent; private final TabMenuManager menuManager; private final WeakHashMap<Project, OverlayInfo> tutorialOverlays = new WeakHashMap<>();
| Constructs a WebTab with a WebView in it | @param url The initial URL to display in the WebView. | @OnThread(Tag.FXPlatform) public WebTab(String url, boolean enableTutorial) { super(false); browser = new WebView(); this.tutorial = enableTutorial; if (enableTutorial) { setClosable(false); JavaFXUtil.addChangeListenerPlatform(this.browser.getEngine().documentProperty(), this::tutorialDocumentMangle); } browser.getEngine().load(url); browser.getEngine().setCreatePopupHandler(p -> { WebTab newTab = new WebTab(null, enableTutorial); parent.addTab(newTab, true, true); return newTab.browser.getEngine(); }); setGraphic(getWebIcon()); setContent(browser); textProperty().bind(browser.getEngine().titleProperty()); menuManager = new TabMenuManager(this) { final List<Menu> menus = Collections.singletonList(JavaFXUtil.makeMenu(Config.getString("frame.webmenu.title"), mainMoveMenu, JavaFXUtil.makeMenuItem(Config.getString("frame.webmenu.open.external"), () -> { String location = browser.getEngine().getLocation(); SwingUtilities.invokeLater(() -> Utility.openWebBrowser(location)); }, new KeyCodeCombination(KeyCode.O, KeyCombination.SHORTCUT_DOWN)), JavaFXUtil.makeMenuItem(Config.getString("frame.classmenu.close"), () -> tab.getParent().close(tab), new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN)) )); @Override @OnThread(Tag.FXPlatform) List<Menu> getMenus() { updateMoveMenus(); return menus; } }; }
| Gets an icon to display next to web view tabs | private Node getWebIcon() { Label j = new Label("W"); JavaFXUtil.addStyleClass(j, "icon-label"); return j; } @Override void focusWhenShown() { } @Override @OnThread(Tag.FXPlatform) List<Menu> getMenus() { return menuManager.getMenus(); } @Override @OnThread(Tag.FXPlatform) String getWebAddress() { return browser.getEngine().getLocation(); } @Override void initialiseFX() { } @Override p.public void setParent(FXTabbedEditor parent, boolean partOfMove) { this.parent = parent; } @Override FXTabbedEditor getParent() { return parent; } @Override ObservableStringValue windowTitleProperty() { return textProperty(); } @Override public void notifySelected() { } @Override public void notifyUnselected() { } @Override public boolean isTutorial() { return tutorial; }
| Modifies the HTML document to turn tutorial-specific links into actions which highlight or open items. | @OnThread(Tag.FXPlatform) private void tutorialDocumentMangle(Document doc) { if (doc != null) { NodeList anchors = doc.getElementsByTagName("a"); for (int i = 0; i < anchors.getLength(); i++) { org.w3c.dom.Node anchorItem = anchors.item(i); org.w3c.dom.Node anchorHref = anchorItem.getAttributes().getNamedItem("href"); if (anchorHref != null && anchorHref.getNodeValue() != null) { if (anchorHref.getNodeValue().startsWith("class:")) { ((EventTarget) anchorItem).addEventListener("click", e -> { ((EditableTarget) parent.getProject().getTarget(anchorHref.getNodeValue(). substring("class:".length()).trim())).getEditor(). setEditorVisible(true, false); e.stopPropagation(); }, true); } else if (anchorHref.getNodeValue().startsWith("guicss:")) { ((EventTarget) anchorItem).addEventListener("click", e -> { OverlayInfo prevOverlay = tutorialOverlays.get(parent.getProject()); if (prevOverlay != null) { prevOverlay.hide(); } String nodeCSS = anchorHref.getNodeValue().substring("guicss:".length()); final Window targetWindow; if (nodeCSS.startsWith("Terminal")) { targetWindow = parent.getProject().getTerminal().getWindow(); nodeCSS = nodeCSS.substring("Terminal".length()); } else if (nodeCSS.startsWith("Editor")) { targetWindow = parent.getProject().getDefaultFXTabbedEditor().getWindow(); nodeCSS = nodeCSS.substring("Editor".length()); } else if (nodeCSS.startsWith("TestResults")) { targetWindow = TestDisplayFrame.getTestDisplay().getWindow(); nodeCSS = nodeCSS.substring("TestResults".length()); } else { targetWindow = parent.getProject().getPackage("").getEditor().getFXWindow(); } Node n = targetWindow.getScene().lookup(nodeCSS); if (n != null) { Bounds screenBounds = n.localToScreen(n.getBoundsInLocal()); Rectangle[] rects = new Rectangle[] { new Rectangle(screenBounds.getWidth() + 20, 5), new Rectangle(5, screenBounds.getHeight() + 20), new Rectangle(screenBounds.getWidth() + 20, 5), new Rectangle(5, screenBounds.getHeight() + 20) }; Popup[] overlays = new Popup[4]; for (int j = 0; j < 4; j++) { rects[j].setFill(Color.RED); overlays[j] = new Popup(); overlays[j].getContent().setAll(rects[j]); } overlays[0].show(targetWindow, screenBounds.getMinX() - 10, screenBounds.getMinY() - 10); overlays[1].show(targetWindow, screenBounds.getMaxX() + 5, screenBounds.getMinY() - 10); overlays[2].show(targetWindow, screenBounds.getMinX() - 10, screenBounds.getMaxY() + 5); overlays[3].show(targetWindow, screenBounds.getMinX() - 10, screenBounds.getMinY() - 10); OverlayInfo overlayInfo = new OverlayInfo(targetWindow, n, overlays); tutorialOverlays.put(parent.getProject(), overlayInfo); Utility.bringToFrontFX(targetWindow); targetWindow.requestFocus(); } e.stopPropagation(); }, true); } } } } }
| A class which keeps track of the red-line overlays, and on certain actions | (clicking target, window losing focus, window changing size/position) | hides the overlays. | private static class OverlayInfo implements EventHandler<MouseEvent>, ChangeListener<Object> { private final Popup[] overlays; private final Node target; private final Window targetWindow; public OverlayInfo(Window targetWindow, Node target, Popup[] overlays) { this.targetWindow = targetWindow; this.target = target; this.overlays = overlays; target.addEventFilter(MouseEvent.MOUSE_PRESSED, this); targetWindow.focusedProperty().addListener(this); targetWindow.xProperty().addListener(this); targetWindow.yProperty().addListener(this); targetWindow.widthProperty().addListener(this); targetWindow.heightProperty().addListener(this); }
| Implement EventHandler interface | @Override @OnThread(value = Tag.FXPlatform, ignoreParent = true) public void handle(MouseEvent event) { hide(); }
| Hides the red line popups, and cleans up listeners. | @OnThread(Tag.FXPlatform) public void hide() { for (Popup overlay : overlays) { overlay.hide(); } target.removeEventFilter(MouseEvent.MOUSE_PRESSED, this); targetWindow.focusedProperty().removeListener(this); targetWindow.xProperty().removeListener(this); targetWindow.yProperty().removeListener(this); targetWindow.widthProperty().removeListener(this); targetWindow.heightProperty().removeListener(this); }
| Implement ChangeListener interface: | @Override @OnThread(value = Tag.FXPlatform, ignoreParent = true) public void changed(ObservableValue<? extends Object> observable, Object oldValue, Object newValue) { if (observable == targetWindow.focusedProperty() && ((Boolean)newValue) == false) { hide(); } if (observable == targetWindow.xProperty() || observable == targetWindow.yProperty() || observable == targetWindow.widthProperty() || observable == targetWindow.heightProperty()) { hide(); } } } }
top, use, map, class WebTab

.   WebTab
.   getWebIcon
.   focusWhenShown
.   initialiseFX
.   setParent
.   notifySelected
.   notifyUnselected
.   isTutorial
.   tutorialDocumentMangle
.   OverlayInfo
.   handle
.   hide
.   changed




395 neLoCode + 11 LoComm