Refactor SimulationPlotDialog to generic PlotDialog

This commit is contained in:
SiboVG 2024-08-20 18:09:01 +02:00
parent f21dd55452
commit 3e00f31558
2 changed files with 231 additions and 170 deletions

View File

@ -0,0 +1,188 @@
package info.openrocket.swing.gui.plot;
import info.openrocket.core.l10n.Translator;
import info.openrocket.core.preferences.ApplicationPreferences;
import info.openrocket.core.simulation.DataBranch;
import info.openrocket.core.simulation.DataType;
import info.openrocket.core.startup.Application;
import info.openrocket.swing.gui.components.StyledLabel;
import info.openrocket.swing.gui.util.FileHelper;
import info.openrocket.swing.gui.util.GUIUtil;
import info.openrocket.swing.gui.util.Icons;
import info.openrocket.swing.gui.util.SwingPreferences;
import info.openrocket.swing.gui.widgets.SaveFileChooser;
import net.miginfocom.swing.MigLayout;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartUtils;
import org.jfree.chart.JFreeChart;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.io.File;
import java.util.List;
public abstract class PlotDialog<T extends DataType, B extends DataBranch<T>, C extends PlotConfiguration<T, B>,
P extends Plot<T, B, C>> extends JDialog {
protected static final Translator trans = Application.getTranslator();
public PlotDialog(Window parent, String name, P plot, C config, List<B> allBranches, boolean initialShowPoints) {
super(parent, name);
this.setModalityType(ModalityType.DOCUMENT_MODAL);
// Create the dialog
JPanel panel = new JPanel(new MigLayout("fill, hidemode 3","[]","[grow][]"));
this.add(panel);
final ChartPanel chartPanel = new SimulationChart(plot.getJFreeChart());
final JFreeChart jChart = plot.getJFreeChart();
panel.add(chartPanel, "grow, wrap 20lp");
// Ensures normal aspect-ratio of chart elements when resizing the panel
chartPanel.setMinimumDrawWidth(0);
chartPanel.setMaximumDrawWidth(Integer.MAX_VALUE);
chartPanel.setMinimumDrawHeight(0);
chartPanel.setMaximumDrawHeight(Integer.MAX_VALUE);
//// Description text
JLabel label = new StyledLabel(trans.get("PlotDialog.lbl.Chart"), -2);
panel.add(label, "wrap");
// Add X axis warnings
addXAxisWarningMessage(panel, config);
//// Show data points
final JCheckBox checkData = new JCheckBox(trans.get("PlotDialog.CheckBox.Showdatapoints"));
checkData.setSelected(initialShowPoints);
checkData.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
boolean show = checkData.isSelected();
Application.getPreferences().putBoolean(ApplicationPreferences.PLOT_SHOW_POINTS, show);
plot.setShowPoints(show);
}
});
panel.add(checkData, "split, left");
// Show errors checkbox
showErrorsCheckbox(panel, plot);
// Add series selection box
addSeriesSelectionBox(panel, plot, allBranches);
//// Zoom in button
JButton button = new JButton(Icons.ZOOM_IN);
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if ((e.getModifiers() & InputEvent.ALT_MASK) == InputEvent.ALT_MASK) {
chartPanel.actionPerformed(new ActionEvent(chartPanel, ActionEvent.ACTION_FIRST, ChartPanel.ZOOM_IN_DOMAIN_COMMAND));
} else {
chartPanel.actionPerformed(new ActionEvent(chartPanel, ActionEvent.ACTION_FIRST, ChartPanel.ZOOM_IN_BOTH_COMMAND));
}
}
});
panel.add(button, "gapleft rel");
//// Reset Zoom button.
button = new JButton(Icons.ZOOM_RESET);
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
chartPanel.actionPerformed(new ActionEvent(chartPanel, ActionEvent.ACTION_FIRST, ChartPanel.ZOOM_RESET_BOTH_COMMAND));
}
});
panel.add(button, "gapleft rel");
//// Zoom out button
button = new JButton(Icons.ZOOM_OUT);
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if ((e.getModifiers() & InputEvent.ALT_MASK) == InputEvent.ALT_MASK) {
chartPanel.actionPerformed(new ActionEvent(chartPanel, ActionEvent.ACTION_FIRST, ChartPanel.ZOOM_OUT_DOMAIN_COMMAND));
} else {
chartPanel.actionPerformed(new ActionEvent(chartPanel, ActionEvent.ACTION_FIRST, ChartPanel.ZOOM_OUT_BOTH_COMMAND));
}
}
});
panel.add(button, "gapleft rel");
//// Print chart button
button = new JButton(trans.get("PlotDialog.btn.exportImage"));
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
doPNGExport(chartPanel,jChart);
}
});
panel.add(button, "gapleft rel");
//// Close button
button = new JButton(trans.get("dlg.but.close"));
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
PlotDialog.this.dispose();
}
});
panel.add(button, "gapbefore push, right");
this.setLocationByPlatform(true);
this.pack();
GUIUtil.setDisposableDialogOptions(this, button);
GUIUtil.rememberWindowSize(this);
this.setLocationByPlatform(true);
GUIUtil.rememberWindowPosition(this);
}
protected void addXAxisWarningMessage(JPanel panel, C config) {
// Default implementation does nothing
}
protected void showErrorsCheckbox(JPanel panel, P plot) {
// Default implementation does nothing
}
protected void addSeriesSelectionBox(JPanel panel, P plot, List<B> allBranches) {
// Default implementation does nothing
}
private boolean doPNGExport(ChartPanel chartPanel, JFreeChart chart){
JFileChooser chooser = new SaveFileChooser();
chooser.setAcceptAllFileFilterUsed(false);
chooser.setFileFilter(FileHelper.PNG_FILTER);
chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory());
//// Ensures No Problems When Choosing File
if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION)
return false;
File file = chooser.getSelectedFile();
if (file == null)
return false;
file = FileHelper.forceExtension(file, "png");
if (!FileHelper.confirmWrite(file, this)) {
return false;
}
//// Uses JFreeChart Built In PNG Export Method
try{
ChartUtils.saveChartAsPNG(file, chart, chartPanel.getWidth(), chartPanel.getHeight());
} catch(Exception e){
return false;
}
return true;
}
}

