package nl.uva.bromance.QL.gui; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import javafx.stage.FileChooser; import javafx.stage.Stage; import nl.uva.bromance.QL.ast.AST; import nl.uva.bromance.QL.ast.QLNode; import nl.uva.bromance.QL.ast.QLParseTreeListener; import nl.uva.bromance.QL.exceptions.QLError; import nl.uva.bromance.QL.exceptions.TypeCheckingError; import nl.uva.bromance.QL.expressions.unary.Primitive; import nl.uva.bromance.QL.typechecking.TypeCheckerVisitor; import nl.uva.bromance.grammar.QL.QLLexer; import nl.uva.bromance.grammar.QL.QLParser; import org.antlr.v4.runtime.ANTLRInputStream; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.TokenStream; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTreeWalker; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.UUID; public class QLGUI { private AST<QLNode> ast; private Stage stage; private VBox questionArea; private String stylesheets; private Map<String, Primitive> answerMap; private UUID focusUuid; private Node focusedNode; public QLGUI(Stage stage, String stylesheets) { this.stage = stage; this.ast = null; this.stylesheets = stylesheets; this.focusUuid = null; createBaseView(); } private void createBaseView() { VBox rootBox = new VBox(); rootBox.setMaxWidth(650); rootBox.setMinWidth(650); rootBox.setMinHeight(800); rootBox.setMaxHeight(800); questionArea = new VBox(); Scene scene = new Scene(rootBox, 650, 800); MenuBar menuBar = createMenuBar(); rootBox.getChildren().addAll(menuBar, questionArea); scene.getStylesheets().add(stylesheets); stage.setScene(scene); } private MenuBar createMenuBar() { MenuBar menuBar = new MenuBar(); Menu menuFile = new Menu("File"); MenuItem filePicker = new MenuItem("Open"); createOpenMenuItemHandler(filePicker); menuFile.getItems().add(filePicker); menuBar.getMenus().add(menuFile); return menuBar; } private void createOpenMenuItemHandler(MenuItem filePicker) { filePicker.setOnAction((event) -> { FileChooser fileChooser = new FileChooser(); File file = fileChooser.showOpenDialog(stage); if (file != null) { String qlPath = file.getAbsolutePath(); try { // Setup the ANTLR code and build the AST InputStream is = new FileInputStream(qlPath); QLLexer lexer = new QLLexer(new ANTLRInputStream(is)); TokenStream tokenStream = new CommonTokenStream(lexer); QLParser parser = new QLParser(tokenStream); GrammarErrorListener errorListener = new GrammarErrorListener(); List<QLError> syntaxErrors = new ArrayList<>(); parser.addErrorListener(errorListener); ParseTree tree = parser.questionnaire(); errorListener.appendSyntaxErrors(syntaxErrors); QLParseTreeListener qlListener = new QLParseTreeListener(); ParseTreeWalker qlWalker = new ParseTreeWalker(); qlWalker.walk(qlListener, tree); AST<QLNode> qlAst = qlListener.getAST(); TypeCheckerVisitor typeChecker = new TypeCheckerVisitor(); List<TypeCheckingError> typeCheckingErrors = typeChecker.check(qlAst.getRoot()); List<TypeCheckingError> warnings = getAndRemoveWarnings(typeCheckingErrors); if (typeCheckingErrors.isEmpty()) { showAlert(QLError.convertTypeCheckingErrorListToQLErrorList(warnings), Alert.AlertType.WARNING); showAst(syntaxErrors, qlListener, qlAst); } else { showAlert(QLError.convertTypeCheckingErrorListToQLErrorList(typeCheckingErrors), Alert.AlertType.ERROR); } // Error will have been printed elsewhere already, no need to here. Just catching all to keep program running } catch (Exception e) { } } }); } private void showAst(List<QLError> syntaxErrors, QLParseTreeListener qlListener, AST<QLNode> qlAst) { if (syntaxErrors.isEmpty() && qlAst != null) { this.ast = qlAst; this.answerMap = qlListener.getIdentifierMap(); createBaseView(); render(); } else { showAlert(syntaxErrors, Alert.AlertType.ERROR); } } private void showAlert(List<QLError> list, Alert.AlertType type) { if (list.size() > 0) { Alert alert = new Alert(type); alert.setContentText("Your file contains: " + list.size() + " errors and/or warnings."); addScrollAblePaneToAlert(QLError.qlErrorListToString(list), alert); alert.show(); } } private void addScrollAblePaneToAlert(String exceptionText, Alert alert) { TextArea textArea = new TextArea(exceptionText); textArea.setEditable(false); textArea.setWrapText(true); textArea.setMaxWidth(Double.MAX_VALUE); textArea.setMaxHeight(Double.MAX_VALUE); GridPane.setVgrow(textArea, Priority.ALWAYS); GridPane.setHgrow(textArea, Priority.ALWAYS); GridPane expContent = new GridPane(); expContent.setMaxWidth(Double.MAX_VALUE); expContent.add(textArea, 0, 0); alert.getDialogPane().setExpandableContent(expContent); } public void render() { renderWithFocus(null); } public void renderWithFocus(UUID uuid) { this.focusUuid = uuid; if (ast != null) { questionArea.getChildren().clear(); QLGuiVisitor visitor = new QLGuiVisitor(questionArea, answerMap, this, ast.getRoot()); ast.getRoot().accept(visitor); if (this.focusedNode != null) this.focusedNode.requestFocus(); } stage.show(); } private List<TypeCheckingError> getAndRemoveWarnings(List<TypeCheckingError> typeCheckingErrors) { List<TypeCheckingError> warnings = new ArrayList<>(); for (TypeCheckingError e : typeCheckingErrors) { if (e.isWarning()) warnings.add(e); } for (TypeCheckingError w : warnings) { if (typeCheckingErrors.contains(w)) typeCheckingErrors.remove(w); } return warnings; } public UUID getFocusUuid() { return this.focusUuid; } public void setFocusedNode(Node node) { this.focusedNode = node; } }