X-Git-Url: https://gitweb.fperrin.net/?a=blobdiff_plain;f=tim%2Fprune%2Fsave%2FBaseImageConfigDialog.java;fp=tim%2Fprune%2Fsave%2FBaseImageConfigDialog.java;h=514ebd79ec46d8c8fa5203d9f460872ddbc66d49;hb=7f5ed2be62905bd37717376dc22d09e5ea7edb4d;hp=0000000000000000000000000000000000000000;hpb=b361869e590bbca32664c16ac24d6296926594a5;p=GpsPrune.git diff --git a/tim/prune/save/BaseImageConfigDialog.java b/tim/prune/save/BaseImageConfigDialog.java new file mode 100644 index 0000000..514ebd7 --- /dev/null +++ b/tim/prune/save/BaseImageConfigDialog.java @@ -0,0 +1,495 @@ +package tim.prune.save; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.FlowLayout; +import java.awt.GridLayout; +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 javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import tim.prune.DataSubscriber; +import tim.prune.I18nManager; +import tim.prune.config.Config; +import tim.prune.data.Track; +import tim.prune.gui.map.MapSource; +import tim.prune.gui.map.MapSourceLibrary; + +/** + * Dialog to let you choose the parameters for a base image + * (source and zoom) + */ +public class BaseImageConfigDialog implements Runnable +{ + /** Parent to notify */ + private DataSubscriber _parent = null; + /** Parent dialog for position */ + private JDialog _parentDialog = null; + /** Track to use for preview image */ + private Track _track = null; + /** Dialog to show */ + private JDialog _dialog = null; + /** Checkbox for using an image or not */ + private JCheckBox _useImageCheckbox = null; + /** Panel to hold the other controls */ + private JPanel _mainPanel = null; + /** Dropdown for map source */ + private JComboBox _mapSourceDropdown = null; + /** Dropdown for zoom levels */ + private JComboBox _zoomDropdown = null; + /** Warning label that image is incomplete */ + private JLabel _imageIncompleteLabel = null; + /** Label for number of tiles found */ + private JLabel _tilesFoundLabel = null; + /** Label for image size in pixels */ + private JLabel _imageSizeLabel = null; + /** Image preview panel */ + private ImagePreviewPanel _previewPanel = null; + /** OK button, needs to be enabled/disabled */ + private JButton _okButton = null; + /** Flag for rebuilding dialog, don't bother refreshing and recalculating */ + private boolean _rebuilding = false; + /** Cached values to allow cancellation of dialog */ + private boolean _useImage = false; + private int _sourceIndex = 0; + private int _zoomLevel = 0; + + + /** + * Constructor + * @param inParent parent object to notify on completion of dialog + * @param inParentDialog parent dialog + * @param inTrack track object + */ + public BaseImageConfigDialog(DataSubscriber inParent, JDialog inParentDialog, Track inTrack) + { + _parent = inParent; + _parentDialog = inParentDialog; + _dialog = new JDialog(inParentDialog, I18nManager.getText("dialog.baseimage.title"), true); + _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + _dialog.getContentPane().add(makeDialogComponents()); + _dialog.pack(); + _track = inTrack; + } + + /** + * Begin the function + */ + public void begin() + { + initDialog(); + _dialog.setLocationRelativeTo(_parentDialog); + _dialog.setVisible(true); + } + + /** + * Begin the function with a default of using an image + */ + public void beginWithImageYes() + { + initDialog(); + _useImageCheckbox.setSelected(true); + refreshDialog(); + _dialog.setVisible(true); + } + + /** + * Initialise the dialog from the cached values + */ + private void initDialog() + { + _rebuilding = true; + _useImageCheckbox.setSelected(_useImage); + // Populate the dropdown of map sources from the library in case it has changed + _mapSourceDropdown.removeAllItems(); + for (int i=0; i= _mapSourceDropdown.getItemCount()) { + _sourceIndex = 0; + } + _mapSourceDropdown.setSelectedIndex(_sourceIndex); + + // Zoom level + if (_useImage) + { + for (int i=0; i<_zoomDropdown.getItemCount(); i++) + { + String item = _zoomDropdown.getItemAt(i).toString(); + try { + if (Integer.parseInt(item) == _zoomLevel) { + _zoomDropdown.setSelectedIndex(i); + break; + } + } + catch (NumberFormatException nfe) {} + } + } + _rebuilding = false; + refreshDialog(); + } + + /** + * Update the visibility of the controls, and update the zoom dropdown based on the selected map source + */ + private void refreshDialog() + { + _mainPanel.setVisible(_useImageCheckbox.isSelected()); + // Exit if we're in the middle of something + if (_rebuilding) {return;} + int currentZoom = 0; + try { + currentZoom = Integer.parseInt(_zoomDropdown.getSelectedItem().toString()); + } + catch (Exception nfe) {} + // Get the extent of the track so we can work out how big the images are going to be for each zoom level + // System.out.println("Ranges are: x=" + _track.getXRange().getRange() + ", y=" + _track.getYRange().getRange()); + final double xyExtent = Math.max(_track.getXRange().getRange(), _track.getYRange().getRange()); + int zoomToSelect = -1; + + _rebuilding = true; + _zoomDropdown.removeAllItems(); + if (_useImageCheckbox.isSelected() && _mapSourceDropdown.getItemCount() > 0) + { + int currentSource = _mapSourceDropdown.getSelectedIndex(); + for (int i=5; i<18; i++) + { + // How many pixels does this give? + final int zoomFactor = 1 << i; + final int pixCount = (int) (xyExtent * zoomFactor * 256); + if (pixCount > 100 // less than this isn't worth it + && pixCount < 4000 // don't want to run out of memory + && isZoomAvailable(i, MapSourceLibrary.getSource(currentSource))) + { + _zoomDropdown.addItem("" + i); + if (i == currentZoom) { + zoomToSelect = _zoomDropdown.getItemCount() - 1; + } + } + // else System.out.println("Not using zoom " + i + " because pixCount=" + pixCount + " and xyExtent=" + xyExtent); + } + } + _zoomDropdown.setSelectedIndex(zoomToSelect); + _rebuilding = false; + + _okButton.setEnabled(!_useImageCheckbox.isSelected() || + (_zoomDropdown.getItemCount() > 0 && _zoomDropdown.getSelectedIndex() >= 0)); + updateImagePreview(); + } + + /** + * @return true if it should be possible to use an image, false if no disk cache or cache empty + */ + public static boolean isImagePossible() + { + String path = Config.getConfigString(Config.KEY_DISK_CACHE); + if (path != null && !path.equals("")) + { + File cacheDir = new File(path); + if (cacheDir.exists() && cacheDir.isDirectory()) + { + // Check if there are any directories in the cache + for (File subdir : cacheDir.listFiles()) + { + if (subdir.exists() && subdir.isDirectory()) { + return true; + } + } + } + } + return false; + } + + /** + * See if the requested zoom level is available + * @param inZoom zoom level + * @param inSource selected map source + * @return true if there is a zoom directory for each of the source's layers + */ + private static boolean isZoomAvailable(int inZoom, MapSource inSource) + { + if (inSource == null) {return false;} + String path = Config.getConfigString(Config.KEY_DISK_CACHE); + if (path == null || path.equals("")) { + return false; + } + File cacheDir = new File(path); + if (!cacheDir.exists() || !cacheDir.isDirectory()) { + return false; + } + // First layer + File layer0 = new File(cacheDir, inSource.getSiteName(0) + inZoom); + if (!layer0.exists() || !layer0.isDirectory() || !layer0.canRead()) { + return false; + } + // Second layer, if any + if (inSource.getNumLayers() > 1) + { + File layer1 = new File(cacheDir, inSource.getSiteName(1) + inZoom); + if (!layer1.exists() || !layer1.isDirectory() || !layer1.canRead()) { + return false; + } + } + // must be ok + return true; + } + + + /** + * @return true if image has been selected + */ + public boolean useImage() { + return _useImage; + } + + /** + * @return index of selected image source + */ + public int getSourceIndex() { + return _sourceIndex; + } + + /** + * @return selected zoom level + */ + public int getZoomLevel() { + return _zoomLevel; + } + + /** + * Make the dialog components to select the options + * @return Component holding gui elements + */ + private Component makeDialogComponents() + { + JPanel panel = new JPanel(); + panel.setLayout(new BorderLayout()); + _useImageCheckbox = new JCheckBox(I18nManager.getText("dialog.baseimage.useimage")); + _useImageCheckbox.setBorder(BorderFactory.createEmptyBorder(4, 4, 6, 4)); + _useImageCheckbox.setHorizontalAlignment(JLabel.CENTER); + _useImageCheckbox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + refreshDialog(); + } + }); + panel.add(_useImageCheckbox, BorderLayout.NORTH); + + // Outer panel with the grid and the map preview + _mainPanel = new JPanel(); + _mainPanel.setLayout(new BorderLayout(1, 10)); + // Central stuff with labels and dropdowns + JPanel controlsPanel = new JPanel(); + controlsPanel.setLayout(new GridLayout(0, 2, 10, 4)); + // map source + JLabel sourceLabel = new JLabel(I18nManager.getText("dialog.baseimage.mapsource") + ": "); + sourceLabel.setHorizontalAlignment(JLabel.RIGHT); + controlsPanel.add(sourceLabel); + _mapSourceDropdown = new JComboBox(); + _mapSourceDropdown.addItem("name of map source"); + // Add listener to dropdown to change zoom levels + _mapSourceDropdown.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + refreshDialog(); + } + }); + controlsPanel.add(_mapSourceDropdown); + // zoom level + JLabel zoomLabel = new JLabel(I18nManager.getText("dialog.baseimage.zoom") + ": "); + zoomLabel.setHorizontalAlignment(JLabel.RIGHT); + controlsPanel.add(zoomLabel); + _zoomDropdown = new JComboBox(); + // Add action listener to enable ok button when zoom changed + _zoomDropdown.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent arg0) { + if (_zoomDropdown.getSelectedIndex() >= 0) { + _okButton.setEnabled(true); + updateImagePreview(); + } + } + }); + controlsPanel.add(_zoomDropdown); + _mainPanel.add(controlsPanel, BorderLayout.NORTH); + + JPanel imagePanel = new JPanel(); + imagePanel.setLayout(new BorderLayout(10, 1)); + // image preview + _previewPanel = new ImagePreviewPanel(); + imagePanel.add(_previewPanel, BorderLayout.CENTER); + + // Label panel on right + JPanel labelPanel = new JPanel(); + labelPanel.setLayout(new BorderLayout()); + _imageIncompleteLabel = new JLabel(I18nManager.getText("dialog.baseimage.incomplete")); + _imageIncompleteLabel.setForeground(Color.RED); + _imageIncompleteLabel.setVisible(false); + labelPanel.add(_imageIncompleteLabel, BorderLayout.NORTH); + JPanel labelGridPanel = new JPanel(); + labelGridPanel.setLayout(new GridLayout(0, 2, 10, 4)); + labelGridPanel.add(new JLabel(I18nManager.getText("dialog.baseimage.tiles") + ": ")); + _tilesFoundLabel = new JLabel("11 / 11"); + labelGridPanel.add(_tilesFoundLabel); + labelGridPanel.add(new JLabel(I18nManager.getText("dialog.baseimage.size") + ": ")); + _imageSizeLabel = new JLabel("1430"); + labelGridPanel.add(_imageSizeLabel); + labelGridPanel.add(new JLabel(" ")); // just for spacing + labelPanel.add(labelGridPanel, BorderLayout.SOUTH); + imagePanel.add(labelPanel, BorderLayout.EAST); + + _mainPanel.add(imagePanel, BorderLayout.CENTER); + panel.add(_mainPanel, BorderLayout.CENTER); + + // 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) + { + // Check values, maybe don't want to exit + if (!_useImageCheckbox.isSelected() + || (_mapSourceDropdown.getSelectedIndex() >= 0 && _zoomDropdown.getSelectedIndex() >= 0)) + { + storeValues(); + _dialog.dispose(); + } + } + }); + buttonPanel.add(_okButton); + JButton cancelButton = new JButton(I18nManager.getText("button.cancel")); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) + { + _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(); + } + } + }; + _useImageCheckbox.addKeyListener(closer); + _mapSourceDropdown.addKeyListener(closer); + _zoomDropdown.addKeyListener(closer); + _okButton.addKeyListener(closer); + cancelButton.addKeyListener(closer); + + return panel; + } + + /** + * Use the selected settings to make a preview image and (asynchronously) update the preview panel + */ + private void updateImagePreview() + { + // Clear labels + _imageIncompleteLabel.setVisible(false); + _tilesFoundLabel.setText(""); + _imageSizeLabel.setText(""); + if (_useImageCheckbox.isSelected() && _mapSourceDropdown.getSelectedIndex() >= 0 + && _zoomDropdown.getItemCount() > 0 && _zoomDropdown.getSelectedIndex() >= 0) + { + _previewPanel.startLoading(); + // Launch a separate thread to create an image and pass it to the preview panel + new Thread(this).start(); + } + else { + // clear preview + _previewPanel.setImage(null); + } + } + + /** + * Store the selected details in the variables + */ + private void storeValues() + { + // Store values of controls in variables + _useImage = _useImageCheckbox.isSelected(); + _sourceIndex = _mapSourceDropdown.getSelectedIndex(); + try { + String zoomStr = _zoomDropdown.getSelectedItem().toString(); + _zoomLevel = Integer.parseInt(zoomStr); + } + catch (Exception nfe) { + _zoomLevel = 0; + } + // Call parent to retrieve values + _parent.dataUpdated(DataSubscriber.ALL); + } + + /** + * Run method for separate thread. Uses the current dialog parameters + * to trigger a call to the Grouter, and pass the image to the preview panel + */ + public void run() + { + // Remember the current dropdown indices, so we know whether they've changed or not + final int mapIndex = _mapSourceDropdown.getSelectedIndex(); + final int zoomIndex = _zoomDropdown.getSelectedIndex(); + if (!_useImageCheckbox.isSelected() || mapIndex < 0 || zoomIndex < 0) {return;} + + // Get the map source and zoom level + MapSource mapSource = MapSourceLibrary.getSource(mapIndex); + int zoomLevel = 0; + try { + zoomLevel = Integer.parseInt(_zoomDropdown.getSelectedItem().toString()); + } + catch (Exception e) {} + + // Use the Grouter to create an image (slow, blocks thread) + GroutedImage groutedImage = MapGrouter.createMapImage(_track, mapSource, zoomLevel); + + // If the dialog hasn't changed, pass the generated image to the preview panel + if (_useImageCheckbox.isSelected() + && _mapSourceDropdown.getSelectedIndex() == mapIndex + && _zoomDropdown.getSelectedIndex() == zoomIndex + && groutedImage != null) + { + _previewPanel.setImage(groutedImage); + // Set values of labels + _imageIncompleteLabel.setVisible(!groutedImage.isComplete()); + _tilesFoundLabel.setText(groutedImage.getNumTilesUsed() + " / " + groutedImage.getNumTilesTotal()); + if (groutedImage.getImageSize() > 0) { + _imageSizeLabel.setText("" + groutedImage.getImageSize()); + } + else { + _imageSizeLabel.setText(""); + } + } + else + { + _previewPanel.setImage(null); + // Clear labels + _imageIncompleteLabel.setVisible(false); + _tilesFoundLabel.setText(""); + _imageSizeLabel.setText(""); + } + } + + /** + * @return true if any map data has been found for the image + */ + public boolean getFoundData() + { + return _useImage && _zoomLevel > 0 && _previewPanel != null && _previewPanel.getTilesFound(); + } +}