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.
This commit is contained in:
Justin Kunimune 2018-01-22 09:27:51 -10:00
parent a9b5978b9c
commit d6eb4ddcbf
6 changed files with 129 additions and 93 deletions

View File

@ -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);
}
}

View File

@ -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<ButtonType, Button> buttons = new HashMap<ButtonType, Button>();
private String name;
private Stage root;
private ComboBox<Projection> projectionChooser;
private GridPane paramGrid;
@ -202,7 +207,7 @@ public abstract class MapApplication extends Application {
FileChooser.ExtensionFilter defaultExtension,
Consumer<File> 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<Double> 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;
}
}

View File

@ -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));
}

View File

@ -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<Path> 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<Path> 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);
}
}

View File

@ -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
*/

View File

@ -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;