package mj.ocraptor.javafx;
import ij.plugin.BrowserLauncher;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.awt.image.ImageProducer;
import java.awt.image.RenderedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.geometry.Point2D;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.SplitPane;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import javax.swing.GrayFilter;
import mj.ocraptor.configuration.Config;
import mj.ocraptor.configuration.Localization;
import mj.ocraptor.configuration.properties.ConfigBool;
import mj.ocraptor.configuration.properties.ConfigString;
import mj.ocraptor.javafx.controllers.SearchDialog;
import mj.ocraptor.tools.St;
public abstract class GUITemplate extends AnchorPane {
@FXML
protected ResourceBundle resources;
@FXML
protected URL location;
@FXML
protected Label title;
@FXML
protected ProgressIndicator progressIndicator;
@FXML
protected HBox progressBox;
@FXML
protected SplitPane pane;
@FXML
protected Button githubButton;
@FXML
protected Button resizeButton;
@FXML
protected Button helpButton;
@FXML
protected Label progressLabel;
//
protected GUIController g;
protected Config cfg;
public static final Integer DEFAULT_PROGRESS_ICON_SIZE = 53;
public static final Integer SEARCH_PROGRESS_ICON_SIZE = 75;
private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory
.getLogger(GUITemplate.class);
public void setProgress(double value, String text) {
final GUITemplate template = this;
Platform.runLater(new Runnable() {
@Override
public void run() {
progressIndicator.setPrefWidth(DEFAULT_PROGRESS_ICON_SIZE);
progressIndicator.setPrefHeight(DEFAULT_PROGRESS_ICON_SIZE);
if (value == -2) {
progressIndicator.setProgress(-1);
progressLabel.setText("");
progressIndicator.setVisible(false);
progressBox.setVisible(false);
} else {
progressIndicator.setProgress(value);
progressBox.setVisible(true);
progressIndicator.setVisible(true);
if (text != null && progressLabel != null) {
progressLabel.setText(text);
}
if (value != -1 && template instanceof SearchDialog) {
progressIndicator.setPrefWidth(SEARCH_PROGRESS_ICON_SIZE);
progressIndicator.setPrefHeight(SEARCH_PROGRESS_ICON_SIZE);
}
}
}
});
}
/**
*
*/
@FXML
public void initialize() {
this.cfg = Config.inst();
this.setProgress(-1.0, "");
AnchorPane mainpane = (AnchorPane) this.pane.getParent();
Theme theme = Theme.getByName(this.cfg.getProp(ConfigString.THEME));
mainpane.getStylesheets().clear();
mainpane.getStylesheets().add(this.getClass().getResource(theme.toString()).toString());
this.g = GUIController.instance();
this.initTemplateLabels();
this.initLabels();
this.initVisibility();
this.initListeners();
}
@FXML
protected void helpButtonClicked(ActionEvent event) {
this.openHelpInBrowser(Config.getHelpFilePath(Localization.instance().getLocale()), this.getClass()
.getSimpleName());
}
/**
*
*
*/
public void initTemplateLabels() {
// if (this.progressBox != null) {
// this.progressBox.setVisible(false);
// }
// if (this.progressIndicator != null) {
// this.progressIndicator.setVisible(false);
// }
}
/**
*
*
*/
public void close() {
Stage stage = (Stage) this.pane.getScene().getWindow();
stage.close();
}
private static final Integer IN_TRANSITION_TIME = 150;
private static final Double MIN_TRANSITION_VALUE = 0.0;
private static final Double MAX_TRANSITION_VALUE = 1.0;
private static final Integer OUT_TRANSITION_TIME = 150;
private static final Integer OUT_TRANSITION_TIME_FOR_BROWSER = 1500;
/**
*
*
* @param delay
*/
protected void inTransitionAsWorker(int delay) {
this.executeWorker(inTransitionWorker(delay));
Platform.runLater(new Runnable() {
@Override
public void run() {
pane.getScene().setCursor(Cursor.DEFAULT);
}
});
}
/**
*
*
*/
public void initUserComponents() {
this.initCustomComponents();
this.initEventHandlers();
if (this.helpButton != null) {
this.addTooltip(this.helpButton, this.g.getText("HELP_TOOLTIP"), -100, -60);
}
if (this.githubButton != null) {
this.addTooltip(this.githubButton, this.g.getText("TEMPLATE.OPEN_SOURCE_INFO"), 65, 0);
}
if (this.resizeButton != null) {
this.addTooltip(this.resizeButton, this.g.getText("TEMPLATE.REFRESH_WINDOW"), -205, 0);
}
this.pane.getScene().addEventHandler(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent event) {
if (event.getCode() == KeyCode.F1) {
openHelpInBrowser(Config.getHelpFilePath(Localization.instance().getLocale()), "");
}
}
});
this.inTransition();
}
/**
*
*
* @return
*/
protected Task<Object> showDelayedWorker() {
return new Task<Object>() {
@Override
protected Object call() throws Exception {
Stage stage = (Stage) pane.getScene().getWindow();
Platform.runLater(new Runnable() {
@Override
public void run() {
if (stage != null) {
stage.show();
}
}
});
return true;
}
};
}
/**
*
*
* @return
*/
private Task<Object> inTransitionWorker(int delay) {
return new Task<Object>() {
@Override
protected Object call() throws Exception {
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
e.printStackTrace();
}
inTransition();
return true;
}
};
}
/**
*
*
* @return
*/
private Task<Object> outTransitionWorker() {
return new Task<Object>() {
@Override
protected Object call() throws Exception {
outTransition();
return true;
}
};
}
/**
*
*
* @param node
*/
protected void inTransition() {
// if (this.cfg.getProp(this.cfg.getDefaultConfigFilePath(),
// ConfigBool.ENABLE_ANIMATIONS)) {
// FadeTransition fadeTransition = new
// FadeTransition(Duration.millis(IN_TRANSITION_TIME), pane);
// fadeTransition.setFromValue(MIN_TRANSITION_VALUE);
// fadeTransition.setToValue(MAX_TRANSITION_VALUE);
// fadeTransition.play();
// }
Platform.runLater(new Runnable() {
@Override
public void run() {
if (progressIndicator != null) {
progressIndicator.setVisible(false);
}
if (progressBox != null) {
progressBox.setVisible(false);
}
pane.setOpacity(1.0);
pane.getScene().setCursor(Cursor.DEFAULT);
}
});
}
/**
*
*
*/
protected void outTransitionAsWorker() {
this.executeWorker(outTransitionWorker());
}
/**
*
*
* @param node
*/
protected void outTransition() {
Platform.runLater(new Runnable() {
@Override
public void run() {
pane.requestFocus();
pane.getScene().setCursor(Cursor.WAIT);
}
});
if (!this.progressBox.isVisible()) {
Platform.runLater(new Runnable() {
@Override
public void run() {
if (progressBox != null) {
progressBox.setVisible(true);
}
if (progressIndicator != null) {
progressIndicator.setVisible(true);
}
pane.setOpacity(0.1);
}
});
// if (this.pane != null) {
// FadeTransition fadeTransition = new
// FadeTransition(Duration.millis(OUT_TRANSITION_TIME),
// this.pane);
// fadeTransition.setFromValue(MAX_TRANSITION_VALUE);
// fadeTransition.setToValue(MIN_TRANSITION_VALUE);
// fadeTransition.play();
// }
}
}
/**
*
*
* @param template
*/
protected void gotoPage(final String template, final Double width, final Double height) {
this.outTransition();
this.executeWorker(pageWorker(template, width, height));
}
/**
*
*
* @param worker
*/
protected void executeWorker(Task<Object> worker) {
if (this.progressIndicator != null) {
// progressIndicator.progressProperty().bind(worker.progressProperty());
}
Thread th = new Thread(worker);
th.setDaemon(true);
th.start();
}
@FXML
protected void githubButtonClicked(ActionEvent event) {
outTransition();
Platform.runLater(new Runnable() {
@Override
public void run() {
try {
BrowserLauncher.openURL(cfg.getProp(ConfigString.GITHUB_URL));
inTransitionAsWorker(OUT_TRANSITION_TIME_FOR_BROWSER);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
private Double currentXPos = null;
private Double currentYPos = null;
@FXML
protected void resizeButtonClicked(ActionEvent event) {
this.resizeMainPane();
}
/**
*
*
* @param theme
*/
protected void changeThemeTo(Theme theme) {
AnchorPane mainpane = (AnchorPane) this.pane.getParent();
Stage stage = g.getPrimaryStage();
stage.hide();
mainpane.getStylesheets().clear();
mainpane.getStylesheets().add(this.getClass().getResource(theme.toString()).toString());
stage.show();
}
/**
*
*
*/
protected void resizeMainPane() {
AnchorPane mainpane = (AnchorPane) this.pane.getParent();
Stage stage = g.getPrimaryStage();
double x = stage.getX();
double y = stage.getY();
stage.hide();
stage.setMaximized(false);
stage.setFullScreen(false);
if (!Double.isNaN(x) && !Double.isNaN(y)) {
if (!stage.isMaximized() && !stage.isFullScreen() && (mainpane.getWidth() < 810)
&& (mainpane.getHeight() < 700)) {
stage.setX(x);
stage.setY(y);
} else {
if (this.currentXPos != null && this.currentYPos != null) {
stage.setX(this.currentXPos);
stage.setY(this.currentYPos);
}
}
this.currentXPos = this.currentXPos == null ? x : this.currentXPos;
this.currentYPos = this.currentYPos == null ? y : this.currentYPos;
}
mainpane.setPrefWidth(this.getWindowWidth());
mainpane.setPrefHeight(this.getWindowHeight());
stage.show();
}
/**
*
*
*/
protected void openHelpInBrowser(String helpFilePath, String anchor) {
// outTransition();
// inTransitionAsWorker(OUT_TRANSITION_TIME_FOR_BROWSER);
Platform.runLater(new Runnable() {
@Override
public void run() {
try {
File helpFile = new File(helpFilePath);
if (helpFile.exists()) {
g.showHelp(helpFile.toURI().toString(), (anchor == null ? "" : "#" + anchor));
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
*
*
* @return
*/
private Task<Object> pageWorker(final String template, final Double width, final Double height) {
return new Task<Object>() {
@Override
protected Object call() throws Exception {
try {
Thread.sleep(150);
} catch (InterruptedException e) {
e.printStackTrace();
}
Platform.runLater(new Runnable() {
@Override
public void run() {
g.gotoPage(template, width, height);
}
});
return true;
}
};
}
/**
*
*
* @param button
* @param icon
* @param translateX
*/
protected void addImageIcon(ToggleButton button, Icon icon, int translateX) {
ImageView image = new ImageView(this.getClass().getResource(icon.getFileName()).toString());
image.setTranslateX(translateX);
image.setFitHeight(14);
image.setFitWidth(14);
button.setGraphic(image);
}
/**
*
*
* @param button
* @param icon
*/
protected void addImageIcon(Button button, Icon icon, int translateX) {
ImageView image = new ImageView(this.getClass().getResource(icon.getFileName()).toString());
image.setTranslateX(translateX);
image.setFitHeight(14);
image.setFitWidth(14);
button.setGraphic(image);
}
/**
*
*
* @return
*/
protected Line getDivider(String styleClass, float strokeWidth, int rightDistance,
Double... dashArrayElements) {
Line redLine = new Line(0, 0, pane.getWidth() - rightDistance, 0);
redLine.setManaged(true);
redLine.setSmooth(true);
redLine.setStrokeWidth(strokeWidth);
redLine.getStrokeDashArray().addAll(dashArrayElements);
redLine.getStyleClass().add(styleClass);
pane.widthProperty().addListener(new ChangeListener<Number>() {
@Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue,
Number newValue) {
redLine.setEndX((double) newValue - rightDistance);
}
});
return redLine;
}
/**
*
*
* @param image
* @return
*
* @throws IOException
*/
private javafx.scene.image.Image createImage(java.awt.Image image) throws IOException {
if (!(image instanceof RenderedImage)) {
BufferedImage bufferedImage = new BufferedImage(image.getWidth(null), image.getHeight(null),
BufferedImage.TYPE_INT_ARGB);
Graphics g = bufferedImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
image = bufferedImage;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageIO.write((RenderedImage) image, "png", out);
out.flush();
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
return new javafx.scene.image.Image(in);
}
/**
*
*
* @param icon
* @return
*/
protected javafx.scene.image.Image getGreyscaleIcon(Icon icon) {
javafx.scene.image.Image fxImage = null;
try {
BufferedImage bf = ImageIO.read(this.getClass().getResource(icon.getFileName()));
ImageFilter filter = new GrayFilter(true, 70);
ImageProducer producer = new FilteredImageSource(bf.getSource(), filter);
Image mage = Toolkit.getDefaultToolkit().createImage(producer);
fxImage = createImage(mage);
} catch (Exception e) {
// TODO: logging
e.printStackTrace();
}
return fxImage;
}
private static final String EXECUTION_ERROR = "Error executing your command";
/**
*
*
* @param filePath
* @return
*/
protected Task<Object> openFileWorker(String filePath) {
return openFileWorker(filePath, false, 0);
}
final static double TOOLTIP_WIDTH = 200;
// *INDENT-OFF*
// TODO: style, can't be read directly from css-file
final static String TOOLTIP_STYLE =
"-fx-background-color: #123462;" +
"-fx-background-insets: 0;" +
"-fx-background-radius: 0 0 0 0;" +
"-fx-font-size: 13px;" +
"-fx-padding: 0.333333em 0.666667em 0.333333em 0.666667em;" +
"-fx-effect: dropshadow( one-pass-box , #808080 , 5, 0.0 , 0 , 0 );" +
"-fx-text-fill: white;";
// *INDENT-ON*
/**
*
*
* @param node
* @param text
* @param xOffset
* left offset
* @param yOffset
* top offset
*/
protected void addTooltip(Node node, String text, double xOffset, double yOffset) {
try {
final Tooltip tooltip = new Tooltip(text);
tooltip.setStyle(TOOLTIP_STYLE);
tooltip.setMaxWidth(TOOLTIP_WIDTH);
tooltip.setWrapText(true);
node.setOnMouseEntered(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
Point2D point = node.localToScene(0.0, 0.0);
double posX = point.getX() + node.getScene().getX() + node.getScene().getWindow().getX()
+ xOffset;
double posY = point.getY() + node.getScene().getY() + node.getScene().getWindow().getY()
+ yOffset;
tooltip.show(node, posX, posY);
}
});
EventHandler<Event> hideTooltip = new EventHandler<Event>() {
@Override
public void handle(Event event) {
tooltip.hide();
}
};
node.setOnMousePressed(hideTooltip);
node.setOnMouseExited(hideTooltip);
this.pane.getScene().addEventHandler(KeyEvent.KEY_PRESSED, hideTooltip);
} catch (Exception e) {
}
}
/**
*
*
* @return
*/
protected Task<Object> openFileWorker(String filePath, boolean openParentDirectory, int page) {
return new Task<Object>() {
@Override
protected Object call() throws Exception {
String[] errOutput = g.getParentController().getDb().openFile(filePath, page,
openParentDirectory, 60);
String error = errOutput[0];
String command = errOutput[1];
Platform.runLater(new Runnable() {
@Override
public void run() {
List<Node> dialogMessage = null;
if (command == null || command.trim().isEmpty()) {
dialogMessage = new ArrayList<Node>();
dialogMessage.add(new Text("Your command is empty"));
g.showMessage(450, 150, 5, EXECUTION_ERROR, Color.DARKRED, false, dialogMessage
.toArray(new Node[dialogMessage.size()]));
} else if (error != null && !error.trim().isEmpty()) {
dialogMessage = new ArrayList<Node>();
dialogMessage.add(new Text("Your command is not valid:\n"));
Text commandString = new Text(command + "\n");
commandString.setFill(Color.DARKRED);
dialogMessage.add(commandString);
dialogMessage.add(new Text("Stderr-message:\n"));
Text errorString = new Text(error);
errorString.setFill(Color.DARKRED);
dialogMessage.add(errorString);
if (cfg.getProp(//
ConfigBool.ENABLE_USER_COMMAND_STDERR)) {
g.showMessage(500, 200, 5, EXECUTION_ERROR, Color.DARKRED, false, dialogMessage
.toArray(new Node[dialogMessage.size()]));
}
}
}
});
return true;
}
};
}
/**
*
*
* @return
*/
public Task<Object> propValidatingWorker() {
return new Task<Object>() {
@Override
protected Object call() throws Exception {
String error = "";
final String loadedConfigFile = cfg.getConfigUserFilePath();
// ------------------------------------------------ //
final String invalidConfigError = cfg.invalidConfigVersion(loadedConfigFile);
if (invalidConfigError != null) {
for (int i = 0; i < 50; i++) {
if (GUIController.instance().getExceptionQueue() != null
|| Thread.currentThread().isInterrupted()) {
break;
}
Thread.sleep(100);
}
error += invalidConfigError;
error += "\nConfig: " + loadedConfigFile;
}
// ------------------------------------------------ //
cfg.validateProperties(loadedConfigFile);
final Map<String, LinkedHashSet<String>> notFoundProperties = cfg.getNotFoundProperties();
final String configFile = loadedConfigFile;
if (notFoundProperties.containsKey(configFile)) {
final LinkedHashSet<String> properties = notFoundProperties.get(configFile);
String missingProperties = "";
for (String missingProperty : properties) {
missingProperties += "\n -> " + missingProperty;
}
if (!missingProperties.isEmpty()) {
missingProperties = St.removeLastLineBreak(missingProperties);
if (!error.isEmpty()) {
error += "\n\n";
}
error += "Missing properties in config file:\n" + configFile + missingProperties;
}
}
// ------------------------------------------------ //
if (!error.isEmpty()) {
LOGGER.error(error);
}
return true;
}
};
}
public abstract void initCustomComponents();
protected abstract void initVisibility();
protected abstract void initLabels();
protected abstract double getWindowWidth();
protected abstract double getWindowHeight();
protected abstract void initListeners();
protected abstract void initEventHandlers();
protected abstract void asserts();
}