[#1768] Add a welcome dialog at start-up to show the release notes

This commit is contained in:
SiboVG 2022-11-29 23:53:34 +01:00
parent 7ce87f099e
commit faa7e43891
8 changed files with 286 additions and 14 deletions

View File

@ -356,6 +356,13 @@ generalprefs.lbl.language = Interface language
generalprefs.languages.default = System default
generalprefs.lbl.languageEffect = The language will change the next time you start OpenRocket.
! Welcome dialog
welcome.dlg.title = Welcome to OpenRocket
welcome.dlg.lbl.thankYou = Thank you for downloading OpenRocket
welcome.dlg.lbl.seeReleaseNotes = See the release notes below to find what's new
welcome.dlg.checkbox.dontShowAgain = Don't show this dialog again
welcome.dlg.btn.close.ttip = Cool! Now leave me alone\u2026
! Software update checker
update.dlg.error.title = Unable to retrieve update information
update.dlg.error = An error occurred while communicating with the server.

View File

@ -0,0 +1,56 @@
package net.sf.openrocket.communication;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.file.simplesax.AbstractElementHandler;
import net.sf.openrocket.file.simplesax.ElementHandler;
import net.sf.openrocket.file.simplesax.PlainTextHandler;
import org.xml.sax.SAXException;
import java.util.HashMap;
import java.util.Objects;
/**
* Class that parses ReleaseNotes.md.
* <p>
* Releases are stored in an HTML <div> object with as id attribute the release version.
* E.g. <div id="15.03">...</div>
* The content of the div is the release notes for that version.
*/
public class ReleaseNotesHandler extends AbstractElementHandler {
private final String buildVersion;
private String releaseNotes = null;
/**
* @param buildVersion the build version to search the release notes for (e.g. "22.02")
*/
public ReleaseNotesHandler(String buildVersion) {
this.buildVersion = buildVersion;
}
@Override
public ElementHandler openElement(String element, HashMap<String, String> attributes, WarningSet warnings) throws SAXException {
if (element.equals("body")) { // The release notes are encapsulated in a root <body> tag (required for XML parsing)
return this;
}
if (element.equals("div") && Objects.equals(attributes.get("id"), this.buildVersion)) {
return PlainTextHandler.INSTANCE;
}
return null;
}
@Override
public void closeElement(String element, HashMap<String, String> attributes, String content, WarningSet warnings) throws SAXException {
super.closeElement(element, attributes, content, warnings);
if (element.equals("div")) {
this.releaseNotes = content.trim();
}
}
/**
* @return the release notes for the build version
*/
public String getReleaseNotes() {
return releaseNotes;
}
}

View File

@ -0,0 +1,34 @@
package net.sf.openrocket.communication;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.file.simplesax.SimpleSAX;
import net.sf.openrocket.util.BuildProperties;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public abstract class WelcomeInfoRetriever {
/**
* Retrieves the release notes of the current build version from the ReleaseNotes.md file.
* @return the release notes of the current build version
* @throws IOException if the file could not be read
*/
public static String retrieveWelcomeInfo() throws IOException {
InputStream in = new FileInputStream("ReleaseNotes.md");
InputSource source = new InputSource(new InputStreamReader(in));
ReleaseNotesHandler handler = new ReleaseNotesHandler(BuildProperties.getVersion());
WarningSet warnings = new WarningSet();
try {
SimpleSAX.readXML(source, handler, warnings);
return handler.getReleaseNotes();
} catch (SAXException e) {
throw new IOException(e.getMessage(), e);
}
}
}

View File

@ -55,10 +55,12 @@ public abstract class Preferences implements ChangeSource {
public static final String USER_LOCAL = "locale";
public static final String PLOT_SHOW_POINTS = "ShowPlotPoints";
private static final String IGNORE_WELCOME = "IgnoreWelcome";
private static final String CHECK_UPDATES = "CheckUpdates";
private static final String IGNORE_VERSIONS = "IgnoreVersions";
private static final String IGNORE_UPDATE_VERSIONS = "IgnoreUpdateVersions";
private static final String CHECK_BETA_UPDATES = "CheckBetaUpdates";
public static final String MOTOR_DIAMETER_FILTER = "MotorDiameterMatch";
@ -141,9 +143,31 @@ public abstract class Preferences implements ChangeSource {
public abstract void putString(String directory, String key, String value);
public abstract java.util.prefs.Preferences getNode(String nodeName);
/*
* ******************************************************************************************
* Welcome dialog
*/
/**
* Sets to ignore opening the welcome dialog for the supplied OpenRocket build version.
* @param version build version to ignore opening the welcome dialog for (e.g. "22.02")
* @param ignore true to ignore, false to show the welcome dialog
*/
public final void setIgnoreWelcome(String version, boolean ignore) {
this.putBoolean(IGNORE_WELCOME + "_" + version, ignore);
}
/**
* Returns whether to ignore opening the welcome dialog for the supplied OpenRocket build version.
* @param version build version (e.g. "22.02")
* @return true if no welcome dialog should be opened for the supplied version
*/
public final boolean getIgnoreWelcome(String version) {
return this.getBoolean(IGNORE_WELCOME + "_" + version, false);
}
/*
* Software updater
*/
public final boolean getCheckUpdates() {
return this.getBoolean(CHECK_UPDATES, BuildProperties.getDefaultCheckUpdates());
@ -153,12 +177,12 @@ public abstract class Preferences implements ChangeSource {
this.putBoolean(CHECK_UPDATES, check);
}
public final List<String> getIgnoreVersions() {
return List.of(this.getString(IGNORE_VERSIONS, "").split("\n"));
public final List<String> getIgnoreUpdateVersions() {
return List.of(this.getString(IGNORE_UPDATE_VERSIONS, "").split("\n"));
}
public final void setIgnoreVersions(List<String> versions) {
this.putString(IGNORE_VERSIONS, String.join("\n", versions));
public final void setIgnoreUpdateVersions(List<String> versions) {
this.putString(IGNORE_UPDATE_VERSIONS, String.join("\n", versions));
}
public final boolean getCheckBetaUpdates() {
@ -168,6 +192,11 @@ public abstract class Preferences implements ChangeSource {
public final void setCheckBetaUpdates(boolean check) {
this.putBoolean(CHECK_BETA_UPDATES, check);
}
/*
* ******************************************************************************************
*/
public final boolean getConfirmSimDeletion() {
return this.getBoolean(CONFIRM_DELETE_SIMULATION, true);

View File

@ -30,7 +30,6 @@ import net.sf.openrocket.communication.AssetHandler.UpdatePlatform;
import net.sf.openrocket.communication.ReleaseInfo;
import net.sf.openrocket.communication.UpdateInfo;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.configdialog.CommonStrings;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.Icons;
import net.sf.openrocket.gui.util.SwingPreferences;
@ -58,6 +57,7 @@ public class UpdateInfoDialog extends JDialog {
JPanel panel = new JPanel(new MigLayout("insets n n 8px n, fill"));
// OpenRocket logo on the left
panel.add(new JLabel(Icons.getScaledIcon(Icons.loadImageIcon("pix/icon/icon-about.png", "OpenRocket"), 0.6)),
"spany, top, gapright 20px, cell 0 0");
@ -138,11 +138,11 @@ public class UpdateInfoDialog extends JDialog {
btnSkip.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
List<String> ignoreVersions = new ArrayList<>(preferences.getIgnoreVersions());
List<String> ignoreVersions = new ArrayList<>(preferences.getIgnoreUpdateVersions());
String ignore = release.getReleaseName();
if (!ignoreVersions.contains(ignore)) {
ignoreVersions.add(ignore);
preferences.setIgnoreVersions(ignoreVersions);
preferences.setIgnoreUpdateVersions(ignoreVersions);
}
UpdateInfoDialog.this.dispose();
}

View File

@ -0,0 +1,115 @@
package net.sf.openrocket.gui.dialogs;
import net.miginfocom.swing.MigLayout;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.gui.util.Icons;
import net.sf.openrocket.gui.widgets.SelectColorButton;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.BuildProperties;
import net.sf.openrocket.util.MarkdownUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* Dialog that opens when you start up OpenRocket. It will display the release notes of the current version.
*/
public class WelcomeDialog extends JDialog {
private static final Translator trans = Application.getTranslator();
private static final Logger log = LoggerFactory.getLogger(WelcomeDialog.class);
/**
* @param releaseNotes the release notes to display for the current version
*/
public WelcomeDialog(String releaseNotes) {
super(null, trans.get("welcome.dlg.title"), ModalityType.APPLICATION_MODAL);
JPanel panel = new JPanel(new MigLayout("insets n n 8px n, fill"));
// OpenRocket logo on the left
panel.add(new JLabel(Icons.getScaledIcon(Icons.loadImageIcon("pix/icon/icon-about.png", "OpenRocket"), 0.6)),
"spany, top, gapright 20px, cell 0 0");
// Thank you for downloading!
panel.add(new StyledLabel(trans.get("welcome.dlg.lbl.thankYou") + " " + BuildProperties.getVersion() + "!",
2, StyledLabel.Style.BOLD), "spanx, wrap");
// See the release notes below for what's new
panel.add(new StyledLabel(trans.get("welcome.dlg.lbl.seeReleaseNotes"),
1, StyledLabel.Style.PLAIN), "skip 1, spanx, wrap para");
// Release notes
final JTextPane textPane = new JTextPane();
textPane.setEditable(false);
textPane.setContentType("text/html");
textPane.setMargin(new Insets(10, 10, 10, 10));
textPane.putClientProperty(JTextPane.HONOR_DISPLAY_PROPERTIES, true);
String sb = "<html>" +
MarkdownUtil.toHtml(releaseNotes) + "<br><br>" +
"</html>";
textPane.addHyperlinkListener(new HyperlinkListener() {
@Override
public void hyperlinkUpdate(HyperlinkEvent e) {
if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) {
Desktop desktop = Desktop.getDesktop();
try {
desktop.browse(e.getURL().toURI());
} catch (Exception ex) {
log.warn("Exception hyperlink: " + ex.getMessage());
}
}
}
});
textPane.setText(sb);
textPane.setCaretPosition(0); // Scroll to the top
panel.add(new JScrollPane(textPane), "skip 1, left, spanx, grow, push, gapbottom 6px, wrap");
// Don't show this dialog again
JCheckBox dontShowAgain = new JCheckBox(trans.get("welcome.dlg.checkbox.dontShowAgain"));
dontShowAgain.setSelected(Application.getPreferences().getIgnoreWelcome(BuildProperties.getVersion())); // Normally this should never be true, but just in case
panel.add(dontShowAgain, "skip 1");
dontShowAgain.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Application.getPreferences().setIgnoreWelcome(BuildProperties.getVersion(), dontShowAgain.isSelected());
}
});
// Close button
JButton closeBtn = new SelectColorButton(trans.get("button.close"));
closeBtn.setToolTipText(trans.get("welcome.dlg.btn.close.ttip"));
closeBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
WelcomeDialog.this.dispose();
}
});
panel.add(closeBtn, "pushx, right, wrap");
panel.setPreferredSize(new Dimension(800, 600));
this.add(panel);
this.pack();
this.setLocationRelativeTo(null);
GUIUtil.setDisposableDialogOptions(this, closeBtn);
}
}

View File

@ -342,7 +342,7 @@ public class GeneralPreferencesPanel extends PreferencesPanel {
ReleaseInfo release = info.getLatestRelease();
// Do nothing if the release is part of the ignore versions
if (preferences.getIgnoreVersions().contains(release.getReleaseName())) {
if (preferences.getIgnoreUpdateVersions().contains(release.getReleaseName())) {
return;
}

View File

@ -4,6 +4,7 @@ import java.awt.GraphicsEnvironment;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.stream.IntStream;
@ -19,10 +20,11 @@ import net.sf.openrocket.arch.SystemInfo.Platform;
import net.sf.openrocket.communication.UpdateInfo;
import net.sf.openrocket.communication.UpdateInfoRetriever;
import net.sf.openrocket.communication.UpdateInfoRetriever.ReleaseStatus;
import net.sf.openrocket.communication.WelcomeInfoRetriever;
import net.sf.openrocket.database.Databases;
import net.sf.openrocket.gui.dialogs.UpdateInfoDialog;
import net.sf.openrocket.gui.dialogs.WelcomeDialog;
import net.sf.openrocket.gui.main.BasicFrame;
import net.sf.openrocket.gui.main.MRUDesignFile;
import net.sf.openrocket.gui.main.Splash;
import net.sf.openrocket.gui.main.SwingExceptionHandler;
import net.sf.openrocket.gui.util.GUIUtil;
@ -217,6 +219,7 @@ public class SwingStartup {
if (!handleCommandLine(args)) {
BasicFrame startupFrame = BasicFrame.reopen();
BasicFrame.setStartupFrame(startupFrame);
showWelcomeDialog();
}
// Check whether update info has been fetched or whether it needs more time
@ -277,7 +280,7 @@ public class SwingStartup {
// Only display something when an update is found
if (info != null && info.getException() == null && info.getReleaseStatus() == ReleaseStatus.OLDER &&
!preferences.getIgnoreVersions().contains(info.getLatestRelease().getReleaseName())) {
!preferences.getIgnoreUpdateVersions().contains(info.getLatestRelease().getReleaseName())) {
UpdateInfoDialog infoDialog = new UpdateInfoDialog(info);
infoDialog.setVisible(true);
}
@ -290,6 +293,34 @@ public class SwingStartup {
timer.addActionListener(listener);
timer.start();
}
/**
* Shows a welcome dialog displaying the release notes for this build version.
*/
public static void showWelcomeDialog() {
// Don't show if this build version is ignored
if (Application.getPreferences().getIgnoreWelcome(BuildProperties.getVersion())) {
log.debug("Welcome dialog ignored");
return;
}
// Fetch this version's release notes
String releaseNotes;
try {
releaseNotes = WelcomeInfoRetriever.retrieveWelcomeInfo();
} catch (IOException e) {
log.error("Error retrieving welcome info", e);
return;
}
if (releaseNotes == null) {
log.debug("No release notes found");
return;
}
// Show the dialog
WelcomeDialog dialog = new WelcomeDialog(releaseNotes);
dialog.setVisible(true);
}
/**
* Handles arguments passed from the command line. This may be used either