View File

@ -4,126 +4,108 @@ import java.awt.Color;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import info.openrocket.core.document.Simulation;
import info.openrocket.core.l10n.Translator;
import info.openrocket.core.preferences.ApplicationPreferences;
import info.openrocket.core.simulation.FlightDataBranch;
import info.openrocket.core.simulation.FlightDataType;
import info.openrocket.core.startup.Application;
import net.miginfocom.swing.MigLayout;
import info.openrocket.swing.gui.components.StyledLabel;
import info.openrocket.swing.gui.util.FileHelper;
import info.openrocket.swing.gui.util.GUIUtil;
import info.openrocket.swing.gui.util.Icons;
import info.openrocket.swing.gui.util.SwingPreferences;
import info.openrocket.swing.gui.theme.UITheme;
import info.openrocket.swing.gui.widgets.SaveFileChooser;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartUtils;
import org.jfree.chart.JFreeChart;
/**
* Dialog that shows a plot of a simulation results based on user options.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class SimulationPlotDialog extends JDialog {
public class SimulationPlotDialog extends PlotDialog<FlightDataType, FlightDataBranch, SimulationPlotConfiguration,
SimulationPlot> {
private static final Translator trans = Application.getTranslator();
private final Simulation simulation;
private static Color darkErrorColor;
private final JCheckBox checkErrors;
private JCheckBox checkErrors;
static {
initColors();
}
private SimulationPlotDialog(Window parent, Simulation simulation, SimulationPlotConfiguration config) {
//// Flight data plot
super(parent, simulation.getName());
this.setModalityType(ModalityType.DOCUMENT_MODAL);
private SimulationPlotDialog(Window parent, Simulation simulation, SimulationPlotConfiguration config,
SimulationPlot plot, boolean initialShowPoints) {
super(parent, simulation.getName(), plot, config, simulation.getSimulatedData().getBranches(), initialShowPoints);
this.simulation = simulation;
this.checkErrors.setVisible(this.simulation.hasErrors());
}
/**
* Static method that shows a plot with the specified parameters.
*
* @param parent the parent window, which will be blocked.
* @param simulation the simulation to plot.
* @param config the configuration of the plot.
*/
public static SimulationPlotDialog getPlot(Window parent, Simulation simulation, SimulationPlotConfiguration config) {
final boolean initialShowPoints = Application.getPreferences().getBoolean(ApplicationPreferences.PLOT_SHOW_POINTS, false);
final SimulationPlot myPlot = SimulationPlot.create(simulation, config, initialShowPoints);
// Create the dialog
JPanel panel = new JPanel(new MigLayout("fill, hidemode 3","[]","[grow][]"));
this.add(panel);
final ChartPanel chartPanel = new SimulationChart(myPlot.getJFreeChart());
final JFreeChart jChart = myPlot.getJFreeChart();
panel.add(chartPanel, "grow, wrap 20lp");
final SimulationPlot plot = SimulationPlot.create(simulation, config, initialShowPoints);
// Ensures normal aspect-ratio of chart elements when resizing the panel
chartPanel.setMinimumDrawWidth(0);
chartPanel.setMaximumDrawWidth(Integer.MAX_VALUE);
chartPanel.setMinimumDrawHeight(0);
chartPanel.setMaximumDrawHeight(Integer.MAX_VALUE);
//// Description text
JLabel label = new StyledLabel(trans.get("PlotDialog.lbl.Chart"), -2);
panel.add(label, "wrap");
return new SimulationPlotDialog(parent, simulation, config, plot, initialShowPoints);
}
@Override
protected void addXAxisWarningMessage(JPanel panel, SimulationPlotConfiguration config) {
// Add warning if X axis type is not time
if (config.getDomainAxisType() != FlightDataType.TYPE_TIME) {
JLabel msg = new StyledLabel(trans.get("PlotDialog.lbl.timeSeriesWarning"), -2);
msg.setForeground(darkErrorColor);
panel.add(msg, "wrap");
}
//// Show data points
final JCheckBox checkData = new JCheckBox(trans.get("PlotDialog.CheckBox.Showdatapoints"));
checkData.setSelected(initialShowPoints);
checkData.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
boolean show = checkData.isSelected();
Application.getPreferences().putBoolean(ApplicationPreferences.PLOT_SHOW_POINTS, show);
myPlot.setShowPoints(show);
}
});
panel.add(checkData, "split, left");
}
@Override
protected void showErrorsCheckbox(JPanel panel, SimulationPlot plot) {
//// Show errors if any
//// Always enable 'show errors' initially; make user turn it off for themselves
checkErrors = new JCheckBox(trans.get("PlotDialog.CheckBox.ShowErrors"));
checkErrors.setSelected(true);
checkErrors.addActionListener(new ActionListener() {
this.checkErrors = new JCheckBox(trans.get("PlotDialog.CheckBox.ShowErrors"));
this.checkErrors.setSelected(true);
this.checkErrors.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
myPlot.setShowErrors(checkErrors.isSelected());
plot.setShowErrors(checkErrors.isSelected());
}
});
panel.add(checkErrors, "split, left");
checkErrors.setVisible(simulation.hasErrors());
panel.add(this.checkErrors, "split, left");
}
//// Add series selection box
@Override
protected void addSeriesSelectionBox(JPanel panel, SimulationPlot plot, List<FlightDataBranch> allBranches) {
ArrayList<String> stages = new ArrayList<>();
stages.add(trans.get("PlotDialog.StageDropDown.allStages"));
stages.addAll(Util.generateSeriesLabels(simulation.getSimulatedData().getBranches()));
stages.addAll(Util.generateSeriesLabels(allBranches));
final JComboBox<String> stageSelection = new JComboBox<>(stages.toArray(new String[0]));
stageSelection.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (simulation == null) {
return;
}
int selectedStage = stageSelection.getSelectedIndex() - 1;
checkErrors.setEnabled(selectedStage == -1 ? simulation.hasErrors() : simulation.hasErrors(selectedStage));
myPlot.setShowBranch(selectedStage);
plot.setShowBranch(selectedStage);
}
});
@ -131,73 +113,6 @@ public class SimulationPlotDialog extends JDialog {
// Only show the combo box if there are at least 3 entries (ie, "All", "Main", and one other one)
panel.add(stageSelection, "gapleft rel");
}
//// Zoom in button
JButton button = new JButton(Icons.ZOOM_IN);
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if ((e.getModifiers() & InputEvent.ALT_MASK) == InputEvent.ALT_MASK) {
chartPanel.actionPerformed(new ActionEvent(chartPanel, ActionEvent.ACTION_FIRST, ChartPanel.ZOOM_IN_DOMAIN_COMMAND));
} else {
chartPanel.actionPerformed(new ActionEvent(chartPanel, ActionEvent.ACTION_FIRST, ChartPanel.ZOOM_IN_BOTH_COMMAND));
}
}
});
panel.add(button, "gapleft rel");
//// Reset Zoom button.
button = new JButton(Icons.ZOOM_RESET);
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
chartPanel.actionPerformed(new ActionEvent(chartPanel, ActionEvent.ACTION_FIRST, ChartPanel.ZOOM_RESET_BOTH_COMMAND));
}
});
panel.add(button, "gapleft rel");
//// Zoom out button
button = new JButton(Icons.ZOOM_OUT);
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if ((e.getModifiers() & InputEvent.ALT_MASK) == InputEvent.ALT_MASK) {
chartPanel.actionPerformed(new ActionEvent(chartPanel, ActionEvent.ACTION_FIRST, ChartPanel.ZOOM_OUT_DOMAIN_COMMAND));
} else {
chartPanel.actionPerformed(new ActionEvent(chartPanel, ActionEvent.ACTION_FIRST, ChartPanel.ZOOM_OUT_BOTH_COMMAND));
}
}
});
panel.add(button, "gapleft rel");
//// Print chart button
button = new JButton(trans.get("PlotDialog.btn.exportImage"));
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
doPNGExport(chartPanel,jChart);
}
});
panel.add(button, "gapleft rel");
//// Close button
button = new JButton(trans.get("dlg.but.close"));
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
SimulationPlotDialog.this.dispose();
}
});
panel.add(button, "gapbefore push, right");
this.setLocationByPlatform(true);
this.pack();
GUIUtil.setDisposableDialogOptions(this, button);
GUIUtil.rememberWindowSize(this);
this.setLocationByPlatform(true);
GUIUtil.rememberWindowPosition(this);
}
private static void initColors() {
@ -208,46 +123,4 @@ public class SimulationPlotDialog extends JDialog {
private static void updateColors() {
darkErrorColor = GUIUtil.getUITheme().getDarkErrorColor();
}
private boolean doPNGExport(ChartPanel chartPanel, JFreeChart chart){
JFileChooser chooser = new SaveFileChooser();
chooser.setAcceptAllFileFilterUsed(false);
chooser.setFileFilter(FileHelper.PNG_FILTER);
chooser.setCurrentDirectory(((SwingPreferences) Application.getPreferences()).getDefaultDirectory());
//// Ensures No Problems When Choosing File
if (chooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION)
return false;
File file = chooser.getSelectedFile();
if (file == null)
return false;
file = FileHelper.forceExtension(file, "png");
if (!FileHelper.confirmWrite(file, this)) {
return false;
}
//// Uses JFreeChart Built In PNG Export Method
try{
ChartUtils.saveChartAsPNG(file, chart, chartPanel.getWidth(), chartPanel.getHeight());
} catch(Exception e){
return false;
}
return true;
}
/**
* Static method that shows a plot with the specified parameters.
*
* @param parent the parent window, which will be blocked.
* @param simulation the simulation to plot.
* @param config the configuration of the plot.
*/
public static SimulationPlotDialog getPlot(Window parent, Simulation simulation, SimulationPlotConfiguration config) {
return new SimulationPlotDialog(parent, simulation, config);
}
}