package tim.prune.save; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.FlowLayout; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JDialog; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import tim.prune.App; import tim.prune.GenericFunction; import tim.prune.I18nManager; import tim.prune.config.ColourScheme; import tim.prune.config.Config; import tim.prune.data.DataPoint; import tim.prune.data.DoubleRange; import tim.prune.data.Track; import tim.prune.gui.BaseImageDefinitionPanel; import tim.prune.gui.GuiGridLayout; import tim.prune.gui.WholeNumberField; import tim.prune.gui.colour.PointColourer; import tim.prune.gui.map.MapSource; import tim.prune.gui.map.MapSourceLibrary; import tim.prune.gui.map.MapUtils; import tim.prune.gui.map.WpIconDefinition; import tim.prune.gui.map.WpIconLibrary; import tim.prune.load.GenericFileFilter; import tim.prune.threedee.ImageDefinition; /** * Class to handle the exporting of map images, optionally with track data drawn on top. * This allows images larger than the screen to be generated. */ public class ImageExporter extends GenericFunction implements BaseImageConsumer { private JDialog _dialog = null; private JCheckBox _drawDataCheckbox = null; private JCheckBox _drawTrackPointsCheckbox = null; private WholeNumberField _textScaleField = null; private BaseImageDefinitionPanel _baseImagePanel = null; private JFileChooser _fileChooser = null; private JButton _okButton = null; /** * Constructor * @param inApp App object */ public ImageExporter(App inApp) { super(inApp); } /** Get the name key */ public String getNameKey() { return "function.exportimage"; } /** * Begin the function by showing the input dialog */ public void begin() { // Make dialog window if (_dialog == null) { _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true); _dialog.setLocationRelativeTo(_parentFrame); _dialog.getContentPane().add(makeDialogComponents()); _dialog.pack(); _textScaleField.setValue(100); } // Check if there is a cache to use if (!BaseImageConfigDialog.isImagePossible()) { _app.showErrorMessage(getNameKey(), "dialog.exportimage.noimagepossible"); return; } _baseImagePanel.updateBaseImageDetails(); baseImageChanged(); // Show dialog _dialog.setVisible(true); } /** * Make the dialog components to select the export options * @return Component holding gui elements */ private Component makeDialogComponents() { JPanel panel = new JPanel(); panel.setLayout(new BorderLayout(4, 4)); // Checkbox for drawing track or not _drawDataCheckbox = new JCheckBox(I18nManager.getText("dialog.exportimage.drawtrack")); _drawDataCheckbox.setSelected(true); // draw by default // Also whether to draw track points or not _drawTrackPointsCheckbox = new JCheckBox(I18nManager.getText("dialog.exportimage.drawtrackpoints")); _drawTrackPointsCheckbox.setSelected(true); // Add listener to en/disable trackpoints checkbox _drawDataCheckbox.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { _drawTrackPointsCheckbox.setEnabled(_drawDataCheckbox.isSelected()); } }); // TODO: Maybe have other controls such as line width, symbol scale factor JPanel controlsPanel = new JPanel(); GuiGridLayout grid = new GuiGridLayout(controlsPanel); grid.add(new JLabel(I18nManager.getText("dialog.exportimage.textscalepercent") + ": ")); _textScaleField = new WholeNumberField(3); _textScaleField.setText("888"); grid.add(_textScaleField); // OK, Cancel buttons JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); _okButton = new JButton(I18nManager.getText("button.ok")); _okButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { doExport(); _baseImagePanel.getGrouter().clearMapImage(); _dialog.dispose(); } }); buttonPanel.add(_okButton); JButton cancelButton = new JButton(I18nManager.getText("button.cancel")); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { _baseImagePanel.getGrouter().clearMapImage(); _dialog.dispose(); } }); buttonPanel.add(cancelButton); panel.add(buttonPanel, BorderLayout.SOUTH); // Listener to close dialog if escape pressed KeyAdapter closer = new KeyAdapter() { public void keyReleased(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { _dialog.dispose(); _baseImagePanel.getGrouter().clearMapImage(); } } }; _drawDataCheckbox.addKeyListener(closer); // Panel for the base image _baseImagePanel = new BaseImageDefinitionPanel(this, _dialog, _app.getTrackInfo().getTrack()); // Panel for the checkboxes at the top JPanel checkPanel = new JPanel(); checkPanel.setLayout(new BoxLayout(checkPanel, BoxLayout.Y_AXIS)); checkPanel.add(_drawDataCheckbox); checkPanel.add(_drawTrackPointsCheckbox); // add these panels to the holder panel JPanel holderPanel = new JPanel(); holderPanel.setLayout(new BorderLayout(5, 5)); holderPanel.add(checkPanel, BorderLayout.NORTH); holderPanel.add(controlsPanel, BorderLayout.CENTER); holderPanel.add(_baseImagePanel, BorderLayout.SOUTH); holderPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); panel.add(holderPanel, BorderLayout.NORTH); return panel; } /** * Select the file and export data to it */ private void doExport() { _okButton.setEnabled(false); // OK pressed, so choose output file if (_fileChooser == null) { _fileChooser = new JFileChooser(); _fileChooser.setDialogType(JFileChooser.SAVE_DIALOG); _fileChooser.setFileFilter(new GenericFileFilter("filetype.png", new String[] {"png"})); _fileChooser.setAcceptAllFileFilterUsed(false); // start from directory in config which should be set final String configDir = Config.getConfigString(Config.KEY_TRACK_DIR); if (configDir != null) {_fileChooser.setCurrentDirectory(new File(configDir));} } // Allow choose again if an existing file is selected boolean chooseAgain = false; do { chooseAgain = false; if (_fileChooser.showSaveDialog(_parentFrame) == JFileChooser.APPROVE_OPTION) { // OK pressed and file chosen File pngFile = _fileChooser.getSelectedFile(); if (!pngFile.getName().toLowerCase().endsWith(".png")) { pngFile = new File(pngFile.getAbsolutePath() + ".png"); } // Check if file exists and if necessary prompt for overwrite Object[] buttonTexts = {I18nManager.getText("button.overwrite"), I18nManager.getText("button.cancel")}; if (!pngFile.exists() || JOptionPane.showOptionDialog(_parentFrame, I18nManager.getText("dialog.save.overwrite.text"), I18nManager.getText("dialog.save.overwrite.title"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1]) == JOptionPane.YES_OPTION) { // Export the file if (!exportFile(pngFile)) { // export failed so need to choose again chooseAgain = true; } } else { // overwrite cancelled so need to choose again chooseAgain = true; } } } while (chooseAgain); } /** * Export the track data to the specified file * @param inPngFile File object to save to * @return true if successful */ private boolean exportFile(File inPngFile) { // Get the image file from the grouter ImageDefinition imageDef = _baseImagePanel.getImageDefinition(); MapSource source = MapSourceLibrary.getSource(imageDef.getSourceIndex()); MapGrouter grouter = _baseImagePanel.getGrouter(); GroutedImage baseImage = grouter.getMapImage(_app.getTrackInfo().getTrack(), source, imageDef.getZoom()); if (baseImage == null || !baseImage.isValid()) { _app.showErrorMessage(getNameKey(), "dialog.exportpov.cannotmakebaseimage"); return true; } try { if (_drawDataCheckbox.isSelected()) { // Draw the track on top of this image drawData(baseImage); } // Write composite image to file if (!ImageIO.write(baseImage.getImage(), "png", inPngFile)) { _app.showErrorMessage(getNameKey(), "dialog.exportpov.cannotmakebaseimage"); return false; // choose again - the image creation worked but the save failed } } catch (IOException ioe) { System.err.println("Can't write image: " + ioe.getClass().getName()); } return true; } /** * Draw the track and waypoint data from the current Track onto the given image * @param inImage GroutedImage from map tiles */ private void drawData(GroutedImage inImage) { // Work out x, y limits for drawing DoubleRange xRange = inImage.getXRange(); DoubleRange yRange = inImage.getYRange(); final int zoomFactor = 1 << _baseImagePanel.getImageDefinition().getZoom(); Graphics g = inImage.getImage().getGraphics(); // TODO: Set line width, style etc final PointColourer pointColourer = _app.getPointColourer(); final Color defaultPointColour = Config.getColourScheme().getColour(ColourScheme.IDX_POINT); g.setColor(defaultPointColour); // Loop to draw all track points final Track track = _app.getTrackInfo().getTrack(); final int numPoints = track.getNumPoints(); int prevX = 0, prevY = 0; boolean gotPreviousPoint = false; for (int i=0; i