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

Last change on this file since 8492 was 8492, checked in by simon04, 9 years ago

fix #3145 - Allow listing custom presets within default groups

The non-translated group names are taken as criterion.

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