1 package tim.prune.save;
3 import java.awt.BorderLayout;
5 import java.awt.Component;
6 import java.awt.FlowLayout;
7 import java.awt.GridLayout;
8 import java.awt.event.ActionEvent;
9 import java.awt.event.ActionListener;
10 import java.awt.event.KeyAdapter;
11 import java.awt.event.KeyEvent;
14 import javax.swing.BorderFactory;
15 import javax.swing.JButton;
16 import javax.swing.JCheckBox;
17 import javax.swing.JComboBox;
18 import javax.swing.JDialog;
19 import javax.swing.JLabel;
20 import javax.swing.JPanel;
22 import tim.prune.DataSubscriber;
23 import tim.prune.I18nManager;
24 import tim.prune.config.Config;
25 import tim.prune.data.Track;
26 import tim.prune.gui.map.MapSource;
27 import tim.prune.gui.map.MapSourceLibrary;
30 * Dialog to let you choose the parameters for a base image
33 public class BaseImageConfigDialog implements Runnable
35 /** Parent to notify */
36 private DataSubscriber _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 _mapSourceDropdown = null;
49 /** Dropdown for zoom levels */
50 private JComboBox _zoomDropdown = null;
51 /** Warning label that image is incomplete */
52 private JLabel _imageIncompleteLabel = null;
53 /** Label for number of tiles found */
54 private JLabel _tilesFoundLabel = null;
55 /** Label for image size in pixels */
56 private JLabel _imageSizeLabel = null;
57 /** Image preview panel */
58 private ImagePreviewPanel _previewPanel = null;
59 /** OK button, needs to be enabled/disabled */
60 private JButton _okButton = null;
61 /** Flag for rebuilding dialog, don't bother refreshing and recalculating */
62 private boolean _rebuilding = false;
63 /** Cached values to allow cancellation of dialog */
64 private boolean _useImage = false;
65 private int _sourceIndex = 0;
66 private int _zoomLevel = 0;
71 * @param inParent parent object to notify on completion of dialog
72 * @param inParentDialog parent dialog
73 * @param inTrack track object
75 public BaseImageConfigDialog(DataSubscriber inParent, JDialog inParentDialog, Track inTrack)
78 _parentDialog = inParentDialog;
79 _dialog = new JDialog(inParentDialog, I18nManager.getText("dialog.baseimage.title"), true);
80 _dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
81 _dialog.getContentPane().add(makeDialogComponents());
92 _dialog.setLocationRelativeTo(_parentDialog);
93 _dialog.setVisible(true);
97 * Begin the function with a default of using an image
99 public void beginWithImageYes()
102 _useImageCheckbox.setSelected(true);
104 _dialog.setVisible(true);
108 * Initialise the dialog from the cached values
110 private void initDialog()
113 _useImageCheckbox.setSelected(_useImage);
114 // Populate the dropdown of map sources from the library in case it has changed
115 _mapSourceDropdown.removeAllItems();
116 for (int i=0; i<MapSourceLibrary.getNumSources(); i++)
118 _mapSourceDropdown.addItem(MapSourceLibrary.getSource(i).getName());
120 if (_sourceIndex < 0 || _sourceIndex >= _mapSourceDropdown.getItemCount()) {
123 _mapSourceDropdown.setSelectedIndex(_sourceIndex);
128 for (int i=0; i<_zoomDropdown.getItemCount(); i++)
130 String item = _zoomDropdown.getItemAt(i).toString();
132 if (Integer.parseInt(item) == _zoomLevel) {
133 _zoomDropdown.setSelectedIndex(i);
137 catch (NumberFormatException nfe) {}
145 * Update the visibility of the controls, and update the zoom dropdown based on the selected map source
147 private void refreshDialog()
149 _mainPanel.setVisible(_useImageCheckbox.isSelected());
150 // Exit if we're in the middle of something
151 if (_rebuilding) {return;}
154 currentZoom = Integer.parseInt(_zoomDropdown.getSelectedItem().toString());
156 catch (Exception nfe) {}
157 // Get the extent of the track so we can work out how big the images are going to be for each zoom level
158 // System.out.println("Ranges are: x=" + _track.getXRange().getRange() + ", y=" + _track.getYRange().getRange());
159 final double xyExtent = Math.max(_track.getXRange().getRange(), _track.getYRange().getRange());
160 int zoomToSelect = -1;
163 _zoomDropdown.removeAllItems();
164 if (_useImageCheckbox.isSelected() && _mapSourceDropdown.getItemCount() > 0)
166 int currentSource = _mapSourceDropdown.getSelectedIndex();
167 for (int i=5; i<18; i++)
169 // How many pixels does this give?
170 final int zoomFactor = 1 << i;
171 final int pixCount = (int) (xyExtent * zoomFactor * 256);
172 if (pixCount > 100 // less than this isn't worth it
173 && pixCount < 4000 // don't want to run out of memory
174 && isZoomAvailable(i, MapSourceLibrary.getSource(currentSource)))
176 _zoomDropdown.addItem("" + i);
177 if (i == currentZoom) {
178 zoomToSelect = _zoomDropdown.getItemCount() - 1;
181 // else System.out.println("Not using zoom " + i + " because pixCount=" + pixCount + " and xyExtent=" + xyExtent);
184 _zoomDropdown.setSelectedIndex(zoomToSelect);
187 _okButton.setEnabled(!_useImageCheckbox.isSelected() ||
188 (_zoomDropdown.getItemCount() > 0 && _zoomDropdown.getSelectedIndex() >= 0));
189 updateImagePreview();
193 * @return true if it should be possible to use an image, false if no disk cache or cache empty
195 public static boolean isImagePossible()
197 String path = Config.getConfigString(Config.KEY_DISK_CACHE);
198 if (path != null && !path.equals(""))
200 File cacheDir = new File(path);
201 if (cacheDir.exists() && cacheDir.isDirectory())
203 // Check if there are any directories in the cache
204 for (File subdir : cacheDir.listFiles())
206 if (subdir.exists() && subdir.isDirectory()) {
216 * See if the requested zoom level is available
217 * @param inZoom zoom level
218 * @param inSource selected map source
219 * @return true if there is a zoom directory for each of the source's layers
221 private static boolean isZoomAvailable(int inZoom, MapSource inSource)
223 if (inSource == null) {return false;}
224 String path = Config.getConfigString(Config.KEY_DISK_CACHE);
225 if (path == null || path.equals("")) {
228 File cacheDir = new File(path);
229 if (!cacheDir.exists() || !cacheDir.isDirectory()) {
233 File layer0 = new File(cacheDir, inSource.getSiteName(0) + inZoom);
234 if (!layer0.exists() || !layer0.isDirectory() || !layer0.canRead()) {
237 // Second layer, if any
238 if (inSource.getNumLayers() > 1)
240 File layer1 = new File(cacheDir, inSource.getSiteName(1) + inZoom);
241 if (!layer1.exists() || !layer1.isDirectory() || !layer1.canRead()) {
251 * @return true if image has been selected
253 public boolean useImage() {
258 * @return index of selected image source
260 public int getSourceIndex() {
265 * @return selected zoom level
267 public int getZoomLevel() {
272 * Make the dialog components to select the options
273 * @return Component holding gui elements
275 private Component makeDialogComponents()
277 JPanel panel = new JPanel();
278 panel.setLayout(new BorderLayout());
279 _useImageCheckbox = new JCheckBox(I18nManager.getText("dialog.baseimage.useimage"));
280 _useImageCheckbox.setBorder(BorderFactory.createEmptyBorder(4, 4, 6, 4));
281 _useImageCheckbox.setHorizontalAlignment(JLabel.CENTER);
282 _useImageCheckbox.addActionListener(new ActionListener() {
283 public void actionPerformed(ActionEvent arg0) {
287 panel.add(_useImageCheckbox, BorderLayout.NORTH);
289 // Outer panel with the grid and the map preview
290 _mainPanel = new JPanel();
291 _mainPanel.setLayout(new BorderLayout(1, 10));
292 // Central stuff with labels and dropdowns
293 JPanel controlsPanel = new JPanel();
294 controlsPanel.setLayout(new GridLayout(0, 2, 10, 4));
296 JLabel sourceLabel = new JLabel(I18nManager.getText("dialog.baseimage.mapsource") + ": ");
297 sourceLabel.setHorizontalAlignment(JLabel.RIGHT);
298 controlsPanel.add(sourceLabel);
299 _mapSourceDropdown = new JComboBox();
300 _mapSourceDropdown.addItem("name of map source");
301 // Add listener to dropdown to change zoom levels
302 _mapSourceDropdown.addActionListener(new ActionListener() {
303 public void actionPerformed(ActionEvent arg0) {
307 controlsPanel.add(_mapSourceDropdown);
309 JLabel zoomLabel = new JLabel(I18nManager.getText("dialog.baseimage.zoom") + ": ");
310 zoomLabel.setHorizontalAlignment(JLabel.RIGHT);
311 controlsPanel.add(zoomLabel);
312 _zoomDropdown = new JComboBox();
313 // Add action listener to enable ok button when zoom changed
314 _zoomDropdown.addActionListener(new ActionListener() {
315 public void actionPerformed(ActionEvent arg0) {
316 if (_zoomDropdown.getSelectedIndex() >= 0) {
317 _okButton.setEnabled(true);
318 updateImagePreview();
322 controlsPanel.add(_zoomDropdown);
323 _mainPanel.add(controlsPanel, BorderLayout.NORTH);
325 JPanel imagePanel = new JPanel();
326 imagePanel.setLayout(new BorderLayout(10, 1));
328 _previewPanel = new ImagePreviewPanel();
329 imagePanel.add(_previewPanel, BorderLayout.CENTER);
331 // Label panel on right
332 JPanel labelPanel = new JPanel();
333 labelPanel.setLayout(new BorderLayout());
334 _imageIncompleteLabel = new JLabel(I18nManager.getText("dialog.baseimage.incomplete"));
335 _imageIncompleteLabel.setForeground(Color.RED);
336 _imageIncompleteLabel.setVisible(false);
337 labelPanel.add(_imageIncompleteLabel, BorderLayout.NORTH);
338 JPanel labelGridPanel = new JPanel();
339 labelGridPanel.setLayout(new GridLayout(0, 2, 10, 4));
340 labelGridPanel.add(new JLabel(I18nManager.getText("dialog.baseimage.tiles") + ": "));
341 _tilesFoundLabel = new JLabel("11 / 11");
342 labelGridPanel.add(_tilesFoundLabel);
343 labelGridPanel.add(new JLabel(I18nManager.getText("dialog.baseimage.size") + ": "));
344 _imageSizeLabel = new JLabel("1430");
345 labelGridPanel.add(_imageSizeLabel);
346 labelGridPanel.add(new JLabel(" ")); // just for spacing
347 labelPanel.add(labelGridPanel, BorderLayout.SOUTH);
348 imagePanel.add(labelPanel, BorderLayout.EAST);
350 _mainPanel.add(imagePanel, BorderLayout.CENTER);
351 panel.add(_mainPanel, BorderLayout.CENTER);
353 // OK, Cancel buttons
354 JPanel buttonPanel = new JPanel();
355 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
356 _okButton = new JButton(I18nManager.getText("button.ok"));
357 _okButton.addActionListener(new ActionListener() {
358 public void actionPerformed(ActionEvent e)
360 // Check values, maybe don't want to exit
361 if (!_useImageCheckbox.isSelected()
362 || (_mapSourceDropdown.getSelectedIndex() >= 0 && _zoomDropdown.getSelectedIndex() >= 0))
369 buttonPanel.add(_okButton);
370 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
371 cancelButton.addActionListener(new ActionListener() {
372 public void actionPerformed(ActionEvent e)
377 buttonPanel.add(cancelButton);
378 panel.add(buttonPanel, BorderLayout.SOUTH);
380 // Listener to close dialog if escape pressed
381 KeyAdapter closer = new KeyAdapter() {
382 public void keyReleased(KeyEvent e)
384 if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
389 _useImageCheckbox.addKeyListener(closer);
390 _mapSourceDropdown.addKeyListener(closer);
391 _zoomDropdown.addKeyListener(closer);
392 _okButton.addKeyListener(closer);
393 cancelButton.addKeyListener(closer);
399 * Use the selected settings to make a preview image and (asynchronously) update the preview panel
401 private void updateImagePreview()
404 _imageIncompleteLabel.setVisible(false);
405 _tilesFoundLabel.setText("");
406 _imageSizeLabel.setText("");
407 if (_useImageCheckbox.isSelected() && _mapSourceDropdown.getSelectedIndex() >= 0
408 && _zoomDropdown.getItemCount() > 0 && _zoomDropdown.getSelectedIndex() >= 0)
410 _previewPanel.startLoading();
411 // Launch a separate thread to create an image and pass it to the preview panel
412 new Thread(this).start();
416 _previewPanel.setImage(null);
421 * Store the selected details in the variables
423 private void storeValues()
425 // Store values of controls in variables
426 _useImage = _useImageCheckbox.isSelected();
427 _sourceIndex = _mapSourceDropdown.getSelectedIndex();
429 String zoomStr = _zoomDropdown.getSelectedItem().toString();
430 _zoomLevel = Integer.parseInt(zoomStr);
432 catch (Exception nfe) {
435 // Call parent to retrieve values
436 _parent.dataUpdated(DataSubscriber.ALL);
440 * Run method for separate thread. Uses the current dialog parameters
441 * to trigger a call to the Grouter, and pass the image to the preview panel
445 // Remember the current dropdown indices, so we know whether they've changed or not
446 final int mapIndex = _mapSourceDropdown.getSelectedIndex();
447 final int zoomIndex = _zoomDropdown.getSelectedIndex();
448 if (!_useImageCheckbox.isSelected() || mapIndex < 0 || zoomIndex < 0) {return;}
450 // Get the map source and zoom level
451 MapSource mapSource = MapSourceLibrary.getSource(mapIndex);
454 zoomLevel = Integer.parseInt(_zoomDropdown.getSelectedItem().toString());
456 catch (Exception e) {}
458 // Use the Grouter to create an image (slow, blocks thread)
459 GroutedImage groutedImage = MapGrouter.createMapImage(_track, mapSource, zoomLevel);
461 // If the dialog hasn't changed, pass the generated image to the preview panel
462 if (_useImageCheckbox.isSelected()
463 && _mapSourceDropdown.getSelectedIndex() == mapIndex
464 && _zoomDropdown.getSelectedIndex() == zoomIndex
465 && groutedImage != null)
467 _previewPanel.setImage(groutedImage);
468 // Set values of labels
469 _imageIncompleteLabel.setVisible(!groutedImage.isComplete());
470 _tilesFoundLabel.setText(groutedImage.getNumTilesUsed() + " / " + groutedImage.getNumTilesTotal());
471 if (groutedImage.getImageSize() > 0) {
472 _imageSizeLabel.setText("" + groutedImage.getImageSize());
475 _imageSizeLabel.setText("");
480 _previewPanel.setImage(null);
482 _imageIncompleteLabel.setVisible(false);
483 _tilesFoundLabel.setText("");
484 _imageSizeLabel.setText("");
489 * @return true if any map data has been found for the image
491 public boolean getFoundData()
493 return _useImage && _zoomLevel > 0 && _previewPanel != null && _previewPanel.getTilesFound();