1 package tim.prune.save;
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.FlowLayout;
6 import java.awt.GridLayout;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.awt.event.KeyAdapter;
10 import java.awt.event.KeyEvent;
13 import javax.swing.BorderFactory;
14 import javax.swing.JButton;
15 import javax.swing.JCheckBox;
16 import javax.swing.JComboBox;
17 import javax.swing.JDialog;
18 import javax.swing.JLabel;
19 import javax.swing.JPanel;
20 import javax.swing.JProgressBar;
22 import tim.prune.I18nManager;
23 import tim.prune.config.Config;
24 import tim.prune.data.Track;
25 import tim.prune.gui.map.MapSource;
26 import tim.prune.gui.map.MapSourceLibrary;
27 import tim.prune.threedee.ImageDefinition;
30 * Dialog to let you choose the parameters for a base image
31 * (source and zoom) including preview
33 public class BaseImageConfigDialog implements Runnable
35 /** Parent to notify */
36 private BaseImageConsumer _parent = null;
37 /** Parent dialog for position */
38 private JDialog _parentDialog = null;
39 /** Track to use for preview image */
40 private Track _track = null;
42 private JDialog _dialog = null;
43 /** Checkbox for using an image or not */
44 private JCheckBox _useImageCheckbox = null;
45 /** Panel to hold the other controls */
46 private JPanel _mainPanel = null;
47 /** Dropdown for map source */
48 private JComboBox<String> _mapSourceDropdown = null;
49 /** Dropdown for zoom levels */
50 private JComboBox<String> _zoomDropdown = null;
51 /** Button to trigger a download of the missing map tiles */
52 private JButton _downloadTilesButton = null;
53 /** Progress bar for downloading additional tiles */
54 private JProgressBar _progressBar = null;
55 /** Label for number of tiles found */
56 private JLabel _tilesFoundLabel = null;
57 /** Label for image size in pixels */
58 private JLabel _imageSizeLabel = null;
59 /** Image preview panel */
60 private ImagePreviewPanel _previewPanel = null;
61 /** Grouter, used to avoid regenerating images */
62 private MapGrouter _grouter = new MapGrouter();
63 /** OK button, needs to be enabled/disabled */
64 private JButton _okButton = null;
65 /** Flag for rebuilding dialog, don't bother refreshing and recalculating */
66 private boolean _rebuilding = false;
67 /** Cached values to allow cancellation of dialog */
68 private ImageDefinition _imageDef = new ImageDefinition();
73 * @param inParent parent object to notify on completion of dialog
74 * @param inParentDialog parent dialog
75 * @param inTrack track object
77 public BaseImageConfigDialog(BaseImageConsumer inParent, JDialog inParentDialog, Track inTrack)
80 _parentDialog = inParentDialog;
81 _dialog = new JDialog(inParentDialog, I18nManager.getText("dialog.baseimage.title"), true);
82 _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
83 _dialog.getContentPane().add(makeDialogComponents());
89 * @param inDefinition image definition object from previous dialog
91 public void setImageDefinition(ImageDefinition inDefinition)
93 _imageDef = inDefinition;
94 if (_imageDef == null) {
95 _imageDef = new ImageDefinition();
105 _dialog.setLocationRelativeTo(_parentDialog);
106 _dialog.setVisible(true);
110 * Begin the function with a default of using an image
112 public void beginWithImageYes()
115 _useImageCheckbox.setSelected(true);
117 _dialog.setVisible(true);
121 * Initialise the dialog from the cached values
123 private void initDialog()
126 _useImageCheckbox.setSelected(_imageDef.getUseImage());
127 // Populate the dropdown of map sources from the library in case it has changed
128 _mapSourceDropdown.removeAllItems();
129 for (int i=0; i<MapSourceLibrary.getNumSources(); i++)
131 _mapSourceDropdown.addItem(MapSourceLibrary.getSource(i).getName());
133 int sourceIndex = _imageDef.getSourceIndex();
134 if (sourceIndex < 0 || sourceIndex >= _mapSourceDropdown.getItemCount()) {
137 _mapSourceDropdown.setSelectedIndex(sourceIndex);
140 int zoomLevel = _imageDef.getZoom();
141 if (_imageDef.getUseImage())
143 for (int i=0; i<_zoomDropdown.getItemCount(); i++)
145 String item = _zoomDropdown.getItemAt(i).toString();
147 if (Integer.parseInt(item) == zoomLevel)
149 _zoomDropdown.setSelectedIndex(i);
153 catch (NumberFormatException nfe) {}
161 * Update the visibility of the controls, and update the zoom dropdown based on the selected map source
163 private void refreshDialog()
165 _mainPanel.setVisible(_useImageCheckbox.isSelected());
166 // Exit if we're in the middle of something
167 if (_rebuilding) {return;}
170 currentZoom = Integer.parseInt(_zoomDropdown.getSelectedItem().toString());
172 catch (Exception nfe) {}
173 // First time in, the dropdown might be empty but we still might have a zoom in the definition
174 if (_zoomDropdown.getItemCount() == 0) {
175 currentZoom = _imageDef.getZoom();
177 // Get the extent of the track so we can work out how big the images are going to be for each zoom level
178 final double xyExtent = Math.max(_track.getXRange().getRange(), _track.getYRange().getRange());
179 int zoomToSelect = -1;
182 _zoomDropdown.removeAllItems();
183 if (_useImageCheckbox.isSelected() && _mapSourceDropdown.getItemCount() > 0)
185 int currentSource = _mapSourceDropdown.getSelectedIndex();
186 for (int i=5; i<18; i++)
188 // How many pixels does this give?
189 final int zoomFactor = 1 << i;
190 final int pixCount = (int) (xyExtent * zoomFactor * 256);
191 if (pixCount > 100 // less than this isn't worth it
192 && pixCount < 4000 // don't want to run out of memory
193 && isZoomAvailable(i, MapSourceLibrary.getSource(currentSource)))
195 _zoomDropdown.addItem("" + i);
196 if (i == currentZoom) {
197 zoomToSelect = _zoomDropdown.getItemCount() - 1;
200 // else System.out.println("Not using zoom " + i + " because pixCount=" + pixCount + " and xyExtent=" + xyExtent);
203 _zoomDropdown.setSelectedIndex(zoomToSelect);
206 _okButton.setEnabled(!_useImageCheckbox.isSelected() ||
207 (_zoomDropdown.getItemCount() > 0 && _zoomDropdown.getSelectedIndex() >= 0));
208 updateImagePreview();
212 * @return true if it should be possible to use an image, false if no disk cache or cache empty
214 public static boolean isImagePossible()
216 String path = Config.getConfigString(Config.KEY_DISK_CACHE);
217 if (path != null && !path.equals(""))
219 File cacheDir = new File(path);
220 if (cacheDir.exists() && cacheDir.isDirectory())
222 // Check if there are any directories in the cache
223 for (File subdir : cacheDir.listFiles())
225 if (subdir.exists() && subdir.isDirectory()) {
235 * See if the requested zoom level is available
236 * @param inZoom zoom level
237 * @param inSource selected map source
238 * @return true if there is a zoom directory for each of the source's layers
240 private static boolean isZoomAvailable(int inZoom, MapSource inSource)
242 if (inSource == null) {return false;}
243 String path = Config.getConfigString(Config.KEY_DISK_CACHE);
244 if (path == null || path.equals("")) {
247 File cacheDir = new File(path);
248 if (!cacheDir.exists() || !cacheDir.isDirectory()) {
252 File layer0 = new File(cacheDir, inSource.getSiteName(0) + inZoom);
253 if (!layer0.exists() || !layer0.isDirectory() || !layer0.canRead()) {
256 // Second layer, if any
257 if (inSource.getNumLayers() > 1)
259 File layer1 = new File(cacheDir, inSource.getSiteName(1) + inZoom);
260 if (!layer1.exists() || !layer1.isDirectory() || !layer1.canRead()) {
270 * @return image definition object
272 public ImageDefinition getImageDefinition() {
277 * Make the dialog components to select the options
278 * @return Component holding gui elements
280 private Component makeDialogComponents()
282 JPanel panel = new JPanel();
283 panel.setLayout(new BorderLayout());
284 _useImageCheckbox = new JCheckBox(I18nManager.getText("dialog.baseimage.useimage"));
285 _useImageCheckbox.setBorder(BorderFactory.createEmptyBorder(4, 4, 6, 4));
286 _useImageCheckbox.setHorizontalAlignment(JLabel.CENTER);
287 _useImageCheckbox.addActionListener(new ActionListener() {
288 public void actionPerformed(ActionEvent arg0) {
292 panel.add(_useImageCheckbox, BorderLayout.NORTH);
294 // Outer panel with the grid and the map preview
295 _mainPanel = new JPanel();
296 _mainPanel.setLayout(new BorderLayout(1, 10));
297 // Central stuff with labels and dropdowns
298 JPanel controlsPanel = new JPanel();
299 controlsPanel.setLayout(new GridLayout(0, 2, 10, 4));
301 JLabel sourceLabel = new JLabel(I18nManager.getText("dialog.baseimage.mapsource") + ": ");
302 sourceLabel.setHorizontalAlignment(JLabel.RIGHT);
303 controlsPanel.add(sourceLabel);
304 _mapSourceDropdown = new JComboBox<String>();
305 _mapSourceDropdown.addItem("name of map source");
306 // Add listener to dropdown to change zoom levels
307 _mapSourceDropdown.addActionListener(new ActionListener() {
308 public void actionPerformed(ActionEvent arg0) {
312 controlsPanel.add(_mapSourceDropdown);
314 JLabel zoomLabel = new JLabel(I18nManager.getText("dialog.baseimage.zoom") + ": ");
315 zoomLabel.setHorizontalAlignment(JLabel.RIGHT);
316 controlsPanel.add(zoomLabel);
317 _zoomDropdown = new JComboBox<String>();
318 // Add action listener to enable ok button when zoom changed
319 _zoomDropdown.addActionListener(new ActionListener() {
320 public void actionPerformed(ActionEvent arg0) {
321 if (_zoomDropdown.getSelectedIndex() >= 0) {
322 _okButton.setEnabled(true);
323 updateImagePreview();
327 controlsPanel.add(_zoomDropdown);
328 _mainPanel.add(controlsPanel, BorderLayout.NORTH);
330 JPanel imagePanel = new JPanel();
331 imagePanel.setLayout(new BorderLayout(10, 1));
333 _previewPanel = new ImagePreviewPanel();
334 imagePanel.add(_previewPanel, BorderLayout.CENTER);
336 // Label panel on right
337 JPanel labelPanel = new JPanel();
338 labelPanel.setLayout(new BorderLayout());
339 JPanel downloadPanel = new JPanel();
340 downloadPanel.setLayout(new BorderLayout(4, 4));
341 _downloadTilesButton = new JButton(I18nManager.getText("button.load"));
342 _downloadTilesButton.addActionListener(new ActionListener() {
343 public void actionPerformed(ActionEvent arg0) {
344 downloadRemainingTiles();
347 _downloadTilesButton.setVisible(false);
348 downloadPanel.add(_downloadTilesButton, BorderLayout.NORTH);
349 _progressBar = new JProgressBar();
350 _progressBar.setIndeterminate(true);
351 _progressBar.setVisible(false);
352 downloadPanel.add(_progressBar, BorderLayout.SOUTH);
353 labelPanel.add(downloadPanel, BorderLayout.NORTH);
354 JPanel labelGridPanel = new JPanel();
355 labelGridPanel.setLayout(new GridLayout(0, 2, 10, 4));
356 labelGridPanel.add(new JLabel(I18nManager.getText("dialog.baseimage.tiles") + ": "));
357 _tilesFoundLabel = new JLabel("11 / 11");
358 labelGridPanel.add(_tilesFoundLabel);
359 labelGridPanel.add(new JLabel(I18nManager.getText("dialog.baseimage.size") + ": "));
360 _imageSizeLabel = new JLabel("1430");
361 labelGridPanel.add(_imageSizeLabel);
362 labelGridPanel.add(new JLabel(" ")); // just for spacing
363 labelPanel.add(labelGridPanel, BorderLayout.SOUTH);
364 imagePanel.add(labelPanel, BorderLayout.EAST);
366 _mainPanel.add(imagePanel, BorderLayout.CENTER);
367 panel.add(_mainPanel, BorderLayout.CENTER);
369 // OK, Cancel buttons
370 JPanel buttonPanel = new JPanel();
371 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
372 _okButton = new JButton(I18nManager.getText("button.ok"));
373 _okButton.addActionListener(new ActionListener() {
374 public void actionPerformed(ActionEvent e)
376 // Check values, maybe don't want to exit
377 if (!_useImageCheckbox.isSelected()
378 || (_mapSourceDropdown.getSelectedIndex() >= 0 && _zoomDropdown.getSelectedIndex() >= 0))
385 buttonPanel.add(_okButton);
386 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
387 cancelButton.addActionListener(new ActionListener() {
388 public void actionPerformed(ActionEvent e)
393 buttonPanel.add(cancelButton);
394 panel.add(buttonPanel, BorderLayout.SOUTH);
396 // Listener to close dialog if escape pressed
397 KeyAdapter closer = new KeyAdapter() {
398 public void keyReleased(KeyEvent e)
400 if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
405 _useImageCheckbox.addKeyListener(closer);
406 _mapSourceDropdown.addKeyListener(closer);
407 _zoomDropdown.addKeyListener(closer);
408 _okButton.addKeyListener(closer);
409 cancelButton.addKeyListener(closer);
415 * Use the selected settings to make a preview image and (asynchronously) update the preview panel
417 private void updateImagePreview()
420 _downloadTilesButton.setVisible(false);
421 _tilesFoundLabel.setText("");
422 _imageSizeLabel.setText("");
423 if (_useImageCheckbox.isSelected() && _mapSourceDropdown.getSelectedIndex() >= 0
424 && _zoomDropdown.getItemCount() > 0 && _zoomDropdown.getSelectedIndex() >= 0)
426 _previewPanel.startLoading();
427 // Launch a separate thread to create an image and pass it to the preview panel
428 new Thread(this).start();
432 _previewPanel.setImage(null);
437 * Store the selected details in the variables
439 private void storeValues()
441 // Store values of controls in variables
442 _imageDef.setUseImage(_useImageCheckbox.isSelected(),
443 _mapSourceDropdown.getSelectedIndex(),
444 getSelectedZoomLevel());
445 // Inform parent that details have changed
446 _parent.baseImageChanged();
450 * Run method for separate thread. Uses the current dialog parameters
451 * to trigger a call to the Grouter, and pass the image to the preview panel
455 // Remember the current dropdown indices, so we know whether they've changed or not
456 final int mapIndex = _mapSourceDropdown.getSelectedIndex();
457 final int zoomIndex = _zoomDropdown.getSelectedIndex();
458 if (!_useImageCheckbox.isSelected() || mapIndex < 0 || zoomIndex < 0) {return;}
460 // Get the map source from the index
461 MapSource mapSource = MapSourceLibrary.getSource(mapIndex);
463 // Use the Grouter to create an image (slow, blocks thread)
464 GroutedImage groutedImage = _grouter.createMapImage(_track, mapSource, getSelectedZoomLevel());
466 // If the dialog hasn't changed, pass the generated image to the preview panel
467 if (_useImageCheckbox.isSelected()
468 && _mapSourceDropdown.getSelectedIndex() == mapIndex
469 && _zoomDropdown.getSelectedIndex() == zoomIndex
470 && groutedImage != null)
472 _previewPanel.setImage(groutedImage);
473 final int numTilesRemaining = groutedImage.getNumTilesTotal() - groutedImage.getNumTilesUsed();
474 final boolean offerDownload = numTilesRemaining > 0 && numTilesRemaining < 50;
475 // Set values of labels
476 _downloadTilesButton.setVisible(offerDownload);
477 _downloadTilesButton.setEnabled(offerDownload);
478 _tilesFoundLabel.setText(groutedImage.getNumTilesUsed() + " / " + groutedImage.getNumTilesTotal());
479 if (groutedImage.getImageSize() > 0) {
480 _imageSizeLabel.setText("" + groutedImage.getImageSize());
483 _imageSizeLabel.setText("");
488 _previewPanel.setImage(null);
490 _downloadTilesButton.setVisible(false);
491 _tilesFoundLabel.setText("");
492 _imageSizeLabel.setText("");
497 * @return zoom level selected in the dropdown
499 private int getSelectedZoomLevel()
503 zoomLevel = Integer.parseInt(_zoomDropdown.getSelectedItem().toString());
505 catch (Exception e) {
506 System.err.println("Exception: " + e.getClass().getName() + " : " + e.getMessage());
512 * @return true if any map data has been found for the image
514 public boolean getFoundData()
516 return _imageDef.getUseImage() && _imageDef.getZoom() > 0
517 && _previewPanel != null && _previewPanel.getTilesFound();
521 * @return true if selected zoom is valid for the current track (based only on pixel size)
523 public boolean isSelectedZoomValid()
525 final double xyExtent = Math.max(_track.getXRange().getRange(), _track.getYRange().getRange());
526 // How many pixels does this give?
527 final int zoomFactor = 1 << _imageDef.getZoom();
528 final int pixCount = (int) (xyExtent * zoomFactor * 256);
529 return (pixCount > 100 // less than this isn't worth it
530 && pixCount < 4000); // don't want to run out of memory
534 * @return the map grouter for retrieval of generated image
536 public MapGrouter getGrouter()
542 * @return method triggered by "download" button, to asynchronously download the missing tiles
544 private void downloadRemainingTiles()
546 _downloadTilesButton.setEnabled(false);
547 new Thread(new Runnable() {
550 _progressBar.setVisible(true);
551 // Use a grouter to get all tiles from the TileManager, including downloading
552 MapGrouter grouter = new MapGrouter();
553 final int mapIndex = _mapSourceDropdown.getSelectedIndex();
554 if (!_useImageCheckbox.isSelected() || mapIndex < 0) {return;}
555 MapSource mapSource = MapSourceLibrary.getSource(mapIndex);
556 grouter.createMapImage(_track, mapSource, getSelectedZoomLevel(), true);
557 _progressBar.setVisible(false);
558 // And then refresh the dialog
559 _grouter.clearMapImage();
560 updateImagePreview();