From 4e24363e634980874e90fe974cce944463a3de6d Mon Sep 17 00:00:00 2001 From: h7x4 Date: Tue, 23 Feb 2021 00:46:43 +0100 Subject: [PATCH] Automate switching between languages --- src/main/java/app/Main.java | 71 +++++++++++++------ src/main/java/app/MainController.java | 29 ++++++-- .../app/controllers/EditorController.java | 40 +++++++---- .../app/controllers/MenubarController.java | 34 +++++++-- .../app/controllers/ModelineController.java | 28 +++++--- .../java/app/events/EditorChangedEvent.java | 2 +- src/main/java/app/events/Event.java | 2 +- .../java/app/events/LanguageChangedEvent.java | 33 +++++++++ src/main/java/app/languages/Java.java | 27 +++---- src/main/java/app/languages/Markdown.java | 60 ++++++++++++++++ src/main/java/app/model/Model.java | 16 ++++- .../resources/fxml/components/Menubar.fxml | 18 ++++- src/main/resources/styling/language/java.css | 61 ++++++++++++++++ .../resources/styling/language/markdown.css | 71 +++++++++++++++++++ 14 files changed, 418 insertions(+), 74 deletions(-) create mode 100644 src/main/java/app/events/LanguageChangedEvent.java create mode 100644 src/main/java/app/languages/Markdown.java create mode 100644 src/main/resources/styling/language/java.css create mode 100644 src/main/resources/styling/language/markdown.css diff --git a/src/main/java/app/Main.java b/src/main/java/app/Main.java index 0b38f26..7518400 100644 --- a/src/main/java/app/Main.java +++ b/src/main/java/app/Main.java @@ -9,41 +9,70 @@ import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.stage.Stage; +import app.events.LanguageChangedEvent; +import app.model.Model; + public class Main extends Application { + private Stage window; + private Scene scene; + private FXMLLoader fxmlLoader; + private Parent fxmlRoot; + + private static final String TITLE = "Banana Editor"; + private static final String ICON_PATH = "/graphics/logo.png"; + /** - * Boilerplate function to launch the application + * Boilerplate function to launch the application. */ public static void main(String[] args) { launch(args); } /** - * The entrypoint of the application + * Set up a window with title and icon. + */ + private void setupWindow(Stage window) { + this.window = window; + window.setTitle(TITLE); + window.getIcons().add(new Image(getClass().getResourceAsStream(ICON_PATH))); + } + + private void loadFXML() throws IOException { + this.fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/Main.fxml")); + this.fxmlRoot = fxmlLoader.load(); + } + + private void createScene() { + this.scene = new Scene(fxmlRoot); + Model.setScene(scene); + } + + private void applyCSS() { + scene.getStylesheets().add( + getClass().getResource("/styling/app.css").toExternalForm()); + } + + /** + * Set up default values and settings for the editor. + */ + private void setupDefaults() { + MainController mainController = fxmlLoader.getController(); + mainController.getEventBus().post(new LanguageChangedEvent("Java")); + } + + /** + * The entrypoint of the application. * @param window The primary window of the application */ @Override public void start(Stage window) throws IOException { - window.setTitle("Banana Editor"); - window.getIcons().add(new Image(getClass().getResourceAsStream("/graphics/logo.png"))); - - FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/Main.fxml")); - Parent document; - - try { - document = fxmlLoader.load(); - } catch (IOException e) { - System.err.println(e); - throw new RuntimeException( - "Couldn\'t find or missing permissions to load " - + getClass().getResource("/fxml/Main.fxml") - ); - } - - Scene scene = new Scene(document); - - scene.getStylesheets().add(getClass().getResource("/styling/app.css").toExternalForm()); + setupWindow(window); + loadFXML(); + createScene(); + applyCSS(); + setupDefaults(); window.setScene(scene); window.show(); diff --git a/src/main/java/app/MainController.java b/src/main/java/app/MainController.java index 5aeb26d..82c2504 100644 --- a/src/main/java/app/MainController.java +++ b/src/main/java/app/MainController.java @@ -5,8 +5,10 @@ import java.util.List; import java.util.ResourceBundle; import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; import app.controllers.*; +import app.events.LanguageChangedEvent; import app.model.Model; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -26,11 +28,11 @@ public class MainController implements Initializable { private MenubarController menubarController; private EventBus eventBus; - private Model model; @Override public void initialize(URL url, ResourceBundle resourceBundle) { this.eventBus = new EventBus(); + this.eventBus.register(this); List.of( editorController, @@ -38,18 +40,35 @@ public class MainController implements Initializable { modelineController, menubarController ).forEach(c -> c.setEventBus(this.eventBus)); + } + /** + * Get the global eventbus. + * @return The eventbus + */ public EventBus getEventBus() { return this.eventBus; } /** - * Links the controller to the global model - * @param model The model to be linked + * Change the CSS according to which language is being used. + * @param event The event containing data about the language to switch to */ - public void setModel(Model model) { - this.model = model; + @Subscribe + private void handle(LanguageChangedEvent event) { + + boolean containsSeveralStylesheets = Model.getScene().getStylesheets().size() != 1; + + if (containsSeveralStylesheets) + Model.getScene().getStylesheets().remove(1); + + String nextStyleSheet = + getClass() + .getResource("/styling/language/" + event.getLanguage().toLowerCase() + ".css") + .toExternalForm(); + + Model.getScene().getStylesheets().add(nextStyleSheet); } } \ No newline at end of file diff --git a/src/main/java/app/controllers/EditorController.java b/src/main/java/app/controllers/EditorController.java index 1a4e542..6ceaa6d 100644 --- a/src/main/java/app/controllers/EditorController.java +++ b/src/main/java/app/controllers/EditorController.java @@ -5,6 +5,7 @@ import java.util.Collection; import java.util.ResourceBundle; import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; import org.fxmisc.richtext.CodeArea; import org.fxmisc.richtext.LineNumberFactory; @@ -13,6 +14,7 @@ import org.fxmisc.richtext.model.TwoDimensional.Bias; import org.fxmisc.richtext.model.TwoDimensional.Position; import app.events.EditorChangedEvent; +import app.events.LanguageChangedEvent; import app.model.Model; import app.service.LanguageOperations; import javafx.fxml.FXML; @@ -39,20 +41,26 @@ public class EditorController implements Initializable, Controller { } /** - * Links the controller to the global model - * @param model The model to be linked + * Applies highlighting to the editor. + * @param highlighting highlighting data */ - public void setModel(Model model) { - this.model = model; - } - - public void setHighlighting(StyleSpans> highlighting) { + private void setHighlighting(StyleSpans> highlighting) { this.editor.setStyleSpans(0, highlighting); } /** - * Handles events from the editor, and reflects them in the model - * @param event The object containing metadata of the event + * Recalculates and refreshes the syntax highlighting of the editor. + */ + private void refreshHighlighting() { + this.setHighlighting( + LanguageOperations.syntaxHighlight( + this.editor.getText(), + Model.getLanguage())); + } + + /** + * Handles the event whenever the content of the editor is changed. + * @param event JavaFX event containing metadata */ @FXML public void editorChanged(KeyEvent event) { @@ -64,10 +72,16 @@ public class EditorController implements Initializable, Controller { pos.getMajor() + 1, pos.getMinor() )); - this.setHighlighting( - LanguageOperations.syntaxHighlight( - this.editor.getText(), - Model.getLanguage())); + this.refreshHighlighting(); + } + + /** + * Refreshes the editor whenever the language is changed. + * @param event Which language was switched to + */ + @Subscribe + private void handle(LanguageChangedEvent event) { + this.refreshHighlighting(); } } diff --git a/src/main/java/app/controllers/MenubarController.java b/src/main/java/app/controllers/MenubarController.java index 8785ba6..b63ff57 100644 --- a/src/main/java/app/controllers/MenubarController.java +++ b/src/main/java/app/controllers/MenubarController.java @@ -5,11 +5,16 @@ import java.net.URL; import java.util.ResourceBundle; import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; +import app.events.LanguageChangedEvent; import app.model.Model; +import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.scene.control.MenuBar; +import javafx.scene.control.RadioMenuItem; +import javafx.scene.control.ToggleGroup; import javafx.stage.DirectoryChooser; import javafx.stage.FileChooser; import javafx.stage.Stage; @@ -17,11 +22,13 @@ import javafx.stage.Stage; public class MenubarController implements Initializable, Controller { private EventBus eventBus; - private Model model; @FXML private MenuBar menubar; + @FXML + private ToggleGroup languageToggleGroup; + @FXML public String handleOpenFile() { FileChooser fc = new FileChooser(); @@ -76,12 +83,27 @@ public class MenubarController implements Initializable, Controller { } /** - * Links the controller to the global model - * - * @param model The model to be linked + * Handles the event where the language was change from the menu. */ - public void setModel(Model model) { - this.model = model; + @FXML + private void handleLanguageChange(ActionEvent event) { + this.eventBus.post( + new LanguageChangedEvent( + ((RadioMenuItem) event.getSource()).getText())); } + /** + * Updates menubuttons whenever the language is changed + */ + @Subscribe + private void handle(LanguageChangedEvent event) { + this.languageToggleGroup + .getToggles() + .stream() + .map(RadioMenuItem.class::cast) + .filter(t -> t.getId().equals("toggle" + event.getLanguage())) + .findFirst() + .orElseThrow() + .setSelected(true); + } } diff --git a/src/main/java/app/controllers/ModelineController.java b/src/main/java/app/controllers/ModelineController.java index 459eba6..46a01ef 100644 --- a/src/main/java/app/controllers/ModelineController.java +++ b/src/main/java/app/controllers/ModelineController.java @@ -7,6 +7,7 @@ import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import app.events.EditorChangedEvent; +import app.events.LanguageChangedEvent; import app.model.Model; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -21,7 +22,6 @@ public class ModelineController implements Initializable, Controller { private Label language; private EventBus eventBus; - private Model model; @Override public void initialize(URL url, ResourceBundle resourceBundle) { @@ -34,14 +34,6 @@ public class ModelineController implements Initializable, Controller { this.eventBus.register(this); } - /** - * Links the controller to the global model - * @param model The model to be linked - */ - public void setModel(Model model) { - this.model = model; - } - /** * Update the colum row counter * @param column The column number @@ -51,9 +43,23 @@ public class ModelineController implements Initializable, Controller { this.columnrow.setText(String.format("[%d:%d]", row, column)); } + /** + * Updates the column-row number display whenever the editor cursor + * changes position. + * @param event + */ @Subscribe - public void handle(EditorChangedEvent event) { - System.out.println("Key pressed: " + event.getKeyCode()); + private void handle(EditorChangedEvent event) { this.setColumnRow(event.getColumn(), event.getLineNumber()); } + + /** + * Updates the modeline to display a new language + * whenever it is changed. + * @param event + */ + @Subscribe + private void handle(LanguageChangedEvent event) { + this.language.setText(event.getLanguage()); + } } \ No newline at end of file diff --git a/src/main/java/app/events/EditorChangedEvent.java b/src/main/java/app/events/EditorChangedEvent.java index b85e8d9..1effba0 100644 --- a/src/main/java/app/events/EditorChangedEvent.java +++ b/src/main/java/app/events/EditorChangedEvent.java @@ -2,7 +2,7 @@ package app.events; import javafx.scene.input.KeyCode; -public class EditorChangedEvent implements Event { +public class EditorChangedEvent extends Event { private KeyCode keyCode; private int lineNumber; diff --git a/src/main/java/app/events/Event.java b/src/main/java/app/events/Event.java index f7ca520..ebec937 100644 --- a/src/main/java/app/events/Event.java +++ b/src/main/java/app/events/Event.java @@ -1,3 +1,3 @@ package app.events; -interface Event {} +class Event {} diff --git a/src/main/java/app/events/LanguageChangedEvent.java b/src/main/java/app/events/LanguageChangedEvent.java new file mode 100644 index 0000000..b7910e6 --- /dev/null +++ b/src/main/java/app/events/LanguageChangedEvent.java @@ -0,0 +1,33 @@ +package app.events; + +import app.languages.Java; +import app.languages.Markdown; +import app.model.Model; + +public class LanguageChangedEvent extends Event { + + private String language; + + public LanguageChangedEvent(String language) { + this.language = language; + + switch (language) { + + case "Java": + Model.setLanguage(new Java()); + break; + + case "Markdown": + Model.setLanguage(new Markdown()); + break; + + default: + break; + } + } + + public String getLanguage() { + return language; + } + +} diff --git a/src/main/java/app/languages/Java.java b/src/main/java/app/languages/Java.java index 64bcce0..90252e3 100644 --- a/src/main/java/app/languages/Java.java +++ b/src/main/java/app/languages/Java.java @@ -33,17 +33,20 @@ public class Java implements ProgrammingLanguage { private static final Map pattern = Map.ofEntries( - e("\\b(" + String.join("|", keywords) + ")\\b", "keyword"), // , , .... - e("this", "this"), // - e("true", "true"), // - e("false", "false"), // - e("\\(|\\)", "paranthesis"), // <(*)> - e("\\{|\\}", "curlyBrackets"), // <{*}> - e("\\[|\\]", "squareBrackets"), // <[*]> - e(";", "semiColon"), // <;> - e("[A-Z]\\w+", "identifier"), // or - e("\\.\\w+", "property"), // <.property> - e("(?<=this\\.)?\\w+(?=\\(*\\))", "method") // + e("\"([^\"\\\\]|\\\\.)*\"", "string"), + e("\\bthis\\b", "this"), + e("\\btrue\\b", "true"), + e("\\bfalse\\b", "false"), + e("(?<=\\.?)\\w+(?=\\()", "method"), + e("\\(|\\)", "paranthesis"), + e("\\{|\\}", "curlyBrackets"), + e("\\[|\\]", "squareBrackets"), + e(";", "semicolon"), + e("\\.\\w+\\b(?!\\()", "property"), + e("[A-Z]\\w+", "identifier"), + e("\\b(" + String.join("|", keywords) + ")\\b", + "keyword"), + e("(?://.*)|(?:\\/?\\s?\\*.*)", "comment") ); public String getName() { @@ -58,8 +61,6 @@ public class Java implements ProgrammingLanguage { return Java.pattern; } - // https://stackoverflow.com/questions/16517689/java-regex-capturing-groups-indexes/16582083 - public Pattern getPattern() { return Pattern.compile( Java.pattern diff --git a/src/main/java/app/languages/Markdown.java b/src/main/java/app/languages/Markdown.java new file mode 100644 index 0000000..5ab3243 --- /dev/null +++ b/src/main/java/app/languages/Markdown.java @@ -0,0 +1,60 @@ +package app.languages; + +import java.net.URL; +import java.util.AbstractMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import app.model.ProgrammingLanguage; + +public class Markdown implements ProgrammingLanguage { + + private String name = "Markdown"; + private URL iconPath = this.getClass().getResource(""); + + private static Entry e(String k, String v) { + return new AbstractMap.SimpleEntry<>(Pattern.compile(k), v); + } + + private static final Map pattern = + Map.ofEntries( + e("##### .*", "ssssheader"), + e("#### .*", "sssheader"), + e("### .*", "ssheader"), + e("## .*", "sheader"), + e("# .*", "header"), + e("\\*\\*\\*.*\\*\\*\\*|___.*___", "emphasizedItalic"), + e("\\*\\*(?!\\*).*\\*\\*|__(?!_).*__", "emphasized"), + e("\\*(?!\\*).*\\*|_(?!_).*_", "italic"), + e("~~.*~~", "strikethrough"), + e("[\\-*+] .*", "listItem"), + e("\\d+\\. .*", "numberedItem"), + e("(? getPatternMap() { + return Markdown.pattern; + } + + public Pattern getPattern() { + return Pattern.compile( + Markdown.pattern + .entrySet() + .stream() + .map(e -> String.format("(?<%s>%s)", e.getValue(), e.getKey())) + .collect(Collectors.joining("|"))); + } + +} diff --git a/src/main/java/app/model/Model.java b/src/main/java/app/model/Model.java index 298c6d0..3595139 100644 --- a/src/main/java/app/model/Model.java +++ b/src/main/java/app/model/Model.java @@ -1,12 +1,18 @@ package app.model; +import javafx.scene.Scene; + /** - * Data model of the application + * Data model of the application. + * + * Contains a static reference to state that has to be accessed + * by multiple pieces in the application, including the primary scene. */ public class Model { private static String activeFilePath; private static String currentProjectPath; private static ProgrammingLanguage currentProgrammingLanguage; + private static Scene scene; public static String getActiveFilePath() { return activeFilePath; @@ -20,7 +26,15 @@ public class Model { return currentProgrammingLanguage; } + public static Scene getScene() { + return scene; + } + public static void setLanguage(ProgrammingLanguage language) { Model.currentProgrammingLanguage = language; } + + public static void setScene(Scene scene) { + Model.scene = scene; + } } \ No newline at end of file diff --git a/src/main/resources/fxml/components/Menubar.fxml b/src/main/resources/fxml/components/Menubar.fxml index bef8f9c..112f47f 100644 --- a/src/main/resources/fxml/components/Menubar.fxml +++ b/src/main/resources/fxml/components/Menubar.fxml @@ -5,6 +5,8 @@ + + + + + + + - - + + + diff --git a/src/main/resources/styling/language/java.css b/src/main/resources/styling/language/java.css new file mode 100644 index 0000000..95132ce --- /dev/null +++ b/src/main/resources/styling/language/java.css @@ -0,0 +1,61 @@ + +.code-area .this { + -fx-fill: #FFFF00; + -fx-font-weight: bold; +} + +.code-area .true { + -fx-fill: #00FF00; + -fx-font-weight: bold; +} + +.code-area .false { + -fx-fill: #FF0000; + -fx-font-weight: bold; +} + +.code-area .paranthesis { + -fx-fill: -main-fg-color; +} + +.code-area .curlyBrackets { + -fx-fill: -main-fg-color; +} + +.code-area .squareBrackets { + -fx-fill: -main-fg-color; +} + +.code-area .semicolon { + -fx-fill: -main-fg-color; +} + +.code-area .identifier { + -fx-fill: -main-blue-color; + -fx-font-weight: bold; +} + +.code-area .property { + -fx-fill: -main-fg-color; +} + +.code-area .method { + -fx-fill: -main-green-color; +} + +.code-area .keyword { + -fx-fill: -main-red-color; + -fx-font-weight: bold; +} + +.code-area .comment { + -fx-fill: -light-bg-color; +} + +.code-area .string { + -fx-fill: -main-yellow-color; +} + +.code-area .escapeChar { + -fx-fill: -main-magenta-color; +} \ No newline at end of file diff --git a/src/main/resources/styling/language/markdown.css b/src/main/resources/styling/language/markdown.css new file mode 100644 index 0000000..f3716a9 --- /dev/null +++ b/src/main/resources/styling/language/markdown.css @@ -0,0 +1,71 @@ +.code-area { + -test-color: #193814 +} + +.code-area .header { + -fx-fill: -main-green-color; + -fx-font-size: 200%; + -fx-font-weight: bold; +} + +.code-area .sheader { + -fx-fill: -main-blue-color; + -fx-font-size: 150%; + -fx-font-weight: bold; +} + +.code-area .ssheader { + -fx-fill: -main-red-color; + -fx-font-size: 130%; +} + +.code-area .sssheader { + -fx-fill: -main-magenta-color; + -fx-font-size: 120%; +} + +.code-area .ssssheader { + -fx-fill: -main-cyan-color; + -fx-font-size: 110%; +} + +.code-area .emphasized { + -fx-fill: -main-blue-color; + -fx-font-weight: bold; +} + +.code-area .italic { + -fx-fill: -main-cyan-color; + -fx-font-style: italic; +} + +.code-area .emphasizedItalic { + -fx-fill: -main-magenta-color; + -fx-font-weight: bold; + -fx-font-style: italic; +} + +.code-area .strikethrough { + -fx-fill: -main-red-color; + -fx-strikethrough: true; +} + +.code-area .listItem { + -fx-fill: -main-yellow-color; +} + +.code-area .numberedItem { + -fx-fill: -main-yellow-color; +} + +.code-area .link { + -fx-fill: -main-red-color; +} + +.code-area .image { + -fx-fill: -main-magenta-color; +} + +.code-area .source { + -fx-fill: -main-magenta-color; +} \ No newline at end of file