source: josm/trunk/src/org/openstreetmap/josm/gui/preferences/SourceEditor.java@ 5586

Last change on this file since 5586 was 5586, checked in by bastiK, 11 years ago

check property 'min-josm-version' for custom styles and warn user if main version is too low

  • Property svn:eol-style set to native
File size: 57.0 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.gui.preferences;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.Utils.equal;
7
8import java.awt.Component;
9import java.awt.Dimension;
10import java.awt.Font;
11import java.awt.GridBagConstraints;
12import java.awt.GridBagLayout;
13import java.awt.Image;
14import java.awt.Insets;
15import java.awt.Rectangle;
16import java.awt.event.ActionEvent;
17import java.awt.event.FocusAdapter;
18import java.awt.event.FocusEvent;
19import java.awt.event.KeyEvent;
20import java.awt.event.MouseAdapter;
21import java.awt.event.MouseEvent;
22import java.io.BufferedReader;
23import java.io.File;
24import java.io.IOException;
25import java.io.InputStreamReader;
26import java.io.UnsupportedEncodingException;
27import java.net.MalformedURLException;
28import java.net.URL;
29import java.util.ArrayList;
30import java.util.Arrays;
31import java.util.Collection;
32import java.util.Collections;
33import java.util.Comparator;
34import java.util.EventObject;
35import java.util.HashMap;
36import java.util.Iterator;
37import java.util.List;
38import java.util.Map;
39import java.util.concurrent.CopyOnWriteArrayList;
40import java.util.regex.Matcher;
41import java.util.regex.Pattern;
42
43import javax.swing.AbstractAction;
44import javax.swing.BorderFactory;
45import javax.swing.Box;
46import javax.swing.DefaultListModel;
47import javax.swing.DefaultListSelectionModel;
48import javax.swing.Icon;
49import javax.swing.ImageIcon;
50import javax.swing.JButton;
51import javax.swing.JCheckBox;
52import javax.swing.JComponent;
53import javax.swing.JFileChooser;
54import javax.swing.JLabel;
55import javax.swing.JList;
56import javax.swing.JOptionPane;
57import javax.swing.JPanel;
58import javax.swing.JScrollPane;
59import javax.swing.JSeparator;
60import javax.swing.JTable;
61import javax.swing.JTextField;
62import javax.swing.JToolBar;
63import javax.swing.KeyStroke;
64import javax.swing.ListCellRenderer;
65import javax.swing.ListSelectionModel;
66import javax.swing.event.CellEditorListener;
67import javax.swing.event.ChangeEvent;
68import javax.swing.event.ListSelectionEvent;
69import javax.swing.event.ListSelectionListener;
70import javax.swing.event.TableModelEvent;
71import javax.swing.event.TableModelListener;
72import javax.swing.filechooser.FileFilter;
73import javax.swing.table.AbstractTableModel;
74import javax.swing.table.DefaultTableCellRenderer;
75import javax.swing.table.TableCellEditor;
76import javax.swing.table.TableCellRenderer;
77
78import org.openstreetmap.josm.Main;
79import org.openstreetmap.josm.actions.ExtensionFileFilter;
80import org.openstreetmap.josm.data.Version;
81import org.openstreetmap.josm.gui.ExtendedDialog;
82import org.openstreetmap.josm.gui.HelpAwareOptionPane;
83import org.openstreetmap.josm.gui.PleaseWaitRunnable;
84import org.openstreetmap.josm.gui.util.FileFilterAllFiles;
85import org.openstreetmap.josm.gui.widgets.JFileChooserManager;
86import org.openstreetmap.josm.io.MirroredInputStream;
87import org.openstreetmap.josm.io.OsmTransferException;
88import org.openstreetmap.josm.tools.GBC;
89import org.openstreetmap.josm.tools.ImageProvider;
90import org.openstreetmap.josm.tools.LanguageInfo;
91import org.openstreetmap.josm.tools.Utils;
92import org.xml.sax.SAXException;
93
94public abstract class SourceEditor extends JPanel {
95
96 final protected boolean isMapPaint;
97
98 protected final JTable tblActiveSources;
99 protected final ActiveSourcesModel activeSourcesModel;
100 protected final JList lstAvailableSources;
101 protected final AvailableSourcesListModel availableSourcesModel;
102 protected final JTable tblIconPaths;
103 protected final IconPathTableModel iconPathsModel;
104 protected final String availableSourcesUrl;
105 protected final List<SourceProvider> sourceProviders;
106
107 protected boolean sourcesInitiallyLoaded;
108
109 /**
110 * constructor
111 * @param isMapPaint true for MapPaintPreference subclass, false
112 * for TaggingPresetPreference subclass
113 * @param availableSourcesUrl the URL to the list of available sources
114 * @param sourceProviders the list of additional source providers, from plugins
115 */
116 public SourceEditor(final boolean isMapPaint, final String availableSourcesUrl, final List<SourceProvider> sourceProviders) {
117
118 this.isMapPaint = isMapPaint;
119 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
120 this.lstAvailableSources = new JList(availableSourcesModel = new AvailableSourcesListModel(selectionModel));
121 this.lstAvailableSources.setSelectionModel(selectionModel);
122 this.lstAvailableSources.setCellRenderer(new SourceEntryListCellRenderer());
123 this.availableSourcesUrl = availableSourcesUrl;
124 this.sourceProviders = sourceProviders;
125
126 selectionModel = new DefaultListSelectionModel();
127 tblActiveSources = new JTable(activeSourcesModel = new ActiveSourcesModel(selectionModel)) {
128 // some kind of hack to prevent the table from scrolling slightly to the
129 // right when clicking on the text
130 @Override
131 public void scrollRectToVisible(Rectangle aRect) {
132 super.scrollRectToVisible(new Rectangle(0, aRect.y, aRect.width, aRect.height));
133 }
134 };
135 tblActiveSources.putClientProperty("terminateEditOnFocusLost", true);
136 tblActiveSources.setSelectionModel(selectionModel);
137 tblActiveSources.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
138 tblActiveSources.setShowGrid(false);
139 tblActiveSources.setIntercellSpacing(new Dimension(0, 0));
140 tblActiveSources.setTableHeader(null);
141 tblActiveSources.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
142 SourceEntryTableCellRenderer sourceEntryRenderer = new SourceEntryTableCellRenderer();
143 if (isMapPaint) {
144 tblActiveSources.getColumnModel().getColumn(0).setMaxWidth(1);
145 tblActiveSources.getColumnModel().getColumn(0).setResizable(false);
146 tblActiveSources.getColumnModel().getColumn(1).setCellRenderer(sourceEntryRenderer);
147 } else {
148 tblActiveSources.getColumnModel().getColumn(0).setCellRenderer(sourceEntryRenderer);
149 }
150
151 activeSourcesModel.addTableModelListener(new TableModelListener() {
152 // Force swing to show horizontal scrollbars for the JTable
153 // Yes, this is a little ugly, but should work
154 @Override
155 public void tableChanged(TableModelEvent e) {
156 adjustColumnWidth(tblActiveSources, isMapPaint ? 1 : 0);
157 }
158 });
159 activeSourcesModel.setActiveSources(getInitialSourcesList());
160
161 final EditActiveSourceAction editActiveSourceAction = new EditActiveSourceAction();
162 tblActiveSources.getSelectionModel().addListSelectionListener(editActiveSourceAction);
163 tblActiveSources.addMouseListener(new MouseAdapter() {
164 @Override
165 public void mouseClicked(MouseEvent e) {
166 if (e.getClickCount() == 2) {
167 int row = tblActiveSources.rowAtPoint(e.getPoint());
168 int col = tblActiveSources.columnAtPoint(e.getPoint());
169 if (row < 0 || row >= tblActiveSources.getRowCount())
170 return;
171 if (isMapPaint && col != 1)
172 return;
173 editActiveSourceAction.actionPerformed(null);
174 }
175 }
176 });
177
178 RemoveActiveSourcesAction removeActiveSourcesAction = new RemoveActiveSourcesAction();
179 tblActiveSources.getSelectionModel().addListSelectionListener(removeActiveSourcesAction);
180 tblActiveSources.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,0), "delete");
181 tblActiveSources.getActionMap().put("delete", removeActiveSourcesAction);
182
183 MoveUpDownAction moveUp = null;
184 MoveUpDownAction moveDown = null;
185 if (isMapPaint) {
186 moveUp = new MoveUpDownAction(false);
187 moveDown = new MoveUpDownAction(true);
188 tblActiveSources.getSelectionModel().addListSelectionListener(moveUp);
189 tblActiveSources.getSelectionModel().addListSelectionListener(moveDown);
190 activeSourcesModel.addTableModelListener(moveUp);
191 activeSourcesModel.addTableModelListener(moveDown);
192 }
193
194 ActivateSourcesAction activateSourcesAction = new ActivateSourcesAction();
195 lstAvailableSources.addListSelectionListener(activateSourcesAction);
196 JButton activate = new JButton(activateSourcesAction);
197
198 setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
199 setLayout(new GridBagLayout());
200
201 GridBagConstraints gbc = new GridBagConstraints();
202 gbc.gridx = 0;
203 gbc.gridy = 0;
204 gbc.weightx = 0.5;
205 gbc.gridwidth = 2;
206 gbc.anchor = GBC.WEST;
207 gbc.insets = new Insets(5, 11, 0, 0);
208
209 add(new JLabel(getStr(I18nString.AVAILABLE_SOURCES)), gbc);
210
211 gbc.gridx = 2;
212 gbc.insets = new Insets(5, 0, 0, 6);
213
214 add(new JLabel(getStr(I18nString.ACTIVE_SOURCES)), gbc);
215
216 gbc.gridwidth = 1;
217 gbc.gridx = 0;
218 gbc.gridy++;
219 gbc.weighty = 0.8;
220 gbc.fill = GBC.BOTH;
221 gbc.anchor = GBC.CENTER;
222 gbc.insets = new Insets(0, 11, 0, 0);
223
224 JScrollPane sp1 = new JScrollPane(lstAvailableSources);
225 add(sp1, gbc);
226
227 gbc.gridx = 1;
228 gbc.weightx = 0.0;
229 gbc.fill = GBC.VERTICAL;
230 gbc.insets = new Insets(0, 0, 0, 0);
231
232 JToolBar middleTB = new JToolBar();
233 middleTB.setFloatable(false);
234 middleTB.setBorderPainted(false);
235 middleTB.setOpaque(false);
236 middleTB.add(Box.createHorizontalGlue());
237 middleTB.add(activate);
238 middleTB.add(Box.createHorizontalGlue());
239 add(middleTB, gbc);
240
241 gbc.gridx++;
242 gbc.weightx = 0.5;
243 gbc.fill = GBC.BOTH;
244
245 JScrollPane sp = new JScrollPane(tblActiveSources);
246 add(sp, gbc);
247 sp.setColumnHeaderView(null);
248
249 gbc.gridx++;
250 gbc.weightx = 0.0;
251 gbc.fill = GBC.VERTICAL;
252 gbc.insets = new Insets(0, 0, 0, 6);
253
254 JToolBar sideButtonTB = new JToolBar(JToolBar.VERTICAL);
255 sideButtonTB.setFloatable(false);
256 sideButtonTB.setBorderPainted(false);
257 sideButtonTB.setOpaque(false);
258 sideButtonTB.add(new NewActiveSourceAction());
259 sideButtonTB.add(editActiveSourceAction);
260 sideButtonTB.add(removeActiveSourcesAction);
261 sideButtonTB.addSeparator(new Dimension(12, 30));
262 if (isMapPaint) {
263 sideButtonTB.add(moveUp);
264 sideButtonTB.add(moveDown);
265 }
266 add(sideButtonTB, gbc);
267
268 gbc.gridx = 0;
269 gbc.gridy++;
270 gbc.weighty = 0.0;
271 gbc.weightx = 0.5;
272 gbc.fill = GBC.HORIZONTAL;
273 gbc.anchor = GBC.WEST;
274 gbc.insets = new Insets(0, 11, 0, 0);
275
276 JToolBar bottomLeftTB = new JToolBar();
277 bottomLeftTB.setFloatable(false);
278 bottomLeftTB.setBorderPainted(false);
279 bottomLeftTB.setOpaque(false);
280 bottomLeftTB.add(new ReloadSourcesAction(availableSourcesUrl, sourceProviders));
281 bottomLeftTB.add(Box.createHorizontalGlue());
282 add(bottomLeftTB, gbc);
283
284 gbc.gridx = 2;
285 gbc.anchor = GBC.CENTER;
286 gbc.insets = new Insets(0, 0, 0, 0);
287
288 JToolBar bottomRightTB = new JToolBar();
289 bottomRightTB.setFloatable(false);
290 bottomRightTB.setBorderPainted(false);
291 bottomRightTB.setOpaque(false);
292 bottomRightTB.add(Box.createHorizontalGlue());
293 bottomRightTB.add(new JButton(new ResetAction()));
294 add(bottomRightTB, gbc);
295
296 /***
297 * Icon configuration
298 **/
299
300 selectionModel = new DefaultListSelectionModel();
301 tblIconPaths = new JTable(iconPathsModel = new IconPathTableModel(selectionModel));
302 tblIconPaths.setSelectionModel(selectionModel);
303 tblIconPaths.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
304 tblIconPaths.setTableHeader(null);
305 tblIconPaths.getColumnModel().getColumn(0).setCellEditor(new FileOrUrlCellEditor(false));
306 tblIconPaths.setRowHeight(20);
307 tblIconPaths.putClientProperty("terminateEditOnFocusLost", true);
308 iconPathsModel.setIconPaths(getInitialIconPathsList());
309
310 EditIconPathAction editIconPathAction = new EditIconPathAction();
311 tblIconPaths.getSelectionModel().addListSelectionListener(editIconPathAction);
312
313 RemoveIconPathAction removeIconPathAction = new RemoveIconPathAction();
314 tblIconPaths.getSelectionModel().addListSelectionListener(removeIconPathAction);
315 tblIconPaths.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,0), "delete");
316 tblIconPaths.getActionMap().put("delete", removeIconPathAction);
317
318 gbc.gridx = 0;
319 gbc.gridy++;
320 gbc.weightx = 1.0;
321 gbc.gridwidth = GBC.REMAINDER;
322 gbc.insets = new Insets(8, 11, 8, 6);
323
324 add(new JSeparator(), gbc);
325
326 gbc.gridy++;
327 gbc.insets = new Insets(0, 11, 0, 6);
328
329 add(new JLabel(tr("Icon paths:")), gbc);
330
331 gbc.gridy++;
332 gbc.weighty = 0.2;
333 gbc.gridwidth = 3;
334 gbc.fill = GBC.BOTH;
335 gbc.insets = new Insets(0, 11, 0, 0);
336
337 add(sp = new JScrollPane(tblIconPaths), gbc);
338 sp.setColumnHeaderView(null);
339
340 gbc.gridx = 3;
341 gbc.gridwidth = 1;
342 gbc.weightx = 0.0;
343 gbc.fill = GBC.VERTICAL;
344 gbc.insets = new Insets(0, 0, 0, 6);
345
346 JToolBar sideButtonTBIcons = new JToolBar(JToolBar.VERTICAL);
347 sideButtonTBIcons.setFloatable(false);
348 sideButtonTBIcons.setBorderPainted(false);
349 sideButtonTBIcons.setOpaque(false);
350 sideButtonTBIcons.add(new NewIconPathAction());
351 sideButtonTBIcons.add(editIconPathAction);
352 sideButtonTBIcons.add(removeIconPathAction);
353 add(sideButtonTBIcons, gbc);
354 }
355
356 /**
357 * Load the list of source entries that the user has configured.
358 */
359 abstract public Collection<? extends SourceEntry> getInitialSourcesList();
360
361 /**
362 * Load the list of configured icon paths.
363 */
364 abstract public Collection<String> getInitialIconPathsList();
365
366 /**
367 * Get the default list of entries (used when resetting the list).
368 */
369 abstract public Collection<ExtendedSourceEntry> getDefault();
370
371 /**
372 * Save the settings after user clicked "Ok".
373 * @return true if restart is required
374 */
375 abstract public boolean finish();
376
377 /**
378 * Provide the GUI strings. (There are differences for MapPaint and Preset)
379 */
380 abstract protected String getStr(I18nString ident);
381
382 /**
383 * Identifiers for strings that need to be provided.
384 */
385 public enum I18nString { AVAILABLE_SOURCES, ACTIVE_SOURCES, NEW_SOURCE_ENTRY_TOOLTIP, NEW_SOURCE_ENTRY,
386 REMOVE_SOURCE_TOOLTIP, EDIT_SOURCE_TOOLTIP, ACTIVATE_TOOLTIP, RELOAD_ALL_AVAILABLE,
387 LOADING_SOURCES_FROM, FAILED_TO_LOAD_SOURCES_FROM, FAILED_TO_LOAD_SOURCES_FROM_HELP_TOPIC,
388 ILLEGAL_FORMAT_OF_ENTRY }
389
390 /**
391 * adjust the preferred width of column col to the maximum preferred width of the cells
392 * requires JTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
393 */
394 private static void adjustColumnWidth(JTable tbl, int col) {
395 int maxwidth = 0;
396 for (int row=0; row<tbl.getRowCount(); row++) {
397 TableCellRenderer tcr = tbl.getCellRenderer(row, col);
398 Object val = tbl.getValueAt(row, col);
399 Component comp = tcr.getTableCellRendererComponent(tbl, val, false, false, row, col);
400 maxwidth = Math.max(comp.getPreferredSize().width, maxwidth);
401 }
402 tbl.getColumnModel().getColumn(col).setPreferredWidth(maxwidth);
403 }
404
405 public boolean hasActiveSourcesChanged() {
406 Collection<? extends SourceEntry> prev = getInitialSourcesList();
407 List<SourceEntry> cur = activeSourcesModel.getSources();
408 if (prev.size() != cur.size())
409 return true;
410 Iterator<? extends SourceEntry> p = prev.iterator();
411 Iterator<SourceEntry> c = cur.iterator();
412 while (p.hasNext()) {
413 SourceEntry pe = p.next();
414 SourceEntry ce = c.next();
415 if (!equal(pe.url, ce.url) || !equal(pe.name, ce.name) || pe.active != ce.active)
416 return true;
417 }
418 return false;
419 }
420
421 public Collection<SourceEntry> getActiveSources() {
422 return activeSourcesModel.getSources();
423 }
424
425 public void removeSources(Collection<Integer> idxs) {
426 activeSourcesModel.removeIdxs(idxs);
427 }
428
429 protected void reloadAvailableSources(String url, List<SourceProvider> sourceProviders) {
430 Main.worker.submit(new SourceLoader(url, sourceProviders));
431 }
432
433 public void initiallyLoadAvailableSources() {
434 if (!sourcesInitiallyLoaded) {
435 reloadAvailableSources(availableSourcesUrl, sourceProviders);
436 }
437 sourcesInitiallyLoaded = true;
438 }
439
440 protected static class AvailableSourcesListModel extends DefaultListModel {
441 private ArrayList<ExtendedSourceEntry> data;
442 private DefaultListSelectionModel selectionModel;
443
444 public AvailableSourcesListModel(DefaultListSelectionModel selectionModel) {
445 data = new ArrayList<ExtendedSourceEntry>();
446 this.selectionModel = selectionModel;
447 }
448
449 public void setSources(List<ExtendedSourceEntry> sources) {
450 data.clear();
451 if (sources != null) {
452 data.addAll(sources);
453 }
454 fireContentsChanged(this, 0, data.size());
455 }
456
457 @Override
458 public Object getElementAt(int index) {
459 return data.get(index);
460 }
461
462 @Override
463 public int getSize() {
464 if (data == null) return 0;
465 return data.size();
466 }
467
468 public void deleteSelected() {
469 Iterator<ExtendedSourceEntry> it = data.iterator();
470 int i=0;
471 while(it.hasNext()) {
472 it.next();
473 if (selectionModel.isSelectedIndex(i)) {
474 it.remove();
475 }
476 i++;
477 }
478 fireContentsChanged(this, 0, data.size());
479 }
480
481 public List<ExtendedSourceEntry> getSelected() {
482 ArrayList<ExtendedSourceEntry> ret = new ArrayList<ExtendedSourceEntry>();
483 for(int i=0; i<data.size();i++) {
484 if (selectionModel.isSelectedIndex(i)) {
485 ret.add(data.get(i));
486 }
487 }
488 return ret;
489 }
490 }
491
492 protected class ActiveSourcesModel extends AbstractTableModel {
493 private List<SourceEntry> data;
494 private DefaultListSelectionModel selectionModel;
495
496 public ActiveSourcesModel(DefaultListSelectionModel selectionModel) {
497 this.selectionModel = selectionModel;
498 this.data = new ArrayList<SourceEntry>();
499 }
500
501 public int getColumnCount() {
502 return isMapPaint ? 2 : 1;
503 }
504
505 public int getRowCount() {
506 return data == null ? 0 : data.size();
507 }
508
509 @Override
510 public Object getValueAt(int rowIndex, int columnIndex) {
511 if (isMapPaint && columnIndex == 0)
512 return data.get(rowIndex).active;
513 else
514 return data.get(rowIndex);
515 }
516
517 @Override
518 public boolean isCellEditable(int rowIndex, int columnIndex) {
519 return isMapPaint && columnIndex == 0;
520 }
521
522 @Override
523 public Class<?> getColumnClass(int column) {
524 if (isMapPaint && column == 0)
525 return Boolean.class;
526 else return SourceEntry.class;
527 }
528
529 @Override
530 public void setValueAt(Object aValue, int row, int column) {
531 if (row < 0 || row >= getRowCount() || aValue == null)
532 return;
533 if (isMapPaint && column == 0) {
534 data.get(row).active = ! data.get(row).active;
535 }
536 }
537
538 public void setActiveSources(Collection<? extends SourceEntry> sources) {
539 data.clear();
540 if (sources != null) {
541 for (SourceEntry e : sources) {
542 data.add(new SourceEntry(e));
543 }
544 }
545 fireTableDataChanged();
546 }
547
548 public void addSource(SourceEntry entry) {
549 if (entry == null) return;
550 data.add(entry);
551 fireTableDataChanged();
552 int idx = data.indexOf(entry);
553 if (idx >= 0) {
554 selectionModel.setSelectionInterval(idx, idx);
555 }
556 }
557
558 public void removeSelected() {
559 Iterator<SourceEntry> it = data.iterator();
560 int i=0;
561 while(it.hasNext()) {
562 it.next();
563 if (selectionModel.isSelectedIndex(i)) {
564 it.remove();
565 }
566 i++;
567 }
568 fireTableDataChanged();
569 }
570
571 public void removeIdxs(Collection<Integer> idxs) {
572 List<SourceEntry> newData = new ArrayList<SourceEntry>();
573 for (int i=0; i<data.size(); ++i) {
574 if (!idxs.contains(i)) {
575 newData.add(data.get(i));
576 }
577 }
578 data = newData;
579 fireTableDataChanged();
580 }
581
582 public void addExtendedSourceEntries(List<ExtendedSourceEntry> sources) {
583 if (sources == null) return;
584 for (ExtendedSourceEntry info: sources) {
585 data.add(new SourceEntry(info.url, info.name, info.getDisplayName(), true));
586 }
587 fireTableDataChanged();
588 selectionModel.clearSelection();
589 for (ExtendedSourceEntry info: sources) {
590 int pos = data.indexOf(info);
591 if (pos >=0) {
592 selectionModel.addSelectionInterval(pos, pos);
593 }
594 }
595 }
596
597 public List<SourceEntry> getSources() {
598 return new ArrayList<SourceEntry>(data);
599 }
600
601 public boolean canMove(int i) {
602 int[] sel = tblActiveSources.getSelectedRows();
603 if (sel.length == 0)
604 return false;
605 if (i < 0)
606 return sel[0] >= -i;
607 else if (i > 0)
608 return sel[sel.length-1] <= getRowCount()-1 - i;
609 else
610 return true;
611 }
612
613 public void move(int i) {
614 if (!canMove(i)) return;
615 int[] sel = tblActiveSources.getSelectedRows();
616 for (int row: sel) {
617 SourceEntry t1 = data.get(row);
618 SourceEntry t2 = data.get(row + i);
619 data.set(row, t2);
620 data.set(row + i, t1);
621 }
622 selectionModel.clearSelection();
623 for (int row: sel) {
624 selectionModel.addSelectionInterval(row + i, row + i);
625 }
626 }
627 }
628
629 public static class ExtendedSourceEntry extends SourceEntry implements Comparable<ExtendedSourceEntry> {
630 public String simpleFileName;
631 public String version;
632 public String author;
633 public String link;
634 public String description;
635 public Integer minJosmVersion;
636
637 public ExtendedSourceEntry(String simpleFileName, String url) {
638 super(url, null, null, true);
639 this.simpleFileName = simpleFileName;
640 }
641
642 /**
643 * @return string representation for GUI list or menu entry
644 */
645 public String getDisplayName() {
646 return title == null ? simpleFileName : title;
647 }
648
649 private void appendRow(StringBuilder s, String th, String td) {
650 s.append("<tr><th>").append(th).append("</th><td>").append(td).append("</td</tr>");
651 }
652
653 public String getTooltip() {
654 StringBuilder s = new StringBuilder();
655 appendRow(s, tr("Short Description:"), getDisplayName());
656 appendRow(s, tr("URL:"), url);
657 if (author != null) {
658 appendRow(s, tr("Author:"), author);
659 }
660 if (link != null) {
661 appendRow(s, tr("Webpage:"), link);
662 }
663 if (description != null) {
664 appendRow(s, tr("Description:"), description);
665 }
666 if (version != null) {
667 appendRow(s, tr("Version:"), version);
668 }
669 if (minJosmVersion != null) {
670 appendRow(s, tr("Minimum JOSM Version:"), Integer.toString(minJosmVersion));
671 }
672 return "<html><style>th{text-align:right}td{width:400px}</style>"
673 + "<table>" + s + "</table></html>";
674 }
675
676 @Override
677 public String toString() {
678 return "<html><b>" + getDisplayName() + "</b>"
679 + (author == null ? "" : " <span color=\"gray\">" + tr("by {0}", author) + "</color>")
680 + "</html>";
681 }
682
683 @Override
684 public int compareTo(ExtendedSourceEntry o) {
685 if (url.startsWith("resource") && !o.url.startsWith("resource"))
686 return -1;
687 if (o.url.startsWith("resource"))
688 return 1;
689 else
690 return getDisplayName().compareToIgnoreCase(o.getDisplayName());
691 }
692 }
693
694 protected class EditSourceEntryDialog extends ExtendedDialog {
695
696 private JTextField tfTitle;
697 private JTextField tfURL;
698 private JCheckBox cbActive;
699
700 public EditSourceEntryDialog(Component parent, String title, SourceEntry e) {
701 super(parent,
702 title,
703 new String[] {tr("Ok"), tr("Cancel")});
704
705 JPanel p = new JPanel(new GridBagLayout());
706
707 tfTitle = new JTextField(60);
708 p.add(new JLabel(tr("Name (optional):")), GBC.std().insets(15, 0, 5, 5));
709 p.add(tfTitle, GBC.eol().insets(0, 0, 5, 5));
710
711 tfURL = new JTextField(60);
712 p.add(new JLabel(tr("URL / File:")), GBC.std().insets(15, 0, 5, 0));
713 p.add(tfURL, GBC.std().insets(0, 0, 5, 5));
714 JButton fileChooser = new JButton(new LaunchFileChooserAction());
715 fileChooser.setMargin(new Insets(0, 0, 0, 0));
716 p.add(fileChooser, GBC.eol().insets(0, 0, 5, 5));
717
718 if (e != null) {
719 if (e.title != null) {
720 tfTitle.setText(e.title);
721 }
722 tfURL.setText(e.url);
723 }
724
725 if (isMapPaint) {
726 cbActive = new JCheckBox(tr("active"), e != null ? e.active : true);
727 p.add(cbActive, GBC.eol().insets(15, 0, 5, 0));
728 }
729 setButtonIcons(new String[] {"ok", "cancel"});
730 setContent(p);
731 }
732
733 class LaunchFileChooserAction extends AbstractAction {
734 public LaunchFileChooserAction() {
735 putValue(SMALL_ICON, ImageProvider.get("open"));
736 putValue(SHORT_DESCRIPTION, tr("Launch a file chooser to select a file"));
737 }
738
739 protected void prepareFileChooser(String url, JFileChooser fc) {
740 if (url == null || url.trim().length() == 0) return;
741 URL sourceUrl = null;
742 try {
743 sourceUrl = new URL(url);
744 } catch(MalformedURLException e) {
745 File f = new File(url);
746 if (f.isFile()) {
747 f = f.getParentFile();
748 }
749 if (f != null) {
750 fc.setCurrentDirectory(f);
751 }
752 return;
753 }
754 if (sourceUrl.getProtocol().startsWith("file")) {
755 File f = new File(sourceUrl.getPath());
756 if (f.isFile()) {
757 f = f.getParentFile();
758 }
759 if (f != null) {
760 fc.setCurrentDirectory(f);
761 }
762 }
763 }
764
765 public void actionPerformed(ActionEvent e) {
766 FileFilter ff;
767 if (isMapPaint) {
768 ff = new ExtensionFileFilter("xml,mapcss,css,zip", "xml", tr("Map paint style file (*.xml, *.mapcss, *.zip)"));
769 } else {
770 ff = new ExtensionFileFilter("xml,zip", "xml", tr("Preset definition file (*.xml, *.zip)"));
771 }
772 JFileChooserManager fcm = new JFileChooserManager(true)
773 .createFileChooser(true, null, Arrays.asList(ff, FileFilterAllFiles.getInstance()), ff, JFileChooser.FILES_ONLY);
774 prepareFileChooser(tfURL.getText(), fcm.getFileChooser());
775 JFileChooser fc = fcm.openFileChooser(JOptionPane.getFrameForComponent(SourceEditor.this));
776 if (fc != null) {
777 tfURL.setText(fc.getSelectedFile().toString());
778 }
779 }
780 }
781
782 @Override
783 public String getTitle() {
784 return tfTitle.getText();
785 }
786
787 public String getURL() {
788 return tfURL.getText();
789 }
790
791 public boolean active() {
792 if (!isMapPaint)
793 throw new UnsupportedOperationException();
794 return cbActive.isSelected();
795 }
796 }
797
798 class NewActiveSourceAction extends AbstractAction {
799 public NewActiveSourceAction() {
800 putValue(NAME, tr("New"));
801 putValue(SHORT_DESCRIPTION, getStr(I18nString.NEW_SOURCE_ENTRY_TOOLTIP));
802 putValue(SMALL_ICON, ImageProvider.get("dialogs", "add"));
803 }
804
805 public void actionPerformed(ActionEvent evt) {
806 EditSourceEntryDialog editEntryDialog = new EditSourceEntryDialog(
807 SourceEditor.this,
808 getStr(I18nString.NEW_SOURCE_ENTRY),
809 null);
810 editEntryDialog.showDialog();
811 if (editEntryDialog.getValue() == 1) {
812 boolean active = true;
813 if (isMapPaint) {
814 active = editEntryDialog.active();
815 }
816 activeSourcesModel.addSource(new SourceEntry(
817 editEntryDialog.getURL(),
818 null, editEntryDialog.getTitle(), active));
819 activeSourcesModel.fireTableDataChanged();
820 }
821 }
822 }
823
824 class RemoveActiveSourcesAction extends AbstractAction implements ListSelectionListener {
825
826 public RemoveActiveSourcesAction() {
827 putValue(NAME, tr("Remove"));
828 putValue(SHORT_DESCRIPTION, getStr(I18nString.REMOVE_SOURCE_TOOLTIP));
829 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
830 updateEnabledState();
831 }
832
833 protected void updateEnabledState() {
834 setEnabled(tblActiveSources.getSelectedRowCount() > 0);
835 }
836
837 public void valueChanged(ListSelectionEvent e) {
838 updateEnabledState();
839 }
840
841 public void actionPerformed(ActionEvent e) {
842 activeSourcesModel.removeSelected();
843 }
844 }
845
846 class EditActiveSourceAction extends AbstractAction implements ListSelectionListener {
847 public EditActiveSourceAction() {
848 putValue(NAME, tr("Edit"));
849 putValue(SHORT_DESCRIPTION, getStr(I18nString.EDIT_SOURCE_TOOLTIP));
850 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
851 updateEnabledState();
852 }
853
854 protected void updateEnabledState() {
855 setEnabled(tblActiveSources.getSelectedRowCount() == 1);
856 }
857
858 public void valueChanged(ListSelectionEvent e) {
859 updateEnabledState();
860 }
861
862 public void actionPerformed(ActionEvent evt) {
863 int pos = tblActiveSources.getSelectedRow();
864 if (pos < 0 || pos >= tblActiveSources.getRowCount())
865 return;
866
867 SourceEntry e = (SourceEntry) activeSourcesModel.getValueAt(pos, 1);
868
869 EditSourceEntryDialog editEntryDialog = new EditSourceEntryDialog(
870 SourceEditor.this, tr("Edit source entry:"), e);
871 editEntryDialog.showDialog();
872 if (editEntryDialog.getValue() == 1) {
873 if (e.title != null || !equal(editEntryDialog.getTitle(), "")) {
874 e.title = editEntryDialog.getTitle();
875 if (equal(e.title, "")) {
876 e.title = null;
877 }
878 }
879 e.url = editEntryDialog.getURL();
880 if (isMapPaint) {
881 e.active = editEntryDialog.active();
882 }
883 activeSourcesModel.fireTableRowsUpdated(pos, pos);
884 }
885 }
886 }
887
888 /**
889 * The action to move the currently selected entries up or down in the list.
890 */
891 class MoveUpDownAction extends AbstractAction implements ListSelectionListener, TableModelListener {
892 final int increment;
893 public MoveUpDownAction(boolean isDown) {
894 increment = isDown ? 1 : -1;
895 putValue(SMALL_ICON, isDown ? ImageProvider.get("dialogs", "down") : ImageProvider.get("dialogs", "up"));
896 putValue(SHORT_DESCRIPTION, isDown ? tr("Move the selected entry one row down.") : tr("Move the selected entry one row up."));
897 updateEnabledState();
898 }
899
900 public void updateEnabledState() {
901 setEnabled(activeSourcesModel.canMove(increment));
902 }
903
904 @Override
905 public void actionPerformed(ActionEvent e) {
906 activeSourcesModel.move(increment);
907 }
908
909 public void valueChanged(ListSelectionEvent e) {
910 updateEnabledState();
911 }
912
913 public void tableChanged(TableModelEvent e) {
914 updateEnabledState();
915 }
916 }
917
918 class ActivateSourcesAction extends AbstractAction implements ListSelectionListener {
919 public ActivateSourcesAction() {
920 putValue(SHORT_DESCRIPTION, getStr(I18nString.ACTIVATE_TOOLTIP));
921 putValue(SMALL_ICON, ImageProvider.get("preferences", "activate-right"));
922 updateEnabledState();
923 }
924
925 protected void updateEnabledState() {
926 setEnabled(lstAvailableSources.getSelectedIndices().length > 0);
927 }
928
929 public void valueChanged(ListSelectionEvent e) {
930 updateEnabledState();
931 }
932
933 public void actionPerformed(ActionEvent e) {
934 List<ExtendedSourceEntry> sources = availableSourcesModel.getSelected();
935 int josmVersion = Version.getInstance().getVersion();
936 if (josmVersion != Version.JOSM_UNKNOWN_VERSION) {
937 Collection<String> messages = new ArrayList<String>();
938 for (ExtendedSourceEntry entry : sources) {
939 if (entry.minJosmVersion != null && entry.minJosmVersion > josmVersion) {
940 messages.add(tr("Entry ''{0}'' requires JOSM Version {1}. (Currently running: {2})",
941 entry.title,
942 Integer.toString(entry.minJosmVersion),
943 Integer.toString(josmVersion))
944 );
945 }
946 }
947 if (!messages.isEmpty()) {
948 ExtendedDialog dlg = new ExtendedDialog(Main.parent, tr("Warning"), new String [] { tr("Cancel"), tr("Continue anyway") });
949 dlg.setButtonIcons(new Icon[] {
950 ImageProvider.get("cancel"),
951 ImageProvider.overlay(
952 ImageProvider.get("ok"),
953 new ImageIcon(ImageProvider.get("warning-small").getImage().getScaledInstance(12 , 12, Image.SCALE_SMOOTH)),
954 ImageProvider.OverlayPosition.SOUTHEAST)
955 });
956 dlg.setToolTipTexts(new String[] {
957 tr("Cancel and return to the previous dialog"),
958 tr("Ignore warning and install style anyway")});
959 dlg.setContent("<html>" + tr("Some entries have unmet dependencies:") +
960 "<br>" + Utils.join("<br>", messages) + "</html>");
961 dlg.setIcon(JOptionPane.WARNING_MESSAGE);
962 if (dlg.showDialog().getValue() != 2)
963 return;
964 }
965 }
966 activeSourcesModel.addExtendedSourceEntries(sources);
967 }
968 }
969
970 class ResetAction extends AbstractAction {
971
972 public ResetAction() {
973 putValue(NAME, tr("Reset"));
974 putValue(SHORT_DESCRIPTION, tr("Reset to default"));
975 putValue(SMALL_ICON, ImageProvider.get("preferences", "reset"));
976 }
977
978 public void actionPerformed(ActionEvent e) {
979 activeSourcesModel.setActiveSources(getDefault());
980 }
981 }
982
983 class ReloadSourcesAction extends AbstractAction {
984 private final String url;
985 private final List<SourceProvider> sourceProviders;
986 public ReloadSourcesAction(String url, List<SourceProvider> sourceProviders) {
987 putValue(NAME, tr("Reload"));
988 putValue(SHORT_DESCRIPTION, tr(getStr(I18nString.RELOAD_ALL_AVAILABLE), url));
989 putValue(SMALL_ICON, ImageProvider.get("dialogs", "refresh"));
990 this.url = url;
991 this.sourceProviders = sourceProviders;
992 }
993
994 public void actionPerformed(ActionEvent e) {
995 MirroredInputStream.cleanup(url);
996 reloadAvailableSources(url, sourceProviders);
997 }
998 }
999
1000 protected static class IconPathTableModel extends AbstractTableModel {
1001 private ArrayList<String> data;
1002 private DefaultListSelectionModel selectionModel;
1003
1004 public IconPathTableModel(DefaultListSelectionModel selectionModel) {
1005 this.selectionModel = selectionModel;
1006 this.data = new ArrayList<String>();
1007 }
1008
1009 public int getColumnCount() {
1010 return 1;
1011 }
1012
1013 public int getRowCount() {
1014 return data == null ? 0 : data.size();
1015 }
1016
1017 public Object getValueAt(int rowIndex, int columnIndex) {
1018 return data.get(rowIndex);
1019 }
1020
1021 @Override
1022 public boolean isCellEditable(int rowIndex, int columnIndex) {
1023 return true;
1024 }
1025
1026 @Override
1027 public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
1028 updatePath(rowIndex, (String)aValue);
1029 }
1030
1031 public void setIconPaths(Collection<String> paths) {
1032 data.clear();
1033 if (paths !=null) {
1034 data.addAll(paths);
1035 }
1036 sort();
1037 fireTableDataChanged();
1038 }
1039
1040 public void addPath(String path) {
1041 if (path == null) return;
1042 data.add(path);
1043 sort();
1044 fireTableDataChanged();
1045 int idx = data.indexOf(path);
1046 if (idx >= 0) {
1047 selectionModel.setSelectionInterval(idx, idx);
1048 }
1049 }
1050
1051 public void updatePath(int pos, String path) {
1052 if (path == null) return;
1053 if (pos < 0 || pos >= getRowCount()) return;
1054 data.set(pos, path);
1055 sort();
1056 fireTableDataChanged();
1057 int idx = data.indexOf(path);
1058 if (idx >= 0) {
1059 selectionModel.setSelectionInterval(idx, idx);
1060 }
1061 }
1062
1063 public void removeSelected() {
1064 Iterator<String> it = data.iterator();
1065 int i=0;
1066 while(it.hasNext()) {
1067 it.next();
1068 if (selectionModel.isSelectedIndex(i)) {
1069 it.remove();
1070 }
1071 i++;
1072 }
1073 fireTableDataChanged();
1074 selectionModel.clearSelection();
1075 }
1076
1077 protected void sort() {
1078 Collections.sort(
1079 data,
1080 new Comparator<String>() {
1081 public int compare(String o1, String o2) {
1082 if (o1.equals("") && o2.equals(""))
1083 return 0;
1084 if (o1.equals("")) return 1;
1085 if (o2.equals("")) return -1;
1086 return o1.compareTo(o2);
1087 }
1088 }
1089 );
1090 }
1091
1092 public List<String> getIconPaths() {
1093 return new ArrayList<String>(data);
1094 }
1095 }
1096
1097 class NewIconPathAction extends AbstractAction {
1098 public NewIconPathAction() {
1099 putValue(NAME, tr("New"));
1100 putValue(SHORT_DESCRIPTION, tr("Add a new icon path"));
1101 putValue(SMALL_ICON, ImageProvider.get("dialogs", "add"));
1102 }
1103
1104 public void actionPerformed(ActionEvent e) {
1105 iconPathsModel.addPath("");
1106 tblIconPaths.editCellAt(iconPathsModel.getRowCount() -1,0);
1107 }
1108 }
1109
1110 class RemoveIconPathAction extends AbstractAction implements ListSelectionListener {
1111 public RemoveIconPathAction() {
1112 putValue(NAME, tr("Remove"));
1113 putValue(SHORT_DESCRIPTION, tr("Remove the selected icon paths"));
1114 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
1115 updateEnabledState();
1116 }
1117
1118 protected void updateEnabledState() {
1119 setEnabled(tblIconPaths.getSelectedRowCount() > 0);
1120 }
1121
1122 public void valueChanged(ListSelectionEvent e) {
1123 updateEnabledState();
1124 }
1125
1126 public void actionPerformed(ActionEvent e) {
1127 iconPathsModel.removeSelected();
1128 }
1129 }
1130
1131 class EditIconPathAction extends AbstractAction implements ListSelectionListener {
1132 public EditIconPathAction() {
1133 putValue(NAME, tr("Edit"));
1134 putValue(SHORT_DESCRIPTION, tr("Edit the selected icon path"));
1135 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
1136 updateEnabledState();
1137 }
1138
1139 protected void updateEnabledState() {
1140 setEnabled(tblIconPaths.getSelectedRowCount() == 1);
1141 }
1142
1143 public void valueChanged(ListSelectionEvent e) {
1144 updateEnabledState();
1145 }
1146
1147 public void actionPerformed(ActionEvent e) {
1148 int row = tblIconPaths.getSelectedRow();
1149 tblIconPaths.editCellAt(row, 0);
1150 }
1151 }
1152
1153 static class SourceEntryListCellRenderer extends JLabel implements ListCellRenderer {
1154 public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
1155 boolean cellHasFocus) {
1156 String s = value.toString();
1157 setText(s);
1158 if (isSelected) {
1159 setBackground(list.getSelectionBackground());
1160 setForeground(list.getSelectionForeground());
1161 } else {
1162 setBackground(list.getBackground());
1163 setForeground(list.getForeground());
1164 }
1165 setEnabled(list.isEnabled());
1166 setFont(list.getFont());
1167 setFont(getFont().deriveFont(Font.PLAIN));
1168 setOpaque(true);
1169 setToolTipText(((ExtendedSourceEntry) value).getTooltip());
1170 return this;
1171 }
1172 }
1173
1174 class SourceLoader extends PleaseWaitRunnable {
1175 private final String url;
1176 private final List<SourceProvider> sourceProviders;
1177 private BufferedReader reader;
1178 private boolean canceled;
1179 private final List<ExtendedSourceEntry> sources = new ArrayList<ExtendedSourceEntry>();
1180
1181 public SourceLoader(String url, List<SourceProvider> sourceProviders) {
1182 super(tr(getStr(I18nString.LOADING_SOURCES_FROM), url));
1183 this.url = url;
1184 this.sourceProviders = sourceProviders;
1185 }
1186
1187 @Override
1188 protected void cancel() {
1189 canceled = true;
1190 if (reader!= null) {
1191 try {
1192 reader.close();
1193 } catch(IOException e) {
1194 // ignore
1195 }
1196 }
1197 }
1198
1199
1200 protected void warn(Exception e) {
1201 String emsg = e.getMessage() != null ? e.getMessage() : e.toString();
1202 emsg = emsg.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
1203 String msg = tr(getStr(I18nString.FAILED_TO_LOAD_SOURCES_FROM), url, emsg);
1204
1205 HelpAwareOptionPane.showOptionDialog(
1206 Main.parent,
1207 msg,
1208 tr("Error"),
1209 JOptionPane.ERROR_MESSAGE,
1210 ht(getStr(I18nString.FAILED_TO_LOAD_SOURCES_FROM_HELP_TOPIC))
1211 );
1212 }
1213
1214 @Override
1215 protected void realRun() throws SAXException, IOException, OsmTransferException {
1216 String lang = LanguageInfo.getLanguageCodeXML();
1217 try {
1218 sources.addAll(getDefault());
1219
1220 for (SourceProvider provider : sourceProviders) {
1221 for (SourceEntry src : provider.getSources()) {
1222 if (src instanceof ExtendedSourceEntry) {
1223 sources.add((ExtendedSourceEntry) src);
1224 }
1225 }
1226 }
1227
1228 MirroredInputStream stream = new MirroredInputStream(url);
1229 InputStreamReader r;
1230 try {
1231 r = new InputStreamReader(stream, "UTF-8");
1232 } catch (UnsupportedEncodingException e) {
1233 r = new InputStreamReader(stream);
1234 }
1235 reader = new BufferedReader(r);
1236
1237 String line;
1238 ExtendedSourceEntry last = null;
1239
1240 while ((line = reader.readLine()) != null && !canceled) {
1241 if (line.trim().equals("")) {
1242 continue; // skip empty lines
1243 }
1244 if (line.startsWith("\t")) {
1245 Matcher m = Pattern.compile("^\t([^:]+): *(.+)$").matcher(line);
1246 if (! m.matches()) {
1247 System.err.println(tr(getStr(I18nString.ILLEGAL_FORMAT_OF_ENTRY), url, line));
1248 continue;
1249 }
1250 if (last != null) {
1251 String key = m.group(1);
1252 String value = m.group(2);
1253 if ("author".equals(key) && last.author == null) {
1254 last.author = value;
1255 } else if ("version".equals(key)) {
1256 last.version = value;
1257 } else if ("link".equals(key) && last.link == null) {
1258 last.link = value;
1259 } else if ("description".equals(key) && last.description == null) {
1260 last.description = value;
1261 } else if ((lang + "shortdescription").equals(key) && last.title == null) {
1262 last.title = value;
1263 } else if ("shortdescription".equals(key) && last.title == null) {
1264 last.title = value;
1265 } else if ((lang + "title").equals(key) && last.title == null) {
1266 last.title = value;
1267 } else if ("title".equals(key) && last.title == null) {
1268 last.title = value;
1269 } else if ("name".equals(key) && last.name == null) {
1270 last.name = value;
1271 } else if ((lang + "author").equals(key)) {
1272 last.author = value;
1273 } else if ((lang + "link").equals(key)) {
1274 last.link = value;
1275 } else if ((lang + "description").equals(key)) {
1276 last.description = value;
1277 } else if ("min-josm-version".equals(key)) {
1278 try {
1279 last.minJosmVersion = Integer.parseInt(value);
1280 } catch (NumberFormatException e) {
1281 // ignore
1282 }
1283 }
1284 }
1285 } else {
1286 last = null;
1287 Matcher m = Pattern.compile("^(.+);(.+)$").matcher(line);
1288 if (m.matches()) {
1289 sources.add(last = new ExtendedSourceEntry(m.group(1), m.group(2)));
1290 } else {
1291 System.err.println(tr(getStr(I18nString.ILLEGAL_FORMAT_OF_ENTRY), url, line));
1292 }
1293 }
1294 }
1295 } catch (Exception e) {
1296 if (canceled)
1297 // ignore the exception and return
1298 return;
1299 OsmTransferException ex = new OsmTransferException(e);
1300 ex.setUrl(url);
1301 warn(ex);
1302 return;
1303 }
1304 }
1305
1306 @Override
1307 protected void finish() {
1308 Collections.sort(sources);
1309 availableSourcesModel.setSources(sources);
1310 }
1311 }
1312
1313 static class SourceEntryTableCellRenderer extends DefaultTableCellRenderer {
1314 @Override
1315 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
1316 if (value == null)
1317 return this;
1318 SourceEntry se = (SourceEntry) value;
1319 JLabel label = (JLabel)super.getTableCellRendererComponent(table,
1320 fromSourceEntry(se), isSelected, hasFocus, row, column);
1321 return label;
1322 }
1323
1324 private String fromSourceEntry(SourceEntry entry) {
1325 if (entry == null)
1326 return null;
1327 StringBuilder s = new StringBuilder("<html><b>");
1328 if (entry.title != null) {
1329 s.append(entry.title).append("</b> <span color=\"gray\">");
1330 }
1331 s.append(entry.url);
1332 if (entry.title != null) {
1333 s.append("</span>");
1334 }
1335 s.append("</html>");
1336 return s.toString();
1337 }
1338 }
1339
1340 class FileOrUrlCellEditor extends JPanel implements TableCellEditor {
1341 private JTextField tfFileName;
1342 private CopyOnWriteArrayList<CellEditorListener> listeners;
1343 private String value;
1344 private boolean isFile;
1345
1346 /**
1347 * build the GUI
1348 */
1349 protected void build() {
1350 setLayout(new GridBagLayout());
1351 GridBagConstraints gc = new GridBagConstraints();
1352 gc.gridx = 0;
1353 gc.gridy = 0;
1354 gc.fill = GridBagConstraints.BOTH;
1355 gc.weightx = 1.0;
1356 gc.weighty = 1.0;
1357 add(tfFileName = new JTextField(), gc);
1358
1359 gc.gridx = 1;
1360 gc.gridy = 0;
1361 gc.fill = GridBagConstraints.BOTH;
1362 gc.weightx = 0.0;
1363 gc.weighty = 1.0;
1364 add(new JButton(new LaunchFileChooserAction()));
1365
1366 tfFileName.addFocusListener(
1367 new FocusAdapter() {
1368 @Override
1369 public void focusGained(FocusEvent e) {
1370 tfFileName.selectAll();
1371 }
1372 }
1373 );
1374 }
1375
1376 public FileOrUrlCellEditor(boolean isFile) {
1377 this.isFile = isFile;
1378 listeners = new CopyOnWriteArrayList<CellEditorListener>();
1379 build();
1380 }
1381
1382 public void addCellEditorListener(CellEditorListener l) {
1383 if (l != null) {
1384 listeners.addIfAbsent(l);
1385 }
1386 }
1387
1388 protected void fireEditingCanceled() {
1389 for (CellEditorListener l: listeners) {
1390 l.editingCanceled(new ChangeEvent(this));
1391 }
1392 }
1393
1394 protected void fireEditingStopped() {
1395 for (CellEditorListener l: listeners) {
1396 l.editingStopped(new ChangeEvent(this));
1397 }
1398 }
1399
1400 public void cancelCellEditing() {
1401 fireEditingCanceled();
1402 }
1403
1404 public Object getCellEditorValue() {
1405 return value;
1406 }
1407
1408 public boolean isCellEditable(EventObject anEvent) {
1409 if (anEvent instanceof MouseEvent)
1410 return ((MouseEvent)anEvent).getClickCount() >= 2;
1411 return true;
1412 }
1413
1414 public void removeCellEditorListener(CellEditorListener l) {
1415 listeners.remove(l);
1416 }
1417
1418 public boolean shouldSelectCell(EventObject anEvent) {
1419 return true;
1420 }
1421
1422 public boolean stopCellEditing() {
1423 value = tfFileName.getText();
1424 fireEditingStopped();
1425 return true;
1426 }
1427
1428 public void setInitialValue(String initialValue) {
1429 this.value = initialValue;
1430 if (initialValue == null) {
1431 this.tfFileName.setText("");
1432 } else {
1433 this.tfFileName.setText(initialValue);
1434 }
1435 }
1436
1437 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
1438 setInitialValue((String)value);
1439 tfFileName.selectAll();
1440 return this;
1441 }
1442
1443 class LaunchFileChooserAction extends AbstractAction {
1444 public LaunchFileChooserAction() {
1445 putValue(NAME, "...");
1446 putValue(SHORT_DESCRIPTION, tr("Launch a file chooser to select a file"));
1447 }
1448
1449 protected void prepareFileChooser(String url, JFileChooser fc) {
1450 if (url == null || url.trim().length() == 0) return;
1451 URL sourceUrl = null;
1452 try {
1453 sourceUrl = new URL(url);
1454 } catch(MalformedURLException e) {
1455 File f = new File(url);
1456 if (f.isFile()) {
1457 f = f.getParentFile();
1458 }
1459 if (f != null) {
1460 fc.setCurrentDirectory(f);
1461 }
1462 return;
1463 }
1464 if (sourceUrl.getProtocol().startsWith("file")) {
1465 File f = new File(sourceUrl.getPath());
1466 if (f.isFile()) {
1467 f = f.getParentFile();
1468 }
1469 if (f != null) {
1470 fc.setCurrentDirectory(f);
1471 }
1472 }
1473 }
1474
1475 public void actionPerformed(ActionEvent e) {
1476 JFileChooserManager fcm = new JFileChooserManager(true).createFileChooser();
1477 if (!isFile) {
1478 fcm.getFileChooser().setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
1479 }
1480 prepareFileChooser(tfFileName.getText(), fcm.getFileChooser());
1481 JFileChooser fc = fcm.openFileChooser(JOptionPane.getFrameForComponent(SourceEditor.this));
1482 if (fc != null) {
1483 tfFileName.setText(fc.getSelectedFile().toString());
1484 }
1485 }
1486 }
1487 }
1488
1489 abstract public static class SourcePrefHelper {
1490
1491 private final String prefOld;
1492 private final String pref;
1493
1494 public SourcePrefHelper(String pref, String prefOld) {
1495 this.pref = pref;
1496 this.prefOld = prefOld;
1497 }
1498
1499 abstract public Collection<ExtendedSourceEntry> getDefault();
1500
1501 abstract public Map<String, String> serialize(SourceEntry entry);
1502
1503 abstract public SourceEntry deserialize(Map<String, String> entryStr);
1504
1505 // migration can be removed end 2012
1506 abstract public Map<String, String> migrate(Collection<String> old);
1507
1508 public List<SourceEntry> get() {
1509
1510 boolean migration = false;
1511 Collection<Map<String, String>> src = Main.pref.getListOfStructs(pref, (Collection<Map<String, String>>) null);
1512 if (src == null) {
1513 Collection<Collection<String>> srcOldPrefFormat = Main.pref.getArray(prefOld, null);
1514 if (srcOldPrefFormat != null) {
1515 migration = true;
1516 src = new ArrayList<Map<String, String>>();
1517 for (Collection<String> p : srcOldPrefFormat) {
1518 src.add(migrate(p));
1519 }
1520 }
1521 }
1522 if (src == null)
1523 return new ArrayList<SourceEntry>(getDefault());
1524
1525 List<SourceEntry> entries = new ArrayList<SourceEntry>();
1526 for (Map<String, String> sourcePref : src) {
1527 SourceEntry e = deserialize(new HashMap<String, String>(sourcePref));
1528 if (e != null) {
1529 entries.add(e);
1530 }
1531 }
1532 if (migration) {
1533 put(entries);
1534 }
1535 return entries;
1536 }
1537
1538 public boolean put(Collection<? extends SourceEntry> entries) {
1539 Collection<Map<String, String>> setting = new ArrayList<Map<String, String>>();
1540 for (SourceEntry e : entries) {
1541 setting.add(serialize(e));
1542 }
1543 return Main.pref.putListOfStructs(pref, setting);
1544 }
1545 }
1546
1547}
Note: See TracBrowser for help on using the repository browser.