/**
* erlyberly, erlang trace debugger
* Copyright (C) 2016 Andy Till
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package erlyberly;
import java.io.IOException;
import erlyberly.format.ErlangFormatter;
import erlyberly.format.ElixirFormatter;
import erlyberly.format.LFEFormatter;
import erlyberly.format.TermFormatter;
import erlyberly.node.NodeAPI;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.SplitPane;
import javafx.scene.control.SplitPane.Divider;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
public class ErlyBerly extends Application {
private static final NodeAPI NODE_API = new NodeAPI();
/**
* The preferences tab, that is added when the user presses the 'Preferences'
* button in the top bar. Static because there can be only one.
*/
private static Tab prefstab;
private SplitPane splitPane;
private Region entopPane;
private double entopDivPosition;
private static TabPane tabPane;
private static TermFormatter termFormatter;
public static void main(String[] args) throws Exception {
launch(args);
}
@Override
public void start(Stage primaryStage) {
try {
PrefBind.setup();
} catch (IOException e) {
e.printStackTrace();
}
termFormatter = formatterFromConfig();
FxmlLoadable topBarFxml;
topBarFxml = new FxmlLoadable("/erlyberly/topbar.fxml");
topBarFxml.load();
FxmlLoadable dbgFxml;
dbgFxml = new FxmlLoadable("/erlyberly/dbg.fxml");
dbgFxml.load();
DbgView dbgView = (DbgView)dbgFxml.controller;
tabPane = dbgView.getTabPane();
splitPane = new SplitPane();
entopPane = (Region) loadEntopPane();
splitPane.getItems().add(entopPane);
splitPane.getItems().add(dbgFxml.load());
setupProcPaneHiding(topBarFxml, dbgFxml);
VBox rootView;
rootView = new VBox(topBarFxml.fxmlNode, splitPane);
rootView.setMaxWidth(Double.MAX_VALUE);
VBox.setVgrow(splitPane, Priority.ALWAYS);
Scene scene;
scene = new Scene(rootView);
scene.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent t) {
if (KeyCode.W.equals(t.getCode()) && t.isShortcutDown()) {
Tab selectedItem = tabPane.getSelectionModel().getSelectedItem();
if(selectedItem != null && selectedItem.isClosable()) {
tabPane.getTabs().remove(selectedItem);
t.consume();
}
}
}
});
// the window size needs to be set before the scene is added or else it
// counts as a window resize and that also resizes the split panes
final double windowWidth = PrefBind.getOrDefaultDouble("windowWidth", 800D);
primaryStage.setWidth(windowWidth);
primaryStage.widthProperty().addListener((o, ov, nv) -> {
PrefBind.set("windowWidth", nv);
});
final double windowHeight = PrefBind.getOrDefaultDouble("windowHeight", 600D);
primaryStage.setHeight(windowHeight);
primaryStage.heightProperty().addListener((o, ov, nv) -> {
PrefBind.set("windowHeight", nv);
});
applyCssToWIndow(scene);
primaryStage.setScene(scene);
primaryStage.titleProperty().bind(NODE_API.summaryProperty());
primaryStage.setResizable(true);
primaryStage.show();
displayConnectionPopup(primaryStage);
FilterFocusManager.init(scene);
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent t) {
try {
nodeAPI().manuallyDisconnect();
} catch(Exception e) {
System.out.println(e);
}
Platform.exit();
System.exit(0);
}
});
// run this later because it requires the control's scene to be set, which
// may not have happened yet.
Platform.runLater(() -> {
sizeSplitPanes(splitPane);
dbgView.sizeSplitPanes();
});
}
private static TermFormatter formatterFromConfig() {
String formattingPref = PrefBind.getOrDefault("termFormatting", "erlang").toString();
if("erlang".equals(formattingPref))
return new ErlangFormatter();
else if("elixir".equals(formattingPref))
return new ElixirFormatter();
else if("lfe".equals(formattingPref))
return new LFEFormatter();
else
throw new RuntimeException("Invalid configuration for property 'termFormatting' it must be 'erlang' or 'lfe' but was " + formattingPref);
}
public static void applyCssToWIndow(Scene scene) {
scene.getStylesheets().add(ErlyBerly.class.getResource("/floatyfield/floaty-field.css").toExternalForm());
scene.getStylesheets().add(ErlyBerly.class.getResource("/erlyberly/erlyberly.css").toString());
}
private void setupProcPaneHiding(FxmlLoadable topBarFxml, FxmlLoadable dbgFxml) {
TopBarView topView;
DbgView dbgView;
topView = (TopBarView) topBarFxml.controller;
dbgView = (DbgView) dbgFxml.controller;
topView.hideProcsProperty().addListener((ObservableValue<? extends Boolean> o, Boolean ob, Boolean nb) -> {
if(!nb) {
showProcsPane();
}
else {
hideProcsPane();
}
});
topView.hideFunctionsProperty().addListener((ObservableValue<? extends Boolean> o, Boolean ob, Boolean nb) -> {
dbgView.setFunctionsVisibility(nb);
});
boolean hideProcs = PrefBind.getOrDefaultBoolean("hideProcesses", false);
if(hideProcs){
hideProcsPane();
}
boolean hideMods = PrefBind.getOrDefaultBoolean("hideModules", false);
if(hideMods){
dbgView.setFunctionsVisibility(true);
}
topView.setOnRefreshModules(dbgView::onRefreshModules);
Platform.runLater(() -> { topView.addAccelerators(); });
}
private void showProcsPane(){
splitPane.getItems().add(0, entopPane);
Divider div = splitPane.getDividers().get(0);
div.setPosition(entopDivPosition);
}
private void hideProcsPane(){
Divider div = splitPane.getDividers().get(0);
entopDivPosition = div.getPosition();
div.setPosition(0d);
splitPane.getItems().remove(0);
}
private Parent loadEntopPane() {
Parent entopPane = new FxmlLoadable("/erlyberly/entop.fxml").load();
SplitPane.setResizableWithParent(entopPane, Boolean.FALSE);
return entopPane;
}
private void displayConnectionPopup(Stage primaryStage) {
Stage connectStage;
connectStage = new Stage();
connectStage.initModality(Modality.WINDOW_MODAL);
connectStage.setScene(new Scene(new FxmlLoadable("/erlyberly/connection.fxml").load()));
connectStage.setAlwaysOnTop(true);
// javafx vertical resizing is laughably ugly, lets just disallow it
connectStage.setResizable(false);
connectStage.setWidth(400);
// if the user closes the window without connecting then close the app
connectStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent e) {
if(!NODE_API.connectedProperty().get()) {
Platform.exit();
}
Platform.runLater(() -> { primaryStage.setResizable(true); });
}});
connectStage.show();
}
public static NodeAPI nodeAPI() {
return NODE_API;
}
/**
* Show a new control in the tab pane. The tab is closable.
*/
public static void showPane(String title, Pane parentControl) {
assert Platform.isFxApplicationThread();
Tab newTab;
newTab = new Tab(title);
newTab.setContent(parentControl);
addAndSelectTab(newTab);
}
private static void addAndSelectTab(Tab newTab) {
tabPane.getTabs().add(newTab);
tabPane.getSelectionModel().select(newTab);
}
public static void showPreferencesPane() {
if(prefstab == null) {
FxmlLoadable fxmlLoadable = new FxmlLoadable("/erlyberly/preferences.fxml");
Parent parent = fxmlLoadable.load();
Pane tabPane = ErlyBerly.wrapInPane(parent);
prefstab = new Tab("Preferences");
prefstab.setContent(tabPane);
}
if(tabPane.getTabs().contains(prefstab)) {
tabPane.getSelectionModel().select(prefstab);
}
else {
addAndSelectTab(prefstab);
}
}
/**
* All I know is pane.
*/
public static Pane wrapInPane(Node node) {
if(node instanceof Pane)
return (Pane) node;
VBox.setVgrow(node, Priority.ALWAYS);
VBox vBox = new VBox(node);
return vBox;
}
public static TermFormatter getTermFormatter() {
return termFormatter;
}
public static void setTermFormatter(TermFormatter aTermFormatter) {
termFormatter = aTermFormatter;
}
public void sizeSplitPanes(SplitPane splitpane) {
assert splitpane.getScene() != null;
assert splitpane.getScene().getWidth() > 0.0d;
try {
double configuredProcessesWidth = configuredProcessesWidth();
double sceneWidth = splitpane.getScene().getWidth();
double percent = (configuredProcessesWidth / sceneWidth);
// the split pane divider position can only be set as a percentage of the split pane
splitpane.setDividerPosition(0, percent);
splitpane.setDividerPosition(1, 1D - percent);
}
catch (NumberFormatException e) {
e.printStackTrace();
}
// whenever the width of the pane changes, write it to configuration
// this is buffered so rapid writes do not cause rapid writes to disk
entopPane.widthProperty().addListener((o, ov, nv) -> {
PrefBind.set("processesWidth", nv);
});
}
private double configuredProcessesWidth() {
double w = PrefBind.getOrDefaultDouble("processesWidth", 300D);
return w;
}
}