]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/save/BaseImageConfigDialog.java
514ebd79ec46d8c8fa5203d9f460872ddbc66d49
[GpsPrune.git] / tim / prune / save / BaseImageConfigDialog.java
1 package tim.prune.save;
2
3 import java.awt.BorderLayout;
4 import java.awt.Color;
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;
12 import java.io.File;
13
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;
21
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;
28
29 /**
30  * Dialog to let you choose the parameters for a base image
31  * (source and zoom)
32  */
33 public class BaseImageConfigDialog implements Runnable
34 {
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;
41         /** Dialog to show */
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;
67
68
69         /**
70          * Constructor
71          * @param inParent parent object to notify on completion of dialog
72          * @param inParentDialog parent dialog
73          * @param inTrack track object
74          */
75         public BaseImageConfigDialog(DataSubscriber inParent, JDialog inParentDialog, Track inTrack)
76         {
77                 _parent = inParent;
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());
82                 _dialog.pack();
83                 _track = inTrack;
84         }
85
86         /**
87          * Begin the function
88          */
89         public void begin()
90         {
91                 initDialog();
92                 _dialog.setLocationRelativeTo(_parentDialog);
93                 _dialog.setVisible(true);
94         }
95
96         /**
97          * Begin the function with a default of using an image
98          */
99         public void beginWithImageYes()
100         {
101                 initDialog();
102                 _useImageCheckbox.setSelected(true);
103                 refreshDialog();
104                 _dialog.setVisible(true);
105         }
106
107         /**
108          * Initialise the dialog from the cached values
109          */
110         private void initDialog()
111         {
112                 _rebuilding = true;
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++)
117                 {
118                         _mapSourceDropdown.addItem(MapSourceLibrary.getSource(i).getName());
119                 }
120                 if (_sourceIndex < 0 || _sourceIndex >= _mapSourceDropdown.getItemCount()) {
121                         _sourceIndex = 0;
122                 }
123                 _mapSourceDropdown.setSelectedIndex(_sourceIndex);
124
125                 // Zoom level
126                 if (_useImage)
127                 {
128                         for (int i=0; i<_zoomDropdown.getItemCount(); i++)
129                         {
130                                 String item = _zoomDropdown.getItemAt(i).toString();
131                                 try {
132                                         if (Integer.parseInt(item) == _zoomLevel) {
133                                                 _zoomDropdown.setSelectedIndex(i);
134                                                 break;
135                                         }
136                                 }
137                                 catch (NumberFormatException nfe) {}
138                         }
139                 }
140                 _rebuilding = false;
141                 refreshDialog();
142         }
143
144         /**
145          * Update the visibility of the controls, and update the zoom dropdown based on the selected map source
146          */
147         private void refreshDialog()
148         {
149                 _mainPanel.setVisible(_useImageCheckbox.isSelected());
150                 // Exit if we're in the middle of something
151                 if (_rebuilding) {return;}
152                 int currentZoom = 0;
153                 try {
154                         currentZoom = Integer.parseInt(_zoomDropdown.getSelectedItem().toString());
155                 }
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;
161
162                 _rebuilding = true;
163                 _zoomDropdown.removeAllItems();
164                 if (_useImageCheckbox.isSelected() && _mapSourceDropdown.getItemCount() > 0)
165                 {
166                         int currentSource = _mapSourceDropdown.getSelectedIndex();
167                         for (int i=5; i<18; i++)
168                         {
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)))
175                                 {
176                                         _zoomDropdown.addItem("" + i);
177                                         if (i == currentZoom) {
178                                                 zoomToSelect = _zoomDropdown.getItemCount() - 1;
179                                         }
180                                 }
181                                 // else System.out.println("Not using zoom " + i + " because pixCount=" + pixCount + " and xyExtent=" + xyExtent);
182                         }
183                 }
184                 _zoomDropdown.setSelectedIndex(zoomToSelect);
185                 _rebuilding = false;
186
187                 _okButton.setEnabled(!_useImageCheckbox.isSelected() ||
188                         (_zoomDropdown.getItemCount() > 0 && _zoomDropdown.getSelectedIndex() >= 0));
189                 updateImagePreview();
190         }
191
192         /**
193          * @return true if it should be possible to use an image, false if no disk cache or cache empty
194          */
195         public static boolean isImagePossible()
196         {
197                 String path = Config.getConfigString(Config.KEY_DISK_CACHE);
198                 if (path != null && !path.equals(""))
199                 {
200                         File cacheDir = new File(path);
201                         if (cacheDir.exists() && cacheDir.isDirectory())
202                         {
203                                 // Check if there are any directories in the cache
204                                 for (File subdir : cacheDir.listFiles())
205                                 {
206                                         if (subdir.exists() && subdir.isDirectory()) {
207                                                 return true;
208                                         }
209                                 }
210                         }
211                 }
212                 return false;
213         }
214
215         /**
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
220          */
221         private static boolean isZoomAvailable(int inZoom, MapSource inSource)
222         {
223                 if (inSource == null) {return false;}
224                 String path = Config.getConfigString(Config.KEY_DISK_CACHE);
225                 if (path == null || path.equals("")) {
226                         return false;
227                 }
228                 File cacheDir = new File(path);
229                 if (!cacheDir.exists() || !cacheDir.isDirectory()) {
230                         return false;
231                 }
232                 // First layer
233                 File layer0 = new File(cacheDir, inSource.getSiteName(0) + inZoom);
234                 if (!layer0.exists() || !layer0.isDirectory() || !layer0.canRead()) {
235                         return false;
236                 }
237                 // Second layer, if any
238                 if (inSource.getNumLayers() > 1)
239                 {
240                         File layer1 = new File(cacheDir, inSource.getSiteName(1) + inZoom);
241                         if (!layer1.exists() || !layer1.isDirectory() || !layer1.canRead()) {
242                                 return false;
243                         }
244                 }
245                 // must be ok
246                 return true;
247         }
248
249
250         /**
251          * @return true if image has been selected
252          */
253         public boolean useImage() {
254                 return _useImage;
255         }
256
257         /**
258          * @return index of selected image source
259          */
260         public int getSourceIndex() {
261                 return _sourceIndex;
262         }
263
264         /**
265          * @return selected zoom level
266          */
267         public int getZoomLevel() {
268                 return _zoomLevel;
269         }
270
271         /**
272          * Make the dialog components to select the options
273          * @return Component holding gui elements
274          */
275         private Component makeDialogComponents()
276         {
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) {
284                                 refreshDialog();
285                         }
286                 });
287                 panel.add(_useImageCheckbox, BorderLayout.NORTH);
288
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));
295                 // map source
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) {
304                                 refreshDialog();
305                         }
306                 });
307                 controlsPanel.add(_mapSourceDropdown);
308                 // zoom level
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();
319                                 }
320                         }
321                 });
322                 controlsPanel.add(_zoomDropdown);
323                 _mainPanel.add(controlsPanel, BorderLayout.NORTH);
324
325                 JPanel imagePanel = new JPanel();
326                 imagePanel.setLayout(new BorderLayout(10, 1));
327                 // image preview
328                 _previewPanel = new ImagePreviewPanel();
329                 imagePanel.add(_previewPanel, BorderLayout.CENTER);
330
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);
349
350                 _mainPanel.add(imagePanel, BorderLayout.CENTER);
351                 panel.add(_mainPanel, BorderLayout.CENTER);
352
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)
359                         {
360                                 // Check values, maybe don't want to exit
361                                 if (!_useImageCheckbox.isSelected()
362                                         || (_mapSourceDropdown.getSelectedIndex() >= 0 && _zoomDropdown.getSelectedIndex() >= 0))
363                                 {
364                                         storeValues();
365                                         _dialog.dispose();
366                                 }
367                         }
368                 });
369                 buttonPanel.add(_okButton);
370                 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
371                 cancelButton.addActionListener(new ActionListener() {
372                         public void actionPerformed(ActionEvent e)
373                         {
374                                 _dialog.dispose();
375                         }
376                 });
377                 buttonPanel.add(cancelButton);
378                 panel.add(buttonPanel, BorderLayout.SOUTH);
379
380                 // Listener to close dialog if escape pressed
381                 KeyAdapter closer = new KeyAdapter() {
382                         public void keyReleased(KeyEvent e)
383                         {
384                                 if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
385                                         _dialog.dispose();
386                                 }
387                         }
388                 };
389                 _useImageCheckbox.addKeyListener(closer);
390                 _mapSourceDropdown.addKeyListener(closer);
391                 _zoomDropdown.addKeyListener(closer);
392                 _okButton.addKeyListener(closer);
393                 cancelButton.addKeyListener(closer);
394
395                 return panel;
396         }
397
398         /**
399          * Use the selected settings to make a preview image and (asynchronously) update the preview panel
400          */
401         private void updateImagePreview()
402         {
403                 // Clear labels
404                 _imageIncompleteLabel.setVisible(false);
405                 _tilesFoundLabel.setText("");
406                 _imageSizeLabel.setText("");
407                 if (_useImageCheckbox.isSelected() && _mapSourceDropdown.getSelectedIndex() >= 0
408                         && _zoomDropdown.getItemCount() > 0 && _zoomDropdown.getSelectedIndex() >= 0)
409                 {
410                         _previewPanel.startLoading();
411                         // Launch a separate thread to create an image and pass it to the preview panel
412                         new Thread(this).start();
413                 }
414                 else {
415                         // clear preview
416                         _previewPanel.setImage(null);
417                 }
418         }
419
420         /**
421          * Store the selected details in the variables
422          */
423         private void storeValues()
424         {
425                 // Store values of controls in variables
426                 _useImage = _useImageCheckbox.isSelected();
427                 _sourceIndex = _mapSourceDropdown.getSelectedIndex();
428                 try {
429                         String zoomStr = _zoomDropdown.getSelectedItem().toString();
430                         _zoomLevel = Integer.parseInt(zoomStr);
431                 }
432                 catch (Exception nfe) {
433                         _zoomLevel = 0;
434                 }
435                 // Call parent to retrieve values
436                 _parent.dataUpdated(DataSubscriber.ALL);
437         }
438
439         /**
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
442          */
443         public void run()
444         {
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;}
449
450                 // Get the map source and zoom level
451                 MapSource mapSource = MapSourceLibrary.getSource(mapIndex);
452                 int zoomLevel = 0;
453                 try {
454                         zoomLevel = Integer.parseInt(_zoomDropdown.getSelectedItem().toString());
455                 }
456                 catch (Exception e) {}
457
458                 // Use the Grouter to create an image (slow, blocks thread)
459                 GroutedImage groutedImage = MapGrouter.createMapImage(_track, mapSource, zoomLevel);
460
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)
466                 {
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());
473                         }
474                         else {
475                                 _imageSizeLabel.setText("");
476                         }
477                 }
478                 else
479                 {
480                         _previewPanel.setImage(null);
481                         // Clear labels
482                         _imageIncompleteLabel.setVisible(false);
483                         _tilesFoundLabel.setText("");
484                         _imageSizeLabel.setText("");
485                 }
486         }
487
488         /**
489          * @return true if any map data has been found for the image
490          */
491         public boolean getFoundData()
492         {
493                 return _useImage && _zoomLevel > 0 && _previewPanel != null && _previewPanel.getTilesFound();
494         }
495 }