package com.kodcu.component;
import com.kodcu.controller.ApplicationController;
import com.kodcu.other.Current;
import com.kodcu.service.ThreadService;
import com.sun.javafx.scene.control.skin.ContextMenuContent;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.ObservableList;
import javafx.concurrent.Worker;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.CheckMenuItem;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Separator;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Window;
import netscape.javascript.JSObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import javax.annotation.PostConstruct;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicReference;
/**
* Created by usta on 15.06.2015.
*/
public abstract class ViewPanel extends AnchorPane {
private final Logger logger = LoggerFactory.getLogger(ViewPanel.class);
protected final ThreadService threadService;
protected final ApplicationController controller;
protected final Current current;
protected WebView webView;
protected final BooleanProperty stopScrolling = new SimpleBooleanProperty(false);
protected final BooleanProperty stopJumping = new SimpleBooleanProperty(false);
@Value("${application.generic.url}")
private String genericUrl;
protected ViewPanel(ThreadService threadService, ApplicationController controller, Current current) {
this.threadService = threadService;
this.controller = controller;
this.current = current;
}
@PostConstruct
public void afterViewInit() {
threadService.runActionLater(() -> {
this.getChildren().add(getWebView());
initializeMargins();
initializePreviewContextMenus();
});
}
public void enableScrollingAndJumping() {
stopScrolling.setValue(false);
stopJumping.setValue(false);
}
public void disableScrollingAndJumping() {
stopScrolling.setValue(true);
stopJumping.setValue(true);
}
private void initializePreviewContextMenus() {
CheckMenuItem stopRenderingItem = new CheckMenuItem("Stop rendering");
CheckMenuItem stopScrollingItem = new CheckMenuItem("Stop scrolling");
CheckMenuItem stopJumpingItem = new CheckMenuItem("Stop jumping");
stopRenderingItem.selectedProperty().addListener((observable, oldValue, newValue) -> {
controller.stopRenderingProperty().setValue(newValue);
});
stopScrollingItem.selectedProperty().addListener((observable, oldValue, newValue) -> {
stopScrolling.setValue(newValue);
});
stopJumpingItem.selectedProperty().addListener((observable, oldValue, newValue) -> {
stopJumping.setValue(newValue);
});
MenuItem refresh = MenuItemBuilt.item("Clear image cache").click(e -> {
webEngine().executeScript("clearImageCache()");
});
getWebView().setOnContextMenuRequested(event -> {
@SuppressWarnings("deprecation")
final Iterator<Window> windows = Window.impl_getWindows();
while (windows.hasNext()) {
final Window window = windows.next();
if (window instanceof ContextMenu) {
Optional<Node> nodeOptional = Optional.ofNullable(window)
.map(Window::getScene)
.map(Scene::getRoot)
.map(Parent::getChildrenUnmodifiable)
.filter((nodes) -> !nodes.isEmpty())
.map(e -> e.get(0))
.map(e -> e.lookup(".context-menu"));
if (nodeOptional.isPresent()) {
ObservableList<Node> childrenUnmodifiable = ((Parent) nodeOptional.get())
.getChildrenUnmodifiable();
ContextMenuContent cmc = (ContextMenuContent) childrenUnmodifiable.get(0);
// add new item:
cmc.getItemsContainer().getChildren().add(new Separator());
cmc.getItemsContainer().getChildren().add(cmc.new MenuItemContainer(stopRenderingItem));
cmc.getItemsContainer().getChildren().add(cmc.new MenuItemContainer(stopScrollingItem));
cmc.getItemsContainer().getChildren().add(cmc.new MenuItemContainer(stopJumpingItem));
}
}
}
});
}
protected void initializeMargins() {
AnchorPane.setBottomAnchor(this, 0D);
AnchorPane.setTopAnchor(this, 0D);
AnchorPane.setLeftAnchor(this, 0D);
AnchorPane.setRightAnchor(this, 0D);
VBox.setVgrow(this, Priority.ALWAYS);
AnchorPane.setBottomAnchor(getWebView(), 0D);
AnchorPane.setTopAnchor(getWebView(), 0D);
AnchorPane.setLeftAnchor(getWebView(), 0D);
AnchorPane.setRightAnchor(getWebView(), 0D);
VBox.setVgrow(getWebView(), Priority.ALWAYS);
}
public void browse() {
threadService.runActionLater(() -> {
final String documentURI = webEngine().getDocument().getDocumentURI();
controller.browseInDesktop(documentURI);
});
}
;
public void onscroll(Object pos, Object max) {
if (stopScrolling.get())
return;
threadService.runActionLater(() -> {
runScrolling(pos, max);
});
}
public abstract void runScroller(String text);
private void runScrolling(Object pos, Object max) {
Number position = (Number) pos; // current scroll position for editor
Number maximum = (Number) max; // max scroll position for editor
double currentY = (position.doubleValue() < 0) ? 0 : position.doubleValue();
double ratio = (currentY * 100) / maximum.doubleValue();
Integer browserMaxScroll = (Integer) webEngine().executeScript("document.documentElement.scrollHeight - document.documentElement.clientHeight;");
double browserScrollOffset = (Double.valueOf(browserMaxScroll) * ratio) / 100.0;
webEngine().executeScript(String.format("window.scrollTo(0, %f )", browserScrollOffset));
}
public WebEngine webEngine() {
return getWebView().getEngine();
}
public void load(String url) {
if (Objects.nonNull(url))
Platform.runLater(() -> {
webEngine().load(url);
});
else
logger.error("Url is not loaded. Reason: null reference");
}
public void hide() {
super.setVisible(false);
}
public void show() {
super.setVisible(true);
}
public String getLocation() {
return webEngine().getLocation();
}
public void setMember(String name, Object value) {
getWindow().setMember(name, value);
}
public Object call(String methodName, Object... args) {
return getWindow().call(methodName, args);
}
public Object getMember(String name) {
return getWindow().getMember(name);
}
public WebView getWebView() {
if (Objects.isNull(webView)) {
webView = threadService.supply(WebView::new);
}
return webView;
}
public void loadJs(String... jsPaths) {
threadService.runActionLater(() -> {
for (String jsPath : jsPaths) {
String format = String.format("var scriptEl = document.createElement('script');\n" +
"scriptEl.setAttribute('src','" + genericUrl + "');\n" +
"document.querySelector('body').appendChild(scriptEl);", controller.getPort(), jsPath);
webEngine().executeScript(format);
}
});
}
public void setOnSuccess(Runnable runnable) {
threadService.runActionLater(() -> {
getWindow().setMember("afx", controller);
Worker<Void> loadWorker = webEngine().getLoadWorker();
ReadOnlyObjectProperty<Worker.State> stateProperty = loadWorker.stateProperty();
stateProperty.addListener((observable, oldValue, newValue) -> {
if (newValue == Worker.State.SUCCEEDED) {
threadService.runActionLater(runnable);
}
});
});
}
public JSObject getWindow() {
return (JSObject) webEngine().executeScript("window");
}
public abstract void scrollByPosition(String text);
public abstract void scrollByLine(String text);
public void clearImageCache(Path imagePath) {
Optional.ofNullable(imagePath)
.map(Path::getFileName)
.map(Path::toString)
.ifPresent(this::clearImageCache);
}
;
public void clearImageCache(String imagePath) {
threadService.runActionLater(() -> {
webEngine().executeScript(String.format("clearImageCache(\"%s\")", imagePath));
});
}
}