1 package tim.prune.function;
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;
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;
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;
33 * Class to handle the adding of a new map source
35 public class AddMapSourceDialog
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<Integer> _oZoomCombo = null;
47 // controls for cloudmade panel
48 private JTextField _cNameField = null;
49 private JTextField _cStyleField = null;
50 private JComboBox<Integer> _cZoomCombo = null;
51 private JButton _okButton = null;
53 /** array of file types */
54 private static final String[] FILE_TYPES = {"png", "jpg", "gif"};
59 * @param inParent parent dialog
61 public AddMapSourceDialog(JDialog inParentDialog, SetMapBgFunction inParentFunction)
63 _parent = inParentFunction;
64 _addDialog = new JDialog(inParentDialog, I18nManager.getText("dialog.addmapsource.title"), true);
65 _addDialog.add(makeDialogComponents());
66 _addDialog.setLocationRelativeTo(inParentDialog);
72 * Create dialog components
73 * @return Panel containing all gui elements in dialog
75 private Component makeDialogComponents()
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) {
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);
102 _cards = new JPanel();
103 _cards.setLayout(new CardLayout());
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();
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) {
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);
143 c.gridx = 0; c.gridy = 1;
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++)
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);
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++)
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);
182 c.gridx = 0; c.gridy = 3;
183 gbPanel.add(new JLabel(I18nManager.getText("dialog.addmapsource.maxzoom")), c);
184 _oZoomCombo = new JComboBox<Integer>();
185 for (int i=10; i<=20; i++) {
186 _oZoomCombo.addItem(i);
188 // zoom dropdown needs listener to call enableOk()
189 _oZoomCombo.addActionListener(okEnabler);
191 gbPanel.add(_oZoomCombo, c);
192 osmPanel.add(gbPanel, BorderLayout.NORTH);
193 _cards.add(osmPanel, "card1");
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<Integer>();
212 for (int i=10; i<=20; i++) {
213 _cZoomCombo.addItem(i);
215 cloudGridPanel.add(_cZoomCombo);
216 cloudPanel.add(cloudGridPanel, BorderLayout.NORTH);
217 _cards.add(cloudPanel, "card2");
219 JPanel holderPanel = new JPanel();
220 holderPanel.setLayout(new BorderLayout());
221 holderPanel.add(_cards, BorderLayout.NORTH);
222 dialogPanel.add(holderPanel, BorderLayout.CENTER);
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)
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)
240 _addDialog.dispose();
243 buttonPanel.add(cancelButton);
244 dialogPanel.add(buttonPanel, BorderLayout.SOUTH);
250 * Init and show the dialog
251 * @param inSource source object before edit, or null to add
253 public void showDialog(MapSource inSource)
255 _originalSource = inSource;
260 * Clear all the dialog fields to prepare for an add
262 private void clearAllFields()
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);
277 _addDialog.setVisible(true);
281 * Init the dialog fields from the given source object
283 private void populateFields()
285 if (_originalSource == null)
290 boolean sourceFound = false;
291 // See if it's a cloudmade source
294 CloudmadeMapSource cloudSource = (CloudmadeMapSource) _originalSource;
296 _cNameField.setText(cloudSource.getName());
297 _cStyleField.setText(cloudSource.getStyle());
298 _cZoomCombo.setSelectedIndex(getZoomIndex(cloudSource.getMaxZoomLevel()));
299 _sourceTypeRadios[1].setSelected(true);
301 catch (ClassCastException cce) {} // ignore, sourceFound flag stays false
303 // See if it's an osm source
308 OsmMapSource osmSource = (OsmMapSource) _originalSource;
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);
320 catch (ClassCastException cce) {} // ignore, sourceFound flag stays false
322 for (int i=0; i<2; i++) {
323 _sourceTypeRadios[i].setEnabled(false);
326 _okButton.setEnabled(false);
327 _addDialog.setVisible(true);
332 * React to one of the type radio buttons being clicked
334 private void onRadioClicked()
336 CardLayout cl = (CardLayout) _cards.getLayout();
337 if (_sourceTypeRadios[0].isSelected()) {cl.first(_cards);}
338 else {cl.last(_cards);}
343 * Check the currently entered details and enable the OK button if it looks OK
345 private void enableOK()
348 if (_sourceTypeRadios[0].isSelected()) {ok = isOsmPanelOk();}
349 if (_sourceTypeRadios[1].isSelected()) {ok = isCloudPanelOk();}
350 _okButton.setEnabled(ok);
354 * Check the openstreetmap panel if all details are complete
355 * @return true if details look ok
357 private boolean isOsmPanelOk()
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));
374 * Check the cloudmade panel if all details are complete
375 * @return true if details look ok
377 private boolean isCloudPanelOk()
379 boolean ok = _cNameField.getText().trim().length() > 1;
382 styleNum = Integer.parseInt(_cStyleField.getText());
384 catch (NumberFormatException nfe) {}
385 return (ok && styleNum > 0);
389 * Finish by adding the requested source and refreshing the parent
391 private void finish()
393 MapSource newSource = null;
394 String origName = (_originalSource == null ? null : _originalSource.getName());
395 if (_sourceTypeRadios[0].isSelected())
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);
405 else if (_sourceTypeRadios[1].isSelected())
407 String sourceName = getValidSourcename(_cNameField.getText(), origName);
408 newSource = new CloudmadeMapSource(sourceName, _cStyleField.getText(),
409 _cZoomCombo.getSelectedIndex()+10);
411 // Add new source if ok
412 if (newSource != null)
414 if (_originalSource == null) {
415 MapSourceLibrary.addSource(newSource);
418 MapSourceLibrary.editSource(_originalSource, newSource);
420 // inform setmapbg dialog
421 _parent.updateList();
422 _addDialog.setVisible(false);
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
432 private static String getValidSourcename(String inName, String inOriginalName)
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");
440 // Check there isn't already a map source with this name
441 if (inOriginalName == null || !inOriginalName.equals(name))
443 if (MapSourceLibrary.hasSourceName(name))
446 while (MapSourceLibrary.hasSourceName(name + suffix)) {
456 * Get the selected file extension
457 * @param inRadios array of radio buttons for selection
458 * @return selected file extension
460 private String getFileExtension(JRadioButton[] inRadios)
462 if (inRadios != null)
464 for (int i=0; i<inRadios.length; i++) {
465 if (inRadios[i] != null && inRadios[i].isSelected()) {
466 return FILE_TYPES[i];
470 return FILE_TYPES[0];
474 * Get the index of the given image extension
475 * @param inExt file extension, such as "png"
476 * @return index from 0 to 2
478 private static int getBaseType(String inExt)
480 for (int i=0; i<FILE_TYPES.length; i++) {
481 if (FILE_TYPES[i].equals(inExt)) {
485 // Not found so default to png
490 * Get the dropdown index of the given zoom level
491 * @param inZoomLevel zoom level, eg 18
492 * @return index of dropdown to select
494 private static int getZoomIndex(int inZoomLevel)
496 return Math.max(0, inZoomLevel - 10);