From d6eb4ddcbfa34f7356708ad6d34b80945ed65dae Mon Sep 17 00:00:00 2001 From: Justin Kunimune Date: Mon, 22 Jan 2018 09:27:51 -1000 Subject: [PATCH] Don't push that button! I improved my button handling so that the proper buttons grey out whenever things are happening. I ended up taking a page from JavaFX's book and having a map from an enum to stored buttons. I also made the plot saving thing work, since that seems to have broken at some point, and improved some documentation. --- src/apps/MapAnalyzer.java | 62 ++++++++++++++++------------ src/apps/MapApplication.java | 72 ++++++++++++++++++++------------- src/apps/MapDesignerRaster.java | 42 +++++++++---------- src/apps/MapDesignerVector.java | 40 ++++++++++-------- src/maps/Lenticular.java | 2 +- src/utils/Dixon.java | 4 +- 6 files changed, 129 insertions(+), 93 deletions(-) diff --git a/src/apps/MapAnalyzer.java b/src/apps/MapAnalyzer.java index 9fb24d9..e3adb7a 100644 --- a/src/apps/MapAnalyzer.java +++ b/src/apps/MapAnalyzer.java @@ -33,13 +33,11 @@ import javafx.application.Platform; import javafx.embed.swing.SwingFXUtils; import javafx.geometry.Insets; import javafx.geometry.Pos; -import javafx.scene.Node; import javafx.scene.chart.BarChart; import javafx.scene.chart.CategoryAxis; import javafx.scene.chart.NumberAxis; import javafx.scene.chart.XYChart.Data; import javafx.scene.chart.XYChart.Series; -import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.Separator; import javafx.scene.image.Image; @@ -86,7 +84,6 @@ public class MapAnalyzer extends MapApplication { private Flag cropAtIDL; private MutableDouble graticuleSpacing; - private Button updateBtn; private Text avgSizeDistort, avgShapeDistort; private ImageView mapDisplay; private Region charts; @@ -110,18 +107,17 @@ public class MapAnalyzer extends MapApplication { @Override - protected Node makeWidgets() { + protected Region makeWidgets() { this.cropAtIDL = new Flag(true); this.graticuleSpacing = new MutableDouble(15); - final Node projectionSelector = buildProjectionSelector(Procedure.NONE); - final Node parameterSelector = buildParameterSelector(Procedure.NONE); - final Node optionPane = buildOptionPane(cropAtIDL, graticuleSpacing); - final Node textDisplay = buildTextDisplay(); - this.updateBtn = buildUpdateButton(this::calculateAndUpdate); - this.updateBtn.setText("Calculate"); //I don't need to follow your darn conventions! - final Button saveMapBtn = buildSaveButton(true, "map", RASTER_TYPES, + final Region projectionSelector = buildProjectionSelector(Procedure.NONE); + final Region parameterSelector = buildParameterSelector(Procedure.NONE); + final Region optionPane = buildOptionPane(cropAtIDL, graticuleSpacing); + final Region textDisplay = buildTextDisplay(); + final Region updateBtn = buildUpdateButton("Calculate", this::calculateAndUpdate); + final Region saveMapBtn = buildSaveButton(true, "map", RASTER_TYPES, RASTER_TYPES[0], ()->true, this::calculateAndSaveMap); - final Button savePltBtn = buildSaveButton(true, "plots", RASTER_TYPES, + final Region savePltBtn = buildSaveButton(true, "plots", RASTER_TYPES, RASTER_TYPES[0], ()->true, this::calculateAndSavePlot); final HBox buttons = new HBox(H_SPACE, updateBtn, saveMapBtn, savePltBtn); buttons.setAlignment(Pos.CENTER); @@ -149,7 +145,7 @@ public class MapAnalyzer extends MapApplication { } - private Node buildTextDisplay() { + private Region buildTextDisplay() { this.avgSizeDistort = new Text("..."); this.avgShapeDistort = new Text("..."); final Text txt = new Text("Blue areas are dilated, red areas are compressed, and black areas are stretched."); @@ -188,6 +184,8 @@ public class MapAnalyzer extends MapApplication { private void calculateAndUpdate() { + disable(ButtonType.UPDATE_MAP, ButtonType.SAVE_MAP, ButtonType.SAVE_GRAPH); + Platform.runLater(() -> { sizeChart.getData().clear(); shapeChart.getData().clear(); @@ -202,6 +200,8 @@ public class MapAnalyzer extends MapApplication { mapDisplay.setImage(makeGraphic(distortionM)); + enable(ButtonType.SAVE_MAP); + final double[][][] distortionG = proj.calculateDistortion(Projection.globe(GLOBE_RES)); Platform.runLater(() -> { @@ -212,28 +212,38 @@ public class MapAnalyzer extends MapApplication { avgSizeDistort.setText(format(Math2.stdDev(distortionG[0]))); avgShapeDistort.setText(format(Math2.mean(distortionG[1]))); + + enable(ButtonType.UPDATE_MAP, ButtonType.SAVE_GRAPH); }); } private void calculateAndSavePlot(File file, ProgressBarDialog pBar) { + disable(ButtonType.SAVE_GRAPH); + pBar.setProgress(-1); - final String filename = file.getName(); - final String extension = filename.substring(filename.lastIndexOf('.')+1); - try { - final WritableImage out = new WritableImage( - (int) charts.getWidth(), (int) charts.getHeight()); - Platform.runLater(() -> charts.snapshot(null,out)); - while (out.getProgress() < 1) {} - ImageIO.write(SwingFXUtils.fromFXImage(out, null), extension, file); //save - } catch (IOException e) { - showError("Failure!", - "Could not access "+file.getAbsolutePath()+". It's possible that another program has it open."); - } + Platform.runLater(() -> { + charts.snapshot((snapRes) -> { + final String filename = file.getName(); + final String extension = filename.substring(filename.lastIndexOf('.')+1); + try { + ImageIO.write(SwingFXUtils.fromFXImage(snapRes.getImage(), null), + extension, file); + } catch (IOException e) { + showError("Failure!", + "Could not access "+file.getAbsolutePath()+". It's possible that another program has it open."); + } finally { + enable(ButtonType.SAVE_GRAPH); + } + return null; + }, null, null); + }); } private void calculateAndSaveMap(File file, ProgressBarDialog pBar) { + disable(ButtonType.SAVE_MAP); + loadParameters(); final Projection proj = this.getProjection(); final double[][][] distortion = proj.calculateDistortion(proj.map(FINE_SAMP_NUM), pBar); //calculate @@ -248,6 +258,8 @@ public class MapAnalyzer extends MapApplication { } catch (IOException e) { showError("Failure!", "Could not access "+file.getAbsolutePath()+". It's possible that another program has it open."); + } finally { + enable(ButtonType.SAVE_MAP); } } diff --git a/src/apps/MapApplication.java b/src/apps/MapApplication.java index 313c928..0dfc60b 100644 --- a/src/apps/MapApplication.java +++ b/src/apps/MapApplication.java @@ -23,8 +23,12 @@ */ package apps; +import java.awt.Desktop; import java.io.File; +import java.io.IOException; import java.lang.reflect.Array; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import java.util.function.BiConsumer; import java.util.function.BooleanSupplier; @@ -46,7 +50,6 @@ import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.Control; import javafx.scene.control.Label; -import javafx.scene.control.ListCell; import javafx.scene.control.MenuButton; import javafx.scene.control.MenuItem; import javafx.scene.control.Slider; @@ -154,7 +157,9 @@ public abstract class MapApplication extends Application { { 0., 0.,-90.,-147.,-35., -45., 30., -145., 154. } }; - final private String name; + private final Map buttons = new HashMap(); + + private String name; private Stage root; private ComboBox projectionChooser; private GridPane paramGrid; @@ -202,7 +207,7 @@ public abstract class MapApplication extends Application { FileChooser.ExtensionFilter defaultExtension, Consumer inputSetter) { final Label label = new Label("Current input:"); - final Text inputLabel = new Text("None"); + final Text inputLabel = new Text("Basic"+defaultExtension.getExtensions().get(0).substring(1)); //this line kind of cheats, since it assumes the first input will be called "Basic", but I couldn't figure out a good way for this to update when the subclass programatically sets the input final FileChooser inputChooser = new FileChooser(); //TODO: remember last directory inputChooser.setInitialDirectory(new File("input")); @@ -223,7 +228,7 @@ public abstract class MapApplication extends Application { } if (file != null) { final File f = file; - trigger(loadButton, () -> inputSetter.accept(f)); + new Thread(() -> inputSetter.accept(f)).start(); inputLabel.setText(f.getName()); } }); @@ -236,6 +241,7 @@ public abstract class MapApplication extends Application { } }); + buttons.put(ButtonType.LOAD_INPUT, loadButton); VBox output = new VBox(V_SPACE, new HBox(H_SPACE, label, inputLabel), loadButton); output.setAlignment(Pos.CENTER); return output; @@ -429,10 +435,10 @@ public abstract class MapApplication extends Application { * Create a default button that will update the map * @return the button */ - protected Button buildUpdateButton(Runnable mapUpdater) { - final Button updateButton = new Button("Update map"); + protected Region buildUpdateButton(String text, Runnable mapUpdater) { + final Button updateButton = new Button(text); updateButton.setOnAction((event) -> { - trigger(updateButton, mapUpdater); + new Thread(mapUpdater).start(); }); updateButton.setTooltip(new Tooltip( "Update the current map with your parameters")); @@ -445,6 +451,7 @@ public abstract class MapApplication extends Application { } }); + this.buttons.put(ButtonType.UPDATE_MAP, updateButton); return updateButton; } @@ -459,7 +466,7 @@ public abstract class MapApplication extends Application { * @param calculateAndSaver The callback that saves the thing * @return the button, ready to be pressed */ - protected Button buildSaveButton(boolean bindCtrlS, String savee, + protected Region buildSaveButton(boolean bindCtrlS, String savee, FileChooser.ExtensionFilter[] allowedExtensions, FileChooser.ExtensionFilter defaultExtension, BooleanSupplier settingCollector, @@ -490,10 +497,13 @@ public abstract class MapApplication extends Application { final ProgressBarDialog pBar = new ProgressBarDialog(); pBar.setContentText("Finalizing "+savee+"..."); pBar.show(); - trigger(saveButton, () -> { - calculateAndSaver.accept(f, pBar); - Platform.runLater(pBar::close); - }); //TODO: I think I might want to automatically open the image + new Thread(() -> { + calculateAndSaver.accept(f, pBar); + Platform.runLater(pBar::close); + try { + Desktop.getDesktop().open(f.getParentFile()); + } catch (IOException e) {} //if you can't open the file for any reason, just don't worry about it + }).start(); } } }); @@ -507,6 +517,10 @@ public abstract class MapApplication extends Application { } }); + if (savee.equals("map")) + this.buttons.put(ButtonType.SAVE_MAP, saveButton); + else + this.buttons.put(ButtonType.SAVE_GRAPH, saveButton); return saveButton; } @@ -526,6 +540,18 @@ public abstract class MapApplication extends Application { } + protected void disable(ButtonType... buttons) { + for (ButtonType bt: buttons) + this.buttons.get(bt).setDisable(true); + } + + + protected void enable(ButtonType... buttons) { + for (ButtonType bt: buttons) + this.buttons.get(bt).setDisable(false); + } + + private void chooseProjectionFromExpandedList(Projection lastProjection) { final ProjectionSelectionDialog selectDialog = new ProjectionSelectionDialog(); @@ -604,20 +630,6 @@ public abstract class MapApplication extends Application { } - private static void trigger(Button btn, Runnable task) { - btn.setDisable(true); - new Thread(() -> { - try { - task.run(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - btn.setDisable(false); - } - }).start(); - } - - private static void link(Slider sld, Spinner spn, int i, double[] doubles, DoubleUnaryOperator converter, Procedure callback, Flag isChanging, Flag suppressListeners) { sld.valueChangingProperty().addListener((observable, prev, now) -> { //link spinner to slider @@ -657,5 +669,11 @@ public abstract class MapApplication extends Application { alert.showAndWait(); }); } - + + + + protected enum ButtonType { + LOAD_INPUT, UPDATE_MAP, SAVE_MAP, SAVE_GRAPH; + } } + diff --git a/src/apps/MapDesignerRaster.java b/src/apps/MapDesignerRaster.java index 013e611..fcaae4c 100644 --- a/src/apps/MapDesignerRaster.java +++ b/src/apps/MapDesignerRaster.java @@ -36,16 +36,13 @@ import javax.imageio.ImageIO; import dialogs.MapConfigurationDialog; import dialogs.ProgressBarDialog; -import javafx.application.Platform; import javafx.embed.swing.SwingFXUtils; import javafx.geometry.Insets; import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.Alert; -import javafx.scene.control.Button; import javafx.scene.control.Separator; import javafx.scene.image.ImageView; import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.stage.FileChooser; @@ -85,8 +82,7 @@ public class MapDesignerRaster extends MapApplication { private static final float GRATICULE_WIDTH = 0.5f; private static final Color GRATICULE_COLOR = Color.WHITE; - private Node aspectSelector; - private Button updateBtn, saveMapBtn; + private Region aspectSelector; private double[] aspect; private Flag cropAtIDL; private MutableDouble graticuleSpacing; @@ -106,25 +102,25 @@ public class MapDesignerRaster extends MapApplication { public void start(Stage root) { super.start(root); new Thread(() -> { - setInput(new File("input/Basic.png")); //TODO: this should cause the buttons to grey out + setInput(new File("input/Basic.png")); updateMap(); }).start(); } @Override - protected Node makeWidgets() { + protected Region makeWidgets() { this.aspect = new double[3]; this.cropAtIDL = new Flag(false); this.graticuleSpacing = new MutableDouble(15); - final Node inputSelector = buildInputSelector(READABLE_TYPES, + final Region inputSelector = buildInputSelector(READABLE_TYPES, RASTER_TYPES[0], this::setInput); - final Node projectionSelector = buildProjectionSelector(this::hideAspect); + final Region projectionSelector = buildProjectionSelector(this::hideAspect); this.aspectSelector = buildAspectSelector(this.aspect, Procedure.NONE); - final Node parameterSelector = buildParameterSelector(Procedure.NONE); - final Node optionPane = buildOptionPane(cropAtIDL, graticuleSpacing); - this.updateBtn = buildUpdateButton(this::updateMap); - this.saveMapBtn = buildSaveButton(true, "map", RASTER_TYPES, + final Region parameterSelector = buildParameterSelector(Procedure.NONE); + final Region optionPane = buildOptionPane(cropAtIDL, graticuleSpacing); + final Region updateBtn = buildUpdateButton("Update Map", this::updateMap); + final Region saveMapBtn = buildSaveButton(true, "map", RASTER_TYPES, RASTER_TYPES[0], this::collectFinalSettings, this::calculateAndSaveMap); final HBox buttons = new HBox(H_SPACE, updateBtn, saveMapBtn); buttons.setAlignment(Pos.CENTER); @@ -153,19 +149,15 @@ public class MapDesignerRaster extends MapApplication { private void setInput(File file) { - updateBtn.setDisable(true); - saveMapBtn.setDisable(true); + disable(ButtonType.LOAD_INPUT, ButtonType.UPDATE_MAP, ButtonType.SAVE_MAP); try { input = new PixelMap(file); } catch (IOException e) { - final Alert alert = new Alert(Alert.AlertType.ERROR); - alert.setHeaderText("File not found!"); - alert.setContentText("Couldn't find "+file.getAbsolutePath()+"."); - Platform.runLater(alert::showAndWait); + showError("File not found!", + "Couldn't find "+file.getAbsolutePath()+"."); } finally { - updateBtn.setDisable(false); - saveMapBtn.setDisable(false); + enable(ButtonType.LOAD_INPUT, ButtonType.UPDATE_MAP, ButtonType.SAVE_MAP); } } @@ -176,8 +168,10 @@ public class MapDesignerRaster extends MapApplication { private void updateMap() { + disable(ButtonType.UPDATE_MAP, ButtonType.SAVE_MAP); loadParameters(); display.setImage(SwingFXUtils.toFXImage(makeImage(), null)); + enable(ButtonType.UPDATE_MAP, ButtonType.SAVE_MAP); } @@ -191,6 +185,8 @@ public class MapDesignerRaster extends MapApplication { private void calculateAndSaveMap(File file, ProgressBarDialog pBar) { + disable(ButtonType.SAVE_MAP); + final int[] outDims = configDialog.getDims(); final int smoothing = configDialog.getSmoothing(); BufferedImage theMap = makeImage(outDims[0], outDims[1], smoothing, pBar); //calculate @@ -204,6 +200,8 @@ public class MapDesignerRaster extends MapApplication { } catch (IOException e) { showError("Failure!", "Could not access "+file.getAbsolutePath()+". It's possible that another program has it open."); + } finally { + enable(ButtonType.SAVE_MAP); } display.setImage(SwingFXUtils.toFXImage(theMap, null)); } diff --git a/src/apps/MapDesignerVector.java b/src/apps/MapDesignerVector.java index 79c3309..983e78c 100644 --- a/src/apps/MapDesignerVector.java +++ b/src/apps/MapDesignerVector.java @@ -35,12 +35,11 @@ import org.xml.sax.SAXException; import dialogs.ProgressBarDialog; import javafx.geometry.Insets; import javafx.geometry.Pos; -import javafx.scene.Node; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; -import javafx.scene.control.Button; import javafx.scene.control.Separator; import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.stage.FileChooser; @@ -71,8 +70,7 @@ public class MapDesignerVector extends MapApplication { private static final FileChooser.ExtensionFilter[] VECTOR_TYPES = { new FileChooser.ExtensionFilter("SVG", "*.svg") }; - private Node aspectSelector; - private Button saveBtn; + private Region aspectSelector; private double[] aspect; private SVGMap input; private Canvas viewer; @@ -87,23 +85,26 @@ public class MapDesignerVector extends MapApplication { @Override public void start(Stage root) { + System.out.println("Check 0"); //these are for tracking down that rare BufferOverflowException super.start(root); + System.out.println("Check 1"); new Thread(() -> { - setInput(new File("input/Basic.svg")); //this automatically updates the map//TODO: this should cause the buttons to grey out + setInput(new File("input/Basic.svg")); //this automatically updates the map }).start(); + System.out.println("Check 2"); } @Override - protected Node makeWidgets() { + protected Region makeWidgets() { this.aspect = new double[3]; - final Node inputSelector = buildInputSelector(VECTOR_TYPES, + final Region inputSelector = buildInputSelector(VECTOR_TYPES, VECTOR_TYPES[0], this::setInput); - final Node projectionSelector = buildProjectionSelector( + final Region projectionSelector = buildProjectionSelector( Procedure.concat(this::updateMap, this::hideAspect)); this.aspectSelector = buildAspectSelector(this.aspect, this::updateMap); - final Node parameterSelector = buildParameterSelector(this::updateMap); - this.saveBtn = buildSaveButton(true, "map", VECTOR_TYPES, + final Region parameterSelector = buildParameterSelector(this::updateMap); + final Region saveBtn = buildSaveButton(true, "map", VECTOR_TYPES, VECTOR_TYPES[0], ()->true, this::calculateAndSaveMap); final VBox layout = new VBox(V_SPACE, @@ -128,7 +129,7 @@ public class MapDesignerVector extends MapApplication { private void setInput(File file) { - saveBtn.setDisable(true); + disable(ButtonType.LOAD_INPUT, ButtonType.SAVE_MAP); try { input = new SVGMap(file); @@ -139,23 +140,24 @@ public class MapDesignerVector extends MapApplication { showError("Unreadable file!", "We couldn't read "+file.getAbsolutePath()+". It may be corrupt or an unreadable format."); } catch (ParserConfigurationException e) { - // TODO: Handle this - e.printStackTrace(); + showError("Parser Configuration Error!", + "My parser configured incorrectly. I blame you."); } finally { - saveBtn.setDisable(false); + enable(ButtonType.LOAD_INPUT); + updateMap(); } - updateMap(); } - private void hideAspect() { aspectSelector.setVisible(this.getProjection().hasAspect()); } private void updateMap() { + disable(ButtonType.SAVE_MAP); + loadParameters(); int maxVtx = this.getParamsChanging() ? FAST_MAX_VTX : DEF_MAX_VTX; final Iterable transformed = map(input, input.length()/maxVtx+1, aspect.clone(), null); @@ -169,10 +171,14 @@ public class MapDesignerVector extends MapApplication { viewer.setHeight(IMG_WIDTH); } drawImage(transformed, viewer); + + enable(ButtonType.SAVE_MAP); } private void calculateAndSaveMap(File file, ProgressBarDialog pBar) { + disable(ButtonType.SAVE_MAP); + loadParameters(); final List transformed = map(input, 1, aspect.clone(), pBar::setProgress); //calculate try { @@ -183,6 +189,8 @@ public class MapDesignerVector extends MapApplication { } catch (IOException e) { showError("Failure!", "Could not access "+file.getAbsolutePath()+". It's possible that another program has it open."); + } finally { + enable(ButtonType.SAVE_MAP); } } diff --git a/src/maps/Lenticular.java b/src/maps/Lenticular.java index 964ba29..0d8ba71 100644 --- a/src/maps/Lenticular.java +++ b/src/maps/Lenticular.java @@ -27,7 +27,7 @@ import maps.Projection.Property; import maps.Projection.Type; /** - * TODO: Write description + * A class containing map projections with four-way symmetry and curved parallels. * * @author jkunimune */ diff --git a/src/utils/Dixon.java b/src/utils/Dixon.java index efa7033..8315c0c 100644 --- a/src/utils/Dixon.java +++ b/src/utils/Dixon.java @@ -43,8 +43,8 @@ public class Dixon { */ public static double PERIOD_THIRD = 1.7666387503; - private static final double[] COEF = { 1.000000e0, .625000e-1, .223214e-2, .069754e-3, .020121e-4, - .005539e-5/*, .001477e-6, .000385e-7, .000099e-8, .000025e-9*/ }; + private static final double[] COEF = { 1.000000e0, .625000e-1, .223214e-2, .069754e-3, + .020121e-4/*, .005539e-5, .001477e-6, .000385e-7, .000099e-8, .000025e-9*/ }; private static final double TOLERANCE = 1e-3;