diff --git a/app/gui-javafx/src/main/java/module-info.java b/app/gui-javafx/src/main/java/module-info.java index 1ab8ae09..4cb4ba0f 100644 --- a/app/gui-javafx/src/main/java/module-info.java +++ b/app/gui-javafx/src/main/java/module-info.java @@ -7,6 +7,7 @@ module org.applecommander.javafx { requires org.applecommander.api; requires org.applecommander.applesingle; requires org.applecommander.bastools; + requires org.applecommander.shrinkit; opens org.applecommander.javafx to javafx.graphics; opens org.applecommander.javafx.settings to javafx.graphics; diff --git a/app/gui-javafx/src/main/java/org/applecommander/javafx/AppleCommanderFX.java b/app/gui-javafx/src/main/java/org/applecommander/javafx/AppleCommanderFX.java index 7f6273c1..8ef1239b 100644 --- a/app/gui-javafx/src/main/java/org/applecommander/javafx/AppleCommanderFX.java +++ b/app/gui-javafx/src/main/java/org/applecommander/javafx/AppleCommanderFX.java @@ -30,9 +30,12 @@ import java.io.PrintWriter; import java.io.StringWriter; public class AppleCommanderFX extends Application { + private static AppleCommanderFX application; @Override public void start(Stage stage) throws Exception { + // This is really stupid the hoops just to launch the browser. + application = this; // Based on user selection, use that theme. Settings.getThemeSelection().orElse(ThemeSelection.MODENA).apply(); Platform.getPreferences().colorSchemeProperty().addListener((_, _, _) -> { @@ -46,6 +49,10 @@ public class AppleCommanderFX extends Application { launch(args); } + public static AppleCommanderFX getApplication() { + return application; + } + public static String buildTitle(String... args) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); diff --git a/app/gui-javafx/src/main/java/org/applecommander/javafx/FXControls.java b/app/gui-javafx/src/main/java/org/applecommander/javafx/FXControls.java index 3268a13f..66612f3d 100644 --- a/app/gui-javafx/src/main/java/org/applecommander/javafx/FXControls.java +++ b/app/gui-javafx/src/main/java/org/applecommander/javafx/FXControls.java @@ -23,11 +23,13 @@ import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.event.ActionEvent; import javafx.geometry.Insets; +import javafx.geometry.Orientation; +import javafx.geometry.Pos; import javafx.scene.Node; -import javafx.scene.control.CheckBox; -import javafx.scene.control.Label; -import javafx.scene.control.RadioButton; -import javafx.scene.control.ToggleGroup; +import javafx.scene.control.*; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; import java.util.function.Function; @@ -35,26 +37,82 @@ import java.util.function.Function; /// This is a simple control builder for JavaFX components. It is intended to include bindings /// that reflect the given property. public class FXControls { - public static FXControls builder() { - return new FXControls(); + public static FXControls vertical() { + return new FXControls(new VBox()); + } + public static FXControls horizontal() { + return new FXControls(new HBox()); } - private final VBox vbox; + private final Pane pane; - private FXControls() { - vbox = new VBox(); - vbox.setSpacing(10); - vbox.setPadding(new Insets(10)); + private FXControls(Pane pane) { + this.pane = pane; + spacing(10); + pane.setPadding(new Insets(10)); } public Node get() { - return vbox; + return pane; + } + + public FXControls alignment(Pos value) { + if (pane instanceof VBox vbox) { + vbox.setAlignment(value); + } + else if (pane instanceof HBox hbox) { + hbox.setAlignment(value); + } + return this; + } + + public FXControls spacing(double value) { + if (pane instanceof VBox vbox) { + vbox.setSpacing(value); + } + else if (pane instanceof HBox hbox) { + hbox.setSpacing(value); + } + return this; + } + + public FXControls node(Node node) { + pane.getChildren().add(node); + return this; } public FXControls label(String fmt, Object... args) { Label label = new Label(String.format(fmt, args)); label.setWrapText(true); - vbox.getChildren().add(label); + pane.getChildren().add(label); + return this; + } + + public FXControls link(String text, String url) { + Hyperlink hyperlink = new Hyperlink(url); + hyperlink.setText(text); + hyperlink.setOnAction(event -> { + AppleCommanderFX.getApplication().getHostServices().showDocument(url); + }); + pane.getChildren().add(hyperlink); + return this; + } + + public FXControls largeBold(String fmt, Object... args) { + Label label = new Label(String.format(fmt, args)); + label.setWrapText(true); + label.setStyle("-fx-font-weight: bold; -fx-font-size: 150%"); + pane.getChildren().add(label); + return this; + } + + public FXControls separator() { + if (pane instanceof VBox vbox) { + vbox.getChildren().add(new Separator(Orientation.HORIZONTAL)); + } + else if (pane instanceof HBox hbox) { + hbox.getChildren().add(new Separator(Orientation.VERTICAL)); + } return this; } @@ -69,7 +127,7 @@ public class FXControls { }); radioButton.addEventHandler(ActionEvent.ACTION, _ -> property.set(value)); property.addListener((_, _, newValue) -> radioButton.setSelected(newValue.equals(value))); - vbox.getChildren().add(radioButton); + pane.getChildren().add(radioButton); } return this; } @@ -80,7 +138,30 @@ public class FXControls { // We cannot bind this property, need to add listeners in BOTH directions. checkBox.addEventHandler(ActionEvent.ACTION, _ -> property.set(checkBox.isSelected())); property.addListener((_, _, newValue) -> checkBox.setSelected(newValue)); - vbox.getChildren().add(checkBox); + pane.getChildren().add(checkBox); + return this; + } + + public FXControls table(int columnCount, String... values) { + assert values.length % columnCount == 0; + GridPane grid = new GridPane(); + grid.setAlignment(Pos.CENTER); + grid.setHgap(5); + grid.setVgap(5); + int col = 0; + int row = 0; + for (String value : values) { + Label label = new Label(value); + if (col == 0) label.setStyle("-fx-font-weight: bold;"); + else label.setWrapText(true); + grid.add(label, col, row); + col++; + if (col >= columnCount) { + col = 0; + row++; + } + } + pane.getChildren().add(grid); return this; } } diff --git a/app/gui-javafx/src/main/java/org/applecommander/javafx/FileStoreWindow.java b/app/gui-javafx/src/main/java/org/applecommander/javafx/FileStoreWindow.java index 1e36c513..5cb7d491 100644 --- a/app/gui-javafx/src/main/java/org/applecommander/javafx/FileStoreWindow.java +++ b/app/gui-javafx/src/main/java/org/applecommander/javafx/FileStoreWindow.java @@ -19,6 +19,7 @@ */ package org.applecommander.javafx; +import com.webcodepro.applecommander.ui.AppleCommander; import javafx.beans.property.*; import javafx.geometry.Insets; import javafx.geometry.Orientation; @@ -37,12 +38,15 @@ import javafx.scene.shape.Rectangle; import javafx.stage.FileChooser; import javafx.stage.FileChooser.ExtensionFilter; import javafx.stage.Stage; +import org.applecommander.applesingle.AppleSingle; +import org.applecommander.bastools.api.BasTools; import org.applecommander.capability.Capability; import org.applecommander.filestore.FileStore; import org.applecommander.filestore.FileStores; import org.applecommander.javafx.settings.Settings; import org.applecommander.javafx.settings.SettingsDialog; import org.applecommander.javafx.wizard.CreateFileStoreWizard; +import org.applecommander.shrinkit.NuFileArchive; import org.applecommander.source.Source; import org.applecommander.source.Sources; import org.applecommander.usage.DiskUsage; @@ -50,6 +54,7 @@ import org.applecommander.util.FileExtensions; import org.applecommander.util.FileExtensions.FileExtension; import java.io.File; +import java.net.URL; import java.nio.file.Files; import java.nio.file.StandardOpenOption; import java.util.Objects; @@ -66,6 +71,7 @@ public class FileStoreWindow { private final Button saveFileButton; private final Button saveFileAsButton; private final Button settingsButton; + private final Button aboutButton; private final ToggleButton filesViewButton; private final ToggleButton diskUsageViewButton; private final ToggleButton informationViewButton; @@ -146,6 +152,7 @@ public class FileStoreWindow { saveFileButton = createButton("save-file.png", "Save", _ -> saveFile()); saveFileAsButton = createButton("save-as-file.png", "Save As...", _ -> saveFileAs()); settingsButton = createButton("settings.png", "Settings", _ -> settings()); + aboutButton = createButton("about.png", "About", _ -> about()); ToggleGroup viewModeGroup = new ToggleGroup(); filesViewButton = createToggleButton("image-file-view.png", "Files", viewModeGroup, _ -> selectFilesContent()); @@ -154,7 +161,7 @@ public class FileStoreWindow { HBox viewModeBox = new HBox(filesViewButton, diskUsageViewButton, informationViewButton); ToolBar toolBar = new ToolBar( - openFileButton, createFileButton, saveFileButton, saveFileAsButton, settingsButton, + openFileButton, createFileButton, saveFileButton, saveFileAsButton, settingsButton, aboutButton, new Separator(Orientation.VERTICAL), viewModeBox, new Separator(Orientation.VERTICAL) @@ -162,7 +169,7 @@ public class FileStoreWindow { saveFileButton.disableProperty().bind(changeCount.isEqualTo(0).or(canSave.not())); - ImageView logo = new ImageView(imageUrl("AppleCommanderLogo.png")); + ImageView logo = new ImageView(imageUrl("AppleCommanderLogoLarge.png")); Label label = new Label("No disk image open. Use open to browse for a disk image."); label.setTextFill(Color.BLACK); Rectangle border = new Rectangle(); @@ -372,6 +379,40 @@ public class FileStoreWindow { dialog.showAndWait(primaryStage); } + public void about() { + Dialog dialog = new Dialog<>(); + dialog.setTitle("About AppleCommanderFX"); + + URL imageURL = getClass().getResource("/images/AppleCommanderLogoSmall.png"); + Objects.requireNonNull(imageURL); + ImageView imageView = new ImageView(imageURL.toExternalForm()); + VBox imagePane = new VBox(imageView); + imagePane.setPadding(new Insets(0, 0, 10, 0)); + imagePane.setStyle("-fx-background-color: white;"); + imagePane.setAlignment(Pos.CENTER); + + dialog.getDialogPane().setHeader(imagePane); + dialog.getDialogPane().setContent(FXControls.vertical() + .alignment(Pos.CENTER) + .spacing(5) + .largeBold("AppleCommanderFX") + .label("Version %s", AppleCommander.VERSION) + .node(FXControls.horizontal() + .alignment(Pos.CENTER) + .spacing(5) + .label("Visit:") + .link("website", "https://applecommander.github.io/") + .link("github", "https://github.com/AppleCommander/AppleCommander") + .get()) + .table(2, + "AppleSingle:", AppleSingle.VERSION, + "ShrinkIt:", NuFileArchive.VERSION, + "BASIC Tools:", BasTools.VERSION) + .get()); + dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK); + dialog.showAndWait(); + } + private void switchDisk() { if (fileStoreSelection.getSelectedIndex()+1 < fileStoreSelection.getItemCount()) { fileStoreSelection.selectNext(); diff --git a/app/gui-javafx/src/main/java/org/applecommander/javafx/settings/SettingsDialog.java b/app/gui-javafx/src/main/java/org/applecommander/javafx/settings/SettingsDialog.java index 839f1e29..ce47d502 100644 --- a/app/gui-javafx/src/main/java/org/applecommander/javafx/settings/SettingsDialog.java +++ b/app/gui-javafx/src/main/java/org/applecommander/javafx/settings/SettingsDialog.java @@ -47,21 +47,21 @@ public class SettingsDialog { public SettingsDialog() { Tab themeTab = new Tab("Theme"); themeTab.setClosable(false); - themeTab.setContent(FXControls.builder() + themeTab.setContent(FXControls.vertical() .label("Please choose a theme:") .radioButton(ThemeSelection.values(), ThemeSelection::getDescription, themeSelection) .get()); Tab exportTab = new Tab("Export"); exportTab.setClosable(false); - exportTab.setContent(FXControls.builder() + exportTab.setContent(FXControls.vertical() .label("Please select the export type:") .radioButton(ExportOption.values(), ExportOption::getDescription, exportOption) .get()); Tab importTab = new Tab("Import"); importTab.setClosable(false); - importTab.setContent(FXControls.builder() + importTab.setContent(FXControls.vertical() .checkBox("Do you want to decode AppleSingle files on import?", importDecodeAppleSingle) .checkBox("Do you want to automatically tokenize .bas files as Applesoft?", importTokenizeApplesoft) .get()); diff --git a/app/gui-javafx/src/main/java/org/applecommander/javafx/wizard/CreateFileStoreWizard.java b/app/gui-javafx/src/main/java/org/applecommander/javafx/wizard/CreateFileStoreWizard.java index 5d3589a4..5cf99d1e 100644 --- a/app/gui-javafx/src/main/java/org/applecommander/javafx/wizard/CreateFileStoreWizard.java +++ b/app/gui-javafx/src/main/java/org/applecommander/javafx/wizard/CreateFileStoreWizard.java @@ -69,15 +69,15 @@ public class CreateFileStoreWizard extends WizardDialog createWizardPages() { return Map.of( - WizardPage.FILESTORE, FXControls.builder() + WizardPage.FILESTORE, FXControls.vertical() .label("Choose an image type to create:") .radioButton(FileStoreSelection.values(), FileStoreSelection::getText, fileStoreSelectionProperty) .get(), - WizardPage.SIZE, FXControls.builder() + WizardPage.SIZE, FXControls.vertical() .label("Choose an image size to create:") .radioButton(ImageSizeSelection.values(), ImageSizeSelection::getText, imageSizeSelectionProperty) .get(), - WizardPage.SECTOR, FXControls.builder() + WizardPage.SECTOR, FXControls.vertical() .label("Choose a sector ordering for this image:") .radioButton(SectorOrderSelection.values(), SectorOrderSelection::getText, sectorOrderSelectionProperty) .get(), diff --git a/app/gui-javafx/src/main/resources/images/AppleCommanderLogo.png b/app/gui-javafx/src/main/resources/images/AppleCommanderLogoLarge.png similarity index 100% rename from app/gui-javafx/src/main/resources/images/AppleCommanderLogo.png rename to app/gui-javafx/src/main/resources/images/AppleCommanderLogoLarge.png diff --git a/app/gui-javafx/src/main/resources/images/AppleCommanderLogoSmall.png b/app/gui-javafx/src/main/resources/images/AppleCommanderLogoSmall.png new file mode 100644 index 00000000..9acf5159 Binary files /dev/null and b/app/gui-javafx/src/main/resources/images/AppleCommanderLogoSmall.png differ diff --git a/app/gui-javafx/src/main/resources/images/about.png b/app/gui-javafx/src/main/resources/images/about.png new file mode 100644 index 00000000..c2e01838 Binary files /dev/null and b/app/gui-javafx/src/main/resources/images/about.png differ