diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java index 25439a934..25d77e61b 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/SceneBuilderApp.java @@ -228,7 +228,6 @@ public boolean canPerformControlAction(ApplicationControlAction a, DocumentWindo switch (a) { case ABOUT: case REGISTER: - case CHECK_UPDATES: case NEW_FILE: case NEW_TEMPLATE: case OPEN_FILE: @@ -238,6 +237,10 @@ public boolean canPerformControlAction(ApplicationControlAction a, DocumentWindo result = true; break; + case CHECK_UPDATES: + result = AppSettings.isUpdateAvailable(); + break; + case CLOSE_FRONT_WINDOW: result = windowList.isEmpty() == false; break; @@ -996,11 +999,13 @@ private void checkUpdates(DocumentWindowController source) { dialog.showAndWait(); }); } else { - SBAlert alert = new SBAlert(Alert.AlertType.INFORMATION, getFrontDocumentWindow().getStage()); - alert.setTitle(I18N.getString("check_for_updates.alert.up_to_date.title")); - alert.setHeaderText(I18N.getString("check_for_updates.alert.headertext")); - alert.setContentText(I18N.getString("check_for_updates.alert.up_to_date.message")); - alert.showAndWait(); + Platform.runLater(()->{ + SBAlert alert = new SBAlert(Alert.AlertType.INFORMATION, getFrontDocumentWindow().getStage()); + alert.setTitle(I18N.getString("check_for_updates.alert.up_to_date.title")); + alert.setHeaderText(I18N.getString("check_for_updates.alert.headertext")); + alert.setContentText(I18N.getString("check_for_updates.alert.up_to_date.message")); + alert.showAndWait(); + }); } } catch (NumberFormatException ex) { Platform.runLater(() -> showVersionNumberFormatError(source)); diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/menubar/MenuBarController.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/menubar/MenuBarController.java index 938c540a7..7be9b38b8 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/menubar/MenuBarController.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/menubar/MenuBarController.java @@ -40,6 +40,7 @@ import com.oracle.javafx.scenebuilder.app.i18n.I18N; import com.oracle.javafx.scenebuilder.app.preferences.PreferencesController; import com.oracle.javafx.scenebuilder.app.preferences.PreferencesRecordGlobal; +import com.oracle.javafx.scenebuilder.app.util.AppSettings; import com.oracle.javafx.scenebuilder.kit.editor.EditorController; import com.oracle.javafx.scenebuilder.kit.editor.EditorController.ControlAction; import com.oracle.javafx.scenebuilder.kit.editor.EditorController.EditAction; @@ -66,6 +67,7 @@ import java.util.TreeMap; import java.util.TreeSet; +import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.collections.ObservableList; import javafx.event.ActionEvent; @@ -81,6 +83,8 @@ import javafx.scene.control.RadioMenuItem; import javafx.scene.control.SeparatorMenuItem; import javafx.scene.effect.Effect; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; import javafx.scene.input.KeyCharacterCombination; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; @@ -1171,6 +1175,23 @@ public String getTitle() { insertMenu.setOnMenuValidation(onCustomPartOfInsertMenuValidationHandler); windowMenu.setOnMenuValidation(onWindowMenuValidationHandler); + + // Modify the Check Updates menu item if there is an update available. + // Icons by Font Awesome (https://fontawesome.com/license/free) under CC BY 4.0 License + AppSettings.getLatestVersion(latestVersion -> { + if (AppSettings.isUpdateAvailable()) { + Image icon = new Image(MenuBarController.class + .getResource("download_icon.png") + .toExternalForm()); + ImageView iconView = new ImageView(icon); + + Platform.runLater(() -> { + checkUpdatesMenuItem.setGraphic(iconView); + checkUpdatesMenuItem.disableProperty().setValue(false); + checkUpdatesMenuItem.setText(I18N.getString("menu.title.check.updates.available")); + }); + } + }); } private void addSwatchGraphic(RadioMenuItem swatchMenuItem) { diff --git a/app/src/main/java/com/oracle/javafx/scenebuilder/app/util/AppSettings.java b/app/src/main/java/com/oracle/javafx/scenebuilder/app/util/AppSettings.java index c22ec3f74..52cfd28bc 100644 --- a/app/src/main/java/com/oracle/javafx/scenebuilder/app/util/AppSettings.java +++ b/app/src/main/java/com/oracle/javafx/scenebuilder/app/util/AppSettings.java @@ -50,6 +50,8 @@ import java.net.URL; import java.util.Properties; import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; public class AppSettings { public static final String APP_ICON_16 = SceneBuilderApp.class.getResource("SceneBuilderLogo_16.png").toString(); @@ -64,9 +66,9 @@ public class AppSettings { private static String sceneBuilderVersion; private static String latestVersion; - private static String latestVersionText; private static String latestVersionAnnouncementURL; + private static boolean isUpdateAvailable = false; private static final JsonReaderFactory readerFactory = Json.createReaderFactory(null); @@ -82,7 +84,7 @@ private static void initSceneBuiderVersion() { sceneBuilderVersion = sbProps.getProperty("build.version", "UNSET"); } } catch (IOException ex) { - ex.printStackTrace(); + Logger.getLogger(AppSettings.class.getName()).log(Level.SEVERE, "Failed to load \"about.propertties\" resource.", ex); } } @@ -114,6 +116,10 @@ public static boolean isCurrentVersionLowerThan(String version) { return false; } + public static boolean isUpdateAvailable() { + return isUpdateAvailable; + } + public static void getLatestVersion(Consumer consumer) { if (latestVersion == null) { @@ -124,8 +130,8 @@ public static void getLatestVersion(Consumer consumer) { URL url = null; try { url = new URL(LATEST_VERSION_CHECK_URL); - } catch (MalformedURLException e) { - e.printStackTrace(); + } catch (MalformedURLException ex) { + Logger.getLogger(AppSettings.class.getName()).log(Level.SEVERE, "Failed to parse latest version check URL.", ex); } try (InputStream inputStream = url.openStream()) { @@ -133,9 +139,16 @@ public static void getLatestVersion(Consumer consumer) { onlineVersionNumber = prop.getProperty(LATEST_VERSION_NUMBER_PROPERTY); } catch (IOException ex) { - ex.printStackTrace(); + Logger.getLogger(AppSettings.class.getName()).log(Level.SEVERE, "Failed retrieve latest version information.", ex); } latestVersion = onlineVersionNumber; + + try { + isUpdateAvailable = isCurrentVersionLowerThan(latestVersion); + } catch (NumberFormatException ex) { + Logger.getLogger(AppSettings.class.getName()).log(Level.SEVERE, "Failed to load parse latest version number.", ex); + } + consumer.accept(latestVersion); }, "GetLatestVersion").start(); } else { @@ -159,11 +172,11 @@ private static void updateLatestVersionInfo() { JsonObject announcementObject = object.getJsonObject("announcement"); latestVersionText = announcementObject.getString("text"); latestVersionAnnouncementURL = announcementObject.getString("url"); - } catch (IOException e) { - e.printStackTrace(); + } catch (IOException ex) { + Logger.getLogger(AppSettings.class.getName()).log(Level.SEVERE, "Failed to retrieve or decode latest version information.", ex); } - } catch (MalformedURLException e) { - e.printStackTrace(); + } catch (MalformedURLException ex) { + Logger.getLogger(AppSettings.class.getName()).log(Level.SEVERE, "Failed to parse latest version information URL", ex); } } diff --git a/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp.properties b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp.properties index 63d80800d..52e0b1794 100644 --- a/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp.properties +++ b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/i18n/SceneBuilderApp.properties @@ -191,7 +191,9 @@ menu.title.no.window = No Window # Help menu items menu.title.scene.builder.help = Scene Builder Help menu.title.show.welcome = Show Welcome Page -menu.title.check.updates = Check for Update... +menu.title.check.updates.available = Update available +menu.title.check.updates.ok = No updates available + menu.title.about = About Scene Builder menu.title.register = Register... menu.title.help.javafx=JavaFX diff --git a/app/src/main/resources/com/oracle/javafx/scenebuilder/app/menubar/MenuBar.fxml b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/menubar/MenuBar.fxml index ad2b92255..5c8c45cfb 100644 --- a/app/src/main/resources/com/oracle/javafx/scenebuilder/app/menubar/MenuBar.fxml +++ b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/menubar/MenuBar.fxml @@ -41,6 +41,9 @@ + + + @@ -313,7 +316,15 @@ - + + + + + + + + + diff --git a/app/src/main/resources/com/oracle/javafx/scenebuilder/app/menubar/check_icon.png b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/menubar/check_icon.png new file mode 100644 index 000000000..4b08e4566 Binary files /dev/null and b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/menubar/check_icon.png differ diff --git a/app/src/main/resources/com/oracle/javafx/scenebuilder/app/menubar/download_icon.png b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/menubar/download_icon.png new file mode 100644 index 000000000..4ad419d77 Binary files /dev/null and b/app/src/main/resources/com/oracle/javafx/scenebuilder/app/menubar/download_icon.png differ