]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/function/AddMapSourceDialog.java
52a8874cf100b355b8b0d6acac60215dcfe29202
[GpsPrune.git] / tim / prune / function / AddMapSourceDialog.java
1 package tim.prune.function;
2
3 import java.awt.BorderLayout;
4 import java.awt.CardLayout;
5 import java.awt.Component;
6 import java.awt.FlowLayout;
7 import java.awt.GridBagConstraints;
8 import java.awt.GridBagLayout;
9 import java.awt.GridLayout;
10 import java.awt.Insets;
11 import java.awt.event.ActionEvent;
12 import java.awt.event.ActionListener;
13 import java.awt.event.KeyAdapter;
14 import java.awt.event.KeyEvent;
15
16 import javax.swing.BorderFactory;
17 import javax.swing.ButtonGroup;
18 import javax.swing.JButton;
19 import javax.swing.JComboBox;
20 import javax.swing.JDialog;
21 import javax.swing.JLabel;
22 import javax.swing.JPanel;
23 import javax.swing.JRadioButton;
24 import javax.swing.JTextField;
25
26 import tim.prune.I18nManager;
27 import tim.prune.gui.map.CloudmadeMapSource;
28 import tim.prune.gui.map.MapSource;
29 import tim.prune.gui.map.MapSourceLibrary;
30 import tim.prune.gui.map.OsmMapSource;
31
32 /**
33  * Class to handle the adding of a new map source
34  */
35 public class AddMapSourceDialog
36 {
37         private SetMapBgFunction _parent = null;
38         private JDialog _addDialog = null;
39         private JRadioButton[] _sourceTypeRadios = null;
40         private JPanel _cards = null;
41         private MapSource _originalSource = null;
42         // controls for osm panel
43         private JTextField _oNameField = null;
44         private JTextField _baseUrlField = null, _topUrlField = null;
45         private JRadioButton[] _baseTypeRadios = null, _topTypeRadios = null;
46         private JComboBox _oZoomCombo = null;
47         // controls for cloudmade panel
48         private JTextField _cNameField = null;
49         private JTextField _cStyleField = null;
50         private JComboBox _cZoomCombo = null;
51         private JButton _okButton = null;
52
53         /** array of file types */
54         private static final String[] FILE_TYPES = {"png", "jpg", "gif"};
55
56
57         /**
58          * Constructor
59          * @param inParent parent dialog
60          */
61         public AddMapSourceDialog(JDialog inParentDialog, SetMapBgFunction inParentFunction)
62         {
63                 _parent = inParentFunction;
64                 _addDialog = new JDialog(inParentDialog, I18nManager.getText("dialog.addmapsource.title"), true);
65                 _addDialog.add(makeDialogComponents());
66                 _addDialog.setLocationRelativeTo(inParentDialog);
67                 _addDialog.pack();
68         }
69
70
71         /**
72          * Create dialog components
73          * @return Panel containing all gui elements in dialog
74          */
75         private Component makeDialogComponents()
76         {
77                 JPanel dialogPanel = new JPanel();
78                 dialogPanel.setLayout(new BorderLayout());
79                 // Top panel with two radio buttons to select source type
80                 JPanel radioPanel = new JPanel();
81                 ButtonGroup radioGroup = new ButtonGroup();
82                 radioPanel.setLayout(new GridLayout(1, 0));
83                 _sourceTypeRadios = new JRadioButton[2];
84                 _sourceTypeRadios[0] = new JRadioButton("Openstreetmap");
85                 radioGroup.add(_sourceTypeRadios[0]);
86                 radioPanel.add(_sourceTypeRadios[0]);
87                 _sourceTypeRadios[1] = new JRadioButton("Cloudmade");
88                 radioGroup.add(_sourceTypeRadios[1]);
89                 radioPanel.add(_sourceTypeRadios[1]);
90                 _sourceTypeRadios[0].setSelected(true);
91                 // listener for clicks on type radios
92                 ActionListener typeListener = new ActionListener() {
93                         public void actionPerformed(ActionEvent arg0) {
94                                 onRadioClicked();
95                         }
96                 };
97                 _sourceTypeRadios[0].addActionListener(typeListener);
98                 _sourceTypeRadios[1].addActionListener(typeListener);
99                 radioPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
100                 dialogPanel.add(radioPanel, BorderLayout.NORTH);
101
102                 _cards = new JPanel();
103                 _cards.setLayout(new CardLayout());
104                 // listener
105                 KeyAdapter keyListener = new KeyAdapter() {
106                         public void keyReleased(KeyEvent e) {
107                                 super.keyReleased(e);
108                                 if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
109                                         _addDialog.dispose();
110                                 }
111                                 else {
112                                         enableOK();
113                                 }
114                         }
115                 };
116                 // Listener for any gui changes (to enable ok when anything changes on an edit)
117                 ActionListener okEnabler = new ActionListener() {
118                         public void actionPerformed(ActionEvent arg0) {
119                                 enableOK();
120                         }
121                 };
122
123                 // openstreetmap panel
124                 JPanel osmPanel = new JPanel();
125                 osmPanel.setLayout(new BorderLayout());
126                 osmPanel.setBorder(BorderFactory.createEmptyBorder(6, 3, 4, 3));
127                 JPanel gbPanel = new JPanel();
128                 GridBagLayout gridbag = new GridBagLayout();
129                 GridBagConstraints c = new GridBagConstraints();
130                 gbPanel.setLayout(gridbag);
131                 c.gridx = 0; c.gridy = 0;
132                 c.gridheight = 1; c.gridwidth = 1;
133                 c.weightx = 0.0; c.weighty = 0.0;
134                 c.ipadx = 3; c.ipady = 5;
135                 c.insets = new Insets(0, 0, 5, 0);
136                 c.anchor = GridBagConstraints.FIRST_LINE_START;
137                 gbPanel.add(new JLabel(I18nManager.getText("dialog.addmapsource.sourcename")), c);
138                 _oNameField = new JTextField(18);
139                 _oNameField.addKeyListener(keyListener);
140                 c.gridx = 1; c.weightx = 1.0;
141                 gbPanel.add(_oNameField, c);
142                 // Base layer
143                 c.gridx = 0; c.gridy = 1;
144                 c.weightx = 0.0;
145                 c.insets = new Insets(0, 0, 0, 0);
146                 gbPanel.add(new JLabel(I18nManager.getText("dialog.addmapsource.layer1url")), c);
147                 _baseUrlField = new JTextField(18);
148                 _baseUrlField.addKeyListener(keyListener);
149                 c.gridx = 1; c.weightx = 1.0;
150                 gbPanel.add(_baseUrlField, c);
151                 _baseTypeRadios = new JRadioButton[3];
152                 radioGroup = new ButtonGroup();
153                 for (int i=0; i<3; i++)
154                 {
155                         _baseTypeRadios[i] = new JRadioButton(FILE_TYPES[i]);
156                         radioGroup.add(_baseTypeRadios[i]);
157                         c.gridx = 2+i; c.weightx = 0.0;
158                         gbPanel.add(_baseTypeRadios[i], c);
159                         // Each type radio needs listener to call enableOk()
160                         _baseTypeRadios[i].addActionListener(okEnabler);
161                 }
162
163                 // Top layer
164                 c.gridx = 0; c.gridy = 2;
165                 gbPanel.add(new JLabel(I18nManager.getText("dialog.addmapsource.layer2url")), c);
166                 _topUrlField = new JTextField(18);
167                 _topUrlField.addKeyListener(keyListener);
168                 c.gridx = 1; c.weightx = 1.0;
169                 gbPanel.add(_topUrlField, c);
170                 _topTypeRadios = new JRadioButton[3];
171                 radioGroup = new ButtonGroup();
172                 for (int i=0; i<3; i++)
173                 {
174                         _topTypeRadios[i] = new JRadioButton(FILE_TYPES[i]);
175                         radioGroup.add(_topTypeRadios[i]);
176                         c.gridx = 2+i; c.weightx = 0.0;
177                         gbPanel.add(_topTypeRadios[i], c);
178                         // Each type radio needs listener to call enableOk()
179                         _topTypeRadios[i].addActionListener(okEnabler);
180                 }
181                 // Max zoom
182                 c.gridx = 0; c.gridy = 3;
183                 gbPanel.add(new JLabel(I18nManager.getText("dialog.addmapsource.maxzoom")), c);
184                 _oZoomCombo = new JComboBox();
185                 for (int i=10; i<=20; i++) {
186                         _oZoomCombo.addItem("" + i);
187                 }
188                 // zoom dropdown needs listener to call enableOk()
189                 _oZoomCombo.addActionListener(okEnabler);
190                 c.gridx = 1;
191                 gbPanel.add(_oZoomCombo, c);
192                 osmPanel.add(gbPanel, BorderLayout.NORTH);
193                 _cards.add(osmPanel, "card1");
194
195                 // Panel for cloudmade source
196                 JPanel cloudPanel = new JPanel();
197                 cloudPanel.setBorder(BorderFactory.createEmptyBorder(6, 3, 4, 3));
198                 // Use a gridlayout inside a borderlayout to avoid stretching
199                 cloudPanel.setLayout(new BorderLayout());
200                 JPanel cloudGridPanel = new JPanel();
201                 cloudGridPanel.setLayout(new GridLayout(0, 2, 5, 5));
202                 cloudGridPanel.add(new JLabel(I18nManager.getText("dialog.addmapsource.sourcename")));
203                 _cNameField = new JTextField(18);
204                 _cNameField.addKeyListener(keyListener);
205                 cloudGridPanel.add(_cNameField);
206                 cloudGridPanel.add(new JLabel(I18nManager.getText("dialog.addmapsource.cloudstyle")));
207                 _cStyleField = new JTextField(18);
208                 _cStyleField.addKeyListener(keyListener);
209                 cloudGridPanel.add(_cStyleField);
210                 cloudGridPanel.add(new JLabel(I18nManager.getText("dialog.addmapsource.maxzoom")));
211                 _cZoomCombo = new JComboBox();
212                 for (int i=10; i<=20; i++) {
213                         _cZoomCombo.addItem("" + i);
214                 }
215                 cloudGridPanel.add(_cZoomCombo);
216                 cloudPanel.add(cloudGridPanel, BorderLayout.NORTH);
217                 _cards.add(cloudPanel, "card2");
218                 // cards
219                 JPanel holderPanel = new JPanel();
220                 holderPanel.setLayout(new BorderLayout());
221                 holderPanel.add(_cards, BorderLayout.NORTH);
222                 dialogPanel.add(holderPanel, BorderLayout.CENTER);
223
224                 // button panel at bottom
225                 JPanel buttonPanel = new JPanel();
226                 buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
227                 _okButton = new JButton(I18nManager.getText("button.ok"));
228                 ActionListener okListener = new ActionListener() {
229                         public void actionPerformed(ActionEvent e)
230                         {
231                                 finish();
232                         }
233                 };
234                 _okButton.addActionListener(okListener);
235                 buttonPanel.add(_okButton);
236                 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
237                 cancelButton.addActionListener(new ActionListener() {
238                         public void actionPerformed(ActionEvent e)
239                         {
240                                 _addDialog.dispose();
241                         }
242                 });
243                 buttonPanel.add(cancelButton);
244                 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
245                 return dialogPanel;
246         }
247
248
249         /**
250          * Init and show the dialog
251          * @param inSource source object before edit, or null to add
252          */
253         public void showDialog(MapSource inSource)
254         {
255                 _originalSource = inSource;
256                 populateFields();
257         }
258
259         /**
260          * Clear all the dialog fields to prepare for an add
261          */
262         private void clearAllFields()
263         {
264                 _oNameField.setText("");
265                 _baseUrlField.setText("");
266                 _baseTypeRadios[0].setSelected(true);
267                 _topUrlField.setText("");
268                 _topTypeRadios[0].setSelected(true);
269                 _oZoomCombo.setSelectedIndex(8);
270                 _cNameField.setText("");
271                 _cStyleField.setText("");
272                 _cZoomCombo.setSelectedIndex(8);
273                 _okButton.setEnabled(false);
274                 for (int i=0; i<2; i++) {
275                         _sourceTypeRadios[i].setEnabled(true);
276                 }
277                 _addDialog.setVisible(true);
278         }
279
280         /**
281          * Init the dialog fields from the given source object
282          */
283         private void populateFields()
284         {
285                 if (_originalSource == null)
286                 {
287                         clearAllFields();
288                         return;
289                 }
290                 boolean sourceFound = false;
291                 // See if it's a cloudmade source
292                 try
293                 {
294                         CloudmadeMapSource cloudSource = (CloudmadeMapSource) _originalSource;
295                         sourceFound = true;
296                         _cNameField.setText(cloudSource.getName());
297                         _cStyleField.setText(cloudSource.getStyle());
298                         _cZoomCombo.setSelectedIndex(getZoomIndex(cloudSource.getMaxZoomLevel()));
299                         _sourceTypeRadios[1].setSelected(true);
300                 }
301                 catch (ClassCastException cce) {} // ignore, sourceFound flag stays false
302
303                 // See if it's an osm source
304                 if (!sourceFound)
305                 {
306                         try
307                         {
308                                 OsmMapSource osmSource = (OsmMapSource) _originalSource;
309                                 sourceFound = true;
310                                 _oNameField.setText(osmSource.getName());
311                                 _baseUrlField.setText(osmSource.getBaseUrl(0));
312                                 int baseType = getBaseType(osmSource.getFileExtension(0));
313                                 _baseTypeRadios[baseType].setSelected(true);
314                                 _topUrlField.setText(osmSource.getNumLayers()==0?"":osmSource.getBaseUrl(1));
315                                 int topType = getBaseType(osmSource.getFileExtension(1));
316                                 _topTypeRadios[topType].setSelected(true);
317                                 _oZoomCombo.setSelectedIndex(getZoomIndex(osmSource.getMaxZoomLevel()));
318                                 _sourceTypeRadios[0].setSelected(true);
319                         }
320                         catch (ClassCastException cce) {} // ignore, sourceFound flag stays false
321                 }
322                 for (int i=0; i<2; i++) {
323                         _sourceTypeRadios[i].setEnabled(false);
324                 }
325                 onRadioClicked();
326                 _okButton.setEnabled(false);
327                 _addDialog.setVisible(true);
328         }
329
330
331         /**
332          * React to one of the type radio buttons being clicked
333          */
334         private void onRadioClicked()
335         {
336                 CardLayout cl = (CardLayout) _cards.getLayout();
337                 if (_sourceTypeRadios[0].isSelected()) {cl.first(_cards);}
338                 else {cl.last(_cards);}
339                 enableOK();
340         }
341
342         /**
343          * Check the currently entered details and enable the OK button if it looks OK
344          */
345         private void enableOK()
346         {
347                 boolean ok = false;
348                 if (_sourceTypeRadios[0].isSelected()) {ok = isOsmPanelOk();}
349                 if (_sourceTypeRadios[1].isSelected()) {ok = isCloudPanelOk();}
350                 _okButton.setEnabled(ok);
351         }
352
353         /**
354          * Check the openstreetmap panel if all details are complete
355          * @return true if details look ok
356          */
357         private boolean isOsmPanelOk()
358         {
359                 boolean ok = _oNameField.getText().trim().length() > 1;
360                 String baseUrl = null, topUrl = null;
361                 // Try to parse base url if given
362                 String baseText = _baseUrlField.getText().trim();
363                 baseUrl = MapSource.fixBaseUrl(baseText);
364                 if (baseText.length() > 0 && baseUrl == null) {ok = false;}
365                 // Same again for top url if given
366                 String topText = _topUrlField.getText().trim();
367                 topUrl = MapSource.fixBaseUrl(topText);
368                 if (topText.length() > 0 && topUrl == null) {ok = false;}
369                 // looks ok if at least one url given
370                 return (ok && (baseUrl != null || topUrl != null));
371         }
372
373         /**
374          * Check the cloudmade panel if all details are complete
375          * @return true if details look ok
376          */
377         private boolean isCloudPanelOk()
378         {
379                 boolean ok = _cNameField.getText().trim().length() > 1;
380                 int styleNum = 0;
381                 try {
382                         styleNum = Integer.parseInt(_cStyleField.getText());
383                 }
384                 catch (NumberFormatException nfe) {}
385                 return (ok && styleNum > 0);
386         }
387
388         /**
389          * Finish by adding the requested source and refreshing the parent
390          */
391         private void finish()
392         {
393                 MapSource newSource = null;
394                 String origName = (_originalSource == null ? null : _originalSource.getName());
395                 if (_sourceTypeRadios[0].isSelected())
396                 {
397                         // Openstreetmap source
398                         String sourceName = getValidSourcename(_oNameField.getText(), origName);
399                         String url1 = _baseUrlField.getText().trim();
400                         String ext1 = getFileExtension(_baseTypeRadios);
401                         String url2 = _topUrlField.getText().trim();
402                         String ext2 = getFileExtension(_topTypeRadios);
403                         newSource = new OsmMapSource(sourceName, url1, ext1, url2, ext2, _oZoomCombo.getSelectedIndex()+10);
404                 }
405                 else if (_sourceTypeRadios[1].isSelected())
406                 {
407                         String sourceName = getValidSourcename(_cNameField.getText(), origName);
408                         newSource = new CloudmadeMapSource(sourceName, _cStyleField.getText(),
409                                 _cZoomCombo.getSelectedIndex()+10);
410                 }
411                 // Add new source if ok
412                 if (newSource != null)
413                 {
414                         if (_originalSource == null) {
415                                 MapSourceLibrary.addSource(newSource);
416                         }
417                         else {
418                                 MapSourceLibrary.editSource(_originalSource, newSource);
419                         }
420                         // inform setmapbg dialog
421                         _parent.updateList();
422                         _addDialog.setVisible(false);
423                 }
424         }
425
426         /**
427          * Check the given source name is valid and whether it exists in library already
428          * @param inName name to check
429          * @param inOriginalName name of source before edit (or null for new source)
430          * @return valid name for the new source
431          */
432         private static String getValidSourcename(String inName, String inOriginalName)
433         {
434                 String name = inName;
435                 if (name == null) {name = "";}
436                 else {name = name.trim();}
437                 if (name.equals("")) {
438                         name = I18nManager.getText("dialog.addmapsource.noname");
439                 }
440                 // Check there isn't already a map source with this name
441                 if (inOriginalName == null || !inOriginalName.equals(name))
442                 {
443                         if (MapSourceLibrary.hasSourceName(name))
444                         {
445                                 int suffix = 1;
446                                 while (MapSourceLibrary.hasSourceName(name + suffix)) {
447                                         suffix++;
448                                 }
449                                 name += suffix;
450                         }
451                 }
452                 return name;
453         }
454
455         /**
456          * Get the selected file extension
457          * @param inRadios array of radio buttons for selection
458          * @return selected file extension
459          */
460         private String getFileExtension(JRadioButton[] inRadios)
461         {
462                 if (inRadios != null)
463                 {
464                         for (int i=0; i<inRadios.length; i++) {
465                                 if (inRadios[i] != null && inRadios[i].isSelected()) {
466                                         return FILE_TYPES[i];
467                                 }
468                         }
469                 }
470                 return FILE_TYPES[0];
471         }
472
473         /**
474          * Get the index of the given image extension
475          * @param inExt file extension, such as "png"
476          * @return index from 0 to 2
477          */
478         private static int getBaseType(String inExt)
479         {
480                 for (int i=0; i<FILE_TYPES.length; i++) {
481                         if (FILE_TYPES[i].equals(inExt)) {
482                                 return i;
483                         }
484                 }
485                 // Not found so default to png
486                 return 0;
487         }
488
489         /**
490          * Get the dropdown index of the given zoom level
491          * @param inZoomLevel zoom level, eg 18
492          * @return index of dropdown to select
493          */
494         private static int getZoomIndex(int inZoomLevel)
495         {
496                 return Math.max(0, inZoomLevel - 10);
497         }
498 }