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

Last change on this file since 6248 was 6248, checked in by Don-vip, 11 years ago

Rework console output:

  • new log level "error"
  • Replace nearly all calls to system.out and system.err to Main.(error|warn|info|debug)
  • Remove some unnecessary debug output
  • Some messages are modified (removal of "Info", "Warning", "Error" from the message itself -> notable i18n impact but limited to console error messages not seen by the majority of users, so that's ok)
  • Property svn:eol-style set to native
File size: 56.1 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.JToolBar;
62import javax.swing.KeyStroke;
63import javax.swing.ListCellRenderer;
64import javax.swing.ListSelectionModel;
65import javax.swing.event.CellEditorListener;
66import javax.swing.event.ChangeEvent;
67import javax.swing.event.ListSelectionEvent;
68import javax.swing.event.ListSelectionListener;
69import javax.swing.event.TableModelEvent;
70import javax.swing.event.TableModelListener;
71import javax.swing.filechooser.FileFilter;
72import javax.swing.table.AbstractTableModel;
73import javax.swing.table.DefaultTableCellRenderer;
74import javax.swing.table.TableCellEditor;
75
76import org.openstreetmap.josm.Main;
77import org.openstreetmap.josm.actions.ExtensionFileFilter;
78import org.openstreetmap.josm.data.Version;
79import org.openstreetmap.josm.gui.ExtendedDialog;
80import org.openstreetmap.josm.gui.HelpAwareOptionPane;
81import org.openstreetmap.josm.gui.PleaseWaitRunnable;
82import org.openstreetmap.josm.gui.util.FileFilterAllFiles;
83import org.openstreetmap.josm.gui.util.TableHelper;
84import org.openstreetmap.josm.gui.widgets.JFileChooserManager;
85import org.openstreetmap.josm.gui.widgets.JosmTextField;
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 TableHelper.adjustColumnWidth(tblActiveSources, isMapPaint ? 1 : 0, 800);
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 public boolean hasActiveSourcesChanged() {
391 Collection<? extends SourceEntry> prev = getInitialSourcesList();
392 List<SourceEntry> cur = activeSourcesModel.getSources();
393 if (prev.size() != cur.size())
394 return true;
395 Iterator<? extends SourceEntry> p = prev.iterator();
396 Iterator<SourceEntry> c = cur.iterator();
397 while (p.hasNext()) {
398 SourceEntry pe = p.next();
399 SourceEntry ce = c.next();
400 if (!equal(pe.url, ce.url) || !equal(pe.name, ce.name) || pe.active != ce.active)
401 return true;
402 }
403 return false;
404 }
405
406 public Collection<SourceEntry> getActiveSources() {
407 return activeSourcesModel.getSources();
408 }
409
410 public void removeSources(Collection<Integer> idxs) {
411 activeSourcesModel.removeIdxs(idxs);
412 }
413
414 protected void reloadAvailableSources(String url, List<SourceProvider> sourceProviders) {
415 Main.worker.submit(new SourceLoader(url, sourceProviders));
416 }
417
418 public void initiallyLoadAvailableSources() {
419 if (!sourcesInitiallyLoaded) {
420 reloadAvailableSources(availableSourcesUrl, sourceProviders);
421 }
422 sourcesInitiallyLoaded = true;
423 }
424
425 protected static class AvailableSourcesListModel extends DefaultListModel {
426 private ArrayList<ExtendedSourceEntry> data;
427 private DefaultListSelectionModel selectionModel;
428
429 public AvailableSourcesListModel(DefaultListSelectionModel selectionModel) {
430 data = new ArrayList<ExtendedSourceEntry>();
431 this.selectionModel = selectionModel;
432 }
433
434 public void setSources(List<ExtendedSourceEntry> sources) {
435 data.clear();
436 if (sources != null) {
437 data.addAll(sources);
438 }
439 fireContentsChanged(this, 0, data.size());
440 }
441
442 @Override
443 public Object getElementAt(int index) {
444 return data.get(index);
445 }
446
447 @Override
448 public int getSize() {
449 if (data == null) return 0;
450 return data.size();
451 }
452
453 public void deleteSelected() {
454 Iterator<ExtendedSourceEntry> it = data.iterator();
455 int i=0;
456 while(it.hasNext()) {
457 it.next();
458 if (selectionModel.isSelectedIndex(i)) {
459 it.remove();
460 }
461 i++;
462 }
463 fireContentsChanged(this, 0, data.size());
464 }
465
466 public List<ExtendedSourceEntry> getSelected() {
467 ArrayList<ExtendedSourceEntry> ret = new ArrayList<ExtendedSourceEntry>();
468 for(int i=0; i<data.size();i++) {
469 if (selectionModel.isSelectedIndex(i)) {
470 ret.add(data.get(i));
471 }
472 }
473 return ret;
474 }
475 }
476
477 protected class ActiveSourcesModel extends AbstractTableModel {
478 private List<SourceEntry> data;
479 private DefaultListSelectionModel selectionModel;
480
481 public ActiveSourcesModel(DefaultListSelectionModel selectionModel) {
482 this.selectionModel = selectionModel;
483 this.data = new ArrayList<SourceEntry>();
484 }
485
486 @Override
487 public int getColumnCount() {
488 return isMapPaint ? 2 : 1;
489 }
490
491 @Override
492 public int getRowCount() {
493 return data == null ? 0 : data.size();
494 }
495
496 @Override
497 public Object getValueAt(int rowIndex, int columnIndex) {
498 if (isMapPaint && columnIndex == 0)
499 return data.get(rowIndex).active;
500 else
501 return data.get(rowIndex);
502 }
503
504 @Override
505 public boolean isCellEditable(int rowIndex, int columnIndex) {
506 return isMapPaint && columnIndex == 0;
507 }
508
509 @Override
510 public Class<?> getColumnClass(int column) {
511 if (isMapPaint && column == 0)
512 return Boolean.class;
513 else return SourceEntry.class;
514 }
515
516 @Override
517 public void setValueAt(Object aValue, int row, int column) {
518 if (row < 0 || row >= getRowCount() || aValue == null)
519 return;
520 if (isMapPaint && column == 0) {
521 data.get(row).active = ! data.get(row).active;
522 }
523 }
524
525 public void setActiveSources(Collection<? extends SourceEntry> sources) {
526 data.clear();
527 if (sources != null) {
528 for (SourceEntry e : sources) {
529 data.add(new SourceEntry(e));
530 }
531 }
532 fireTableDataChanged();
533 }
534
535 public void addSource(SourceEntry entry) {
536 if (entry == null) return;
537 data.add(entry);
538 fireTableDataChanged();
539 int idx = data.indexOf(entry);
540 if (idx >= 0) {
541 selectionModel.setSelectionInterval(idx, idx);
542 }
543 }
544
545 public void removeSelected() {
546 Iterator<SourceEntry> it = data.iterator();
547 int i=0;
548 while(it.hasNext()) {
549 it.next();
550 if (selectionModel.isSelectedIndex(i)) {
551 it.remove();
552 }
553 i++;
554 }
555 fireTableDataChanged();
556 }
557
558 public void removeIdxs(Collection<Integer> idxs) {
559 List<SourceEntry> newData = new ArrayList<SourceEntry>();
560 for (int i=0; i<data.size(); ++i) {
561 if (!idxs.contains(i)) {
562 newData.add(data.get(i));
563 }
564 }
565 data = newData;
566 fireTableDataChanged();
567 }
568
569 public void addExtendedSourceEntries(List<ExtendedSourceEntry> sources) {
570 if (sources == null) return;
571 for (ExtendedSourceEntry info: sources) {
572 data.add(new SourceEntry(info.url, info.name, info.getDisplayName(), true));
573 }
574 fireTableDataChanged();
575 selectionModel.clearSelection();
576 for (ExtendedSourceEntry info: sources) {
577 int pos = data.indexOf(info);
578 if (pos >=0) {
579 selectionModel.addSelectionInterval(pos, pos);
580 }
581 }
582 }
583
584 public List<SourceEntry> getSources() {
585 return new ArrayList<SourceEntry>(data);
586 }
587
588 public boolean canMove(int i) {
589 int[] sel = tblActiveSources.getSelectedRows();
590 if (sel.length == 0)
591 return false;
592 if (i < 0)
593 return sel[0] >= -i;
594 else if (i > 0)
595 return sel[sel.length-1] <= getRowCount()-1 - i;
596 else
597 return true;
598 }
599
600 public void move(int i) {
601 if (!canMove(i)) return;
602 int[] sel = tblActiveSources.getSelectedRows();
603 for (int row: sel) {
604 SourceEntry t1 = data.get(row);
605 SourceEntry t2 = data.get(row + i);
606 data.set(row, t2);
607 data.set(row + i, t1);
608 }
609 selectionModel.clearSelection();
610 for (int row: sel) {
611 selectionModel.addSelectionInterval(row + i, row + i);
612 }
613 }
614 }
615
616 public static class ExtendedSourceEntry extends SourceEntry implements Comparable<ExtendedSourceEntry> {
617 public String simpleFileName;
618 public String version;
619 public String author;
620 public String link;
621 public String description;
622 public Integer minJosmVersion;
623
624 public ExtendedSourceEntry(String simpleFileName, String url) {
625 super(url, null, null, true);
626 this.simpleFileName = simpleFileName;
627 }
628
629 /**
630 * @return string representation for GUI list or menu entry
631 */
632 public String getDisplayName() {
633 return title == null ? simpleFileName : title;
634 }
635
636 private void appendRow(StringBuilder s, String th, String td) {
637 s.append("<tr><th>").append(th).append("</th><td>").append(td).append("</td</tr>");
638 }
639
640 public String getTooltip() {
641 StringBuilder s = new StringBuilder();
642 appendRow(s, tr("Short Description:"), getDisplayName());
643 appendRow(s, tr("URL:"), url);
644 if (author != null) {
645 appendRow(s, tr("Author:"), author);
646 }
647 if (link != null) {
648 appendRow(s, tr("Webpage:"), link);
649 }
650 if (description != null) {
651 appendRow(s, tr("Description:"), description);
652 }
653 if (version != null) {
654 appendRow(s, tr("Version:"), version);
655 }
656 if (minJosmVersion != null) {
657 appendRow(s, tr("Minimum JOSM Version:"), Integer.toString(minJosmVersion));
658 }
659 return "<html><style>th{text-align:right}td{width:400px}</style>"
660 + "<table>" + s + "</table></html>";
661 }
662
663 @Override
664 public String toString() {
665 return "<html><b>" + getDisplayName() + "</b>"
666 + (author == null ? "" : " <span color=\"gray\">" + tr("by {0}", author) + "</color>")
667 + "</html>";
668 }
669
670 @Override
671 public int compareTo(ExtendedSourceEntry o) {
672 if (url.startsWith("resource") && !o.url.startsWith("resource"))
673 return -1;
674 if (o.url.startsWith("resource"))
675 return 1;
676 else
677 return getDisplayName().compareToIgnoreCase(o.getDisplayName());
678 }
679 }
680
681 protected class EditSourceEntryDialog extends ExtendedDialog {
682
683 private JosmTextField tfTitle;
684 private JosmTextField tfURL;
685 private JCheckBox cbActive;
686
687 public EditSourceEntryDialog(Component parent, String title, SourceEntry e) {
688 super(parent,
689 title,
690 new String[] {tr("Ok"), tr("Cancel")});
691
692 JPanel p = new JPanel(new GridBagLayout());
693
694 tfTitle = new JosmTextField(60);
695 p.add(new JLabel(tr("Name (optional):")), GBC.std().insets(15, 0, 5, 5));
696 p.add(tfTitle, GBC.eol().insets(0, 0, 5, 5));
697
698 tfURL = new JosmTextField(60);
699 p.add(new JLabel(tr("URL / File:")), GBC.std().insets(15, 0, 5, 0));
700 p.add(tfURL, GBC.std().insets(0, 0, 5, 5));
701 JButton fileChooser = new JButton(new LaunchFileChooserAction());
702 fileChooser.setMargin(new Insets(0, 0, 0, 0));
703 p.add(fileChooser, GBC.eol().insets(0, 0, 5, 5));
704
705 if (e != null) {
706 if (e.title != null) {
707 tfTitle.setText(e.title);
708 }
709 tfURL.setText(e.url);
710 }
711
712 if (isMapPaint) {
713 cbActive = new JCheckBox(tr("active"), e != null ? e.active : true);
714 p.add(cbActive, GBC.eol().insets(15, 0, 5, 0));
715 }
716 setButtonIcons(new String[] {"ok", "cancel"});
717 setContent(p);
718 }
719
720 class LaunchFileChooserAction extends AbstractAction {
721 public LaunchFileChooserAction() {
722 putValue(SMALL_ICON, ImageProvider.get("open"));
723 putValue(SHORT_DESCRIPTION, tr("Launch a file chooser to select a file"));
724 }
725
726 protected void prepareFileChooser(String url, JFileChooser fc) {
727 if (url == null || url.trim().length() == 0) return;
728 URL sourceUrl = null;
729 try {
730 sourceUrl = new URL(url);
731 } catch(MalformedURLException e) {
732 File f = new File(url);
733 if (f.isFile()) {
734 f = f.getParentFile();
735 }
736 if (f != null) {
737 fc.setCurrentDirectory(f);
738 }
739 return;
740 }
741 if (sourceUrl.getProtocol().startsWith("file")) {
742 File f = new File(sourceUrl.getPath());
743 if (f.isFile()) {
744 f = f.getParentFile();
745 }
746 if (f != null) {
747 fc.setCurrentDirectory(f);
748 }
749 }
750 }
751
752 @Override
753 public void actionPerformed(ActionEvent e) {
754 FileFilter ff;
755 if (isMapPaint) {
756 ff = new ExtensionFileFilter("xml,mapcss,css,zip", "xml", tr("Map paint style file (*.xml, *.mapcss, *.zip)"));
757 } else {
758 ff = new ExtensionFileFilter("xml,zip", "xml", tr("Preset definition file (*.xml, *.zip)"));
759 }
760 JFileChooserManager fcm = new JFileChooserManager(true)
761 .createFileChooser(true, null, Arrays.asList(ff, FileFilterAllFiles.getInstance()), ff, JFileChooser.FILES_ONLY);
762 prepareFileChooser(tfURL.getText(), fcm.getFileChooser());
763 JFileChooser fc = fcm.openFileChooser(JOptionPane.getFrameForComponent(SourceEditor.this));
764 if (fc != null) {
765 tfURL.setText(fc.getSelectedFile().toString());
766 }
767 }
768 }
769
770 @Override
771 public String getTitle() {
772 return tfTitle.getText();
773 }
774
775 public String getURL() {
776 return tfURL.getText();
777 }
778
779 public boolean active() {
780 if (!isMapPaint)
781 throw new UnsupportedOperationException();
782 return cbActive.isSelected();
783 }
784 }
785
786 class NewActiveSourceAction extends AbstractAction {
787 public NewActiveSourceAction() {
788 putValue(NAME, tr("New"));
789 putValue(SHORT_DESCRIPTION, getStr(I18nString.NEW_SOURCE_ENTRY_TOOLTIP));
790 putValue(SMALL_ICON, ImageProvider.get("dialogs", "add"));
791 }
792
793 @Override
794 public void actionPerformed(ActionEvent evt) {
795 EditSourceEntryDialog editEntryDialog = new EditSourceEntryDialog(
796 SourceEditor.this,
797 getStr(I18nString.NEW_SOURCE_ENTRY),
798 null);
799 editEntryDialog.showDialog();
800 if (editEntryDialog.getValue() == 1) {
801 boolean active = true;
802 if (isMapPaint) {
803 active = editEntryDialog.active();
804 }
805 activeSourcesModel.addSource(new SourceEntry(
806 editEntryDialog.getURL(),
807 null, editEntryDialog.getTitle(), active));
808 activeSourcesModel.fireTableDataChanged();
809 }
810 }
811 }
812
813 class RemoveActiveSourcesAction extends AbstractAction implements ListSelectionListener {
814
815 public RemoveActiveSourcesAction() {
816 putValue(NAME, tr("Remove"));
817 putValue(SHORT_DESCRIPTION, getStr(I18nString.REMOVE_SOURCE_TOOLTIP));
818 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
819 updateEnabledState();
820 }
821
822 protected void updateEnabledState() {
823 setEnabled(tblActiveSources.getSelectedRowCount() > 0);
824 }
825
826 @Override
827 public void valueChanged(ListSelectionEvent e) {
828 updateEnabledState();
829 }
830
831 @Override
832 public void actionPerformed(ActionEvent e) {
833 activeSourcesModel.removeSelected();
834 }
835 }
836
837 class EditActiveSourceAction extends AbstractAction implements ListSelectionListener {
838 public EditActiveSourceAction() {
839 putValue(NAME, tr("Edit"));
840 putValue(SHORT_DESCRIPTION, getStr(I18nString.EDIT_SOURCE_TOOLTIP));
841 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
842 updateEnabledState();
843 }
844
845 protected void updateEnabledState() {
846 setEnabled(tblActiveSources.getSelectedRowCount() == 1);
847 }
848
849 @Override
850 public void valueChanged(ListSelectionEvent e) {
851 updateEnabledState();
852 }
853
854 @Override
855 public void actionPerformed(ActionEvent evt) {
856 int pos = tblActiveSources.getSelectedRow();
857 if (pos < 0 || pos >= tblActiveSources.getRowCount())
858 return;
859
860 SourceEntry e = (SourceEntry) activeSourcesModel.getValueAt(pos, 1);
861
862 EditSourceEntryDialog editEntryDialog = new EditSourceEntryDialog(
863 SourceEditor.this, tr("Edit source entry:"), e);
864 editEntryDialog.showDialog();
865 if (editEntryDialog.getValue() == 1) {
866 if (e.title != null || !equal(editEntryDialog.getTitle(), "")) {
867 e.title = editEntryDialog.getTitle();
868 if (equal(e.title, "")) {
869 e.title = null;
870 }
871 }
872 e.url = editEntryDialog.getURL();
873 if (isMapPaint) {
874 e.active = editEntryDialog.active();
875 }
876 activeSourcesModel.fireTableRowsUpdated(pos, pos);
877 }
878 }
879 }
880
881 /**
882 * The action to move the currently selected entries up or down in the list.
883 */
884 class MoveUpDownAction extends AbstractAction implements ListSelectionListener, TableModelListener {
885 final int increment;
886 public MoveUpDownAction(boolean isDown) {
887 increment = isDown ? 1 : -1;
888 putValue(SMALL_ICON, isDown ? ImageProvider.get("dialogs", "down") : ImageProvider.get("dialogs", "up"));
889 putValue(SHORT_DESCRIPTION, isDown ? tr("Move the selected entry one row down.") : tr("Move the selected entry one row up."));
890 updateEnabledState();
891 }
892
893 public void updateEnabledState() {
894 setEnabled(activeSourcesModel.canMove(increment));
895 }
896
897 @Override
898 public void actionPerformed(ActionEvent e) {
899 activeSourcesModel.move(increment);
900 }
901
902 @Override
903 public void valueChanged(ListSelectionEvent e) {
904 updateEnabledState();
905 }
906
907 @Override
908 public void tableChanged(TableModelEvent e) {
909 updateEnabledState();
910 }
911 }
912
913 class ActivateSourcesAction extends AbstractAction implements ListSelectionListener {
914 public ActivateSourcesAction() {
915 putValue(SHORT_DESCRIPTION, getStr(I18nString.ACTIVATE_TOOLTIP));
916 putValue(SMALL_ICON, ImageProvider.get("preferences", "activate-right"));
917 updateEnabledState();
918 }
919
920 protected void updateEnabledState() {
921 setEnabled(lstAvailableSources.getSelectedIndices().length > 0);
922 }
923
924 @Override
925 public void valueChanged(ListSelectionEvent e) {
926 updateEnabledState();
927 }
928
929 @Override
930 public void actionPerformed(ActionEvent e) {
931 List<ExtendedSourceEntry> sources = availableSourcesModel.getSelected();
932 int josmVersion = Version.getInstance().getVersion();
933 if (josmVersion != Version.JOSM_UNKNOWN_VERSION) {
934 Collection<String> messages = new ArrayList<String>();
935 for (ExtendedSourceEntry entry : sources) {
936 if (entry.minJosmVersion != null && entry.minJosmVersion > josmVersion) {
937 messages.add(tr("Entry ''{0}'' requires JOSM Version {1}. (Currently running: {2})",
938 entry.title,
939 Integer.toString(entry.minJosmVersion),
940 Integer.toString(josmVersion))
941 );
942 }
943 }
944 if (!messages.isEmpty()) {
945 ExtendedDialog dlg = new ExtendedDialog(Main.parent, tr("Warning"), new String [] { tr("Cancel"), tr("Continue anyway") });
946 dlg.setButtonIcons(new Icon[] {
947 ImageProvider.get("cancel"),
948 ImageProvider.overlay(
949 ImageProvider.get("ok"),
950 new ImageIcon(ImageProvider.get("warning-small").getImage().getScaledInstance(12 , 12, Image.SCALE_SMOOTH)),
951 ImageProvider.OverlayPosition.SOUTHEAST)
952 });
953 dlg.setToolTipTexts(new String[] {
954 tr("Cancel and return to the previous dialog"),
955 tr("Ignore warning and install style anyway")});
956 dlg.setContent("<html>" + tr("Some entries have unmet dependencies:") +
957 "<br>" + Utils.join("<br>", messages) + "</html>");
958 dlg.setIcon(JOptionPane.WARNING_MESSAGE);
959 if (dlg.showDialog().getValue() != 2)
960 return;
961 }
962 }
963 activeSourcesModel.addExtendedSourceEntries(sources);
964 }
965 }
966
967 class ResetAction extends AbstractAction {
968
969 public ResetAction() {
970 putValue(NAME, tr("Reset"));
971 putValue(SHORT_DESCRIPTION, tr("Reset to default"));
972 putValue(SMALL_ICON, ImageProvider.get("preferences", "reset"));
973 }
974
975 @Override
976 public void actionPerformed(ActionEvent e) {
977 activeSourcesModel.setActiveSources(getDefault());
978 }
979 }
980
981 class ReloadSourcesAction extends AbstractAction {
982 private final String url;
983 private final List<SourceProvider> sourceProviders;
984 public ReloadSourcesAction(String url, List<SourceProvider> sourceProviders) {
985 putValue(NAME, tr("Reload"));
986 putValue(SHORT_DESCRIPTION, tr(getStr(I18nString.RELOAD_ALL_AVAILABLE), url));
987 putValue(SMALL_ICON, ImageProvider.get("dialogs", "refresh"));
988 this.url = url;
989 this.sourceProviders = sourceProviders;
990 }
991
992 @Override
993 public void actionPerformed(ActionEvent e) {
994 MirroredInputStream.cleanup(url);
995 reloadAvailableSources(url, sourceProviders);
996 }
997 }
998
999 protected static class IconPathTableModel extends AbstractTableModel {
1000 private ArrayList<String> data;
1001 private DefaultListSelectionModel selectionModel;
1002
1003 public IconPathTableModel(DefaultListSelectionModel selectionModel) {
1004 this.selectionModel = selectionModel;
1005 this.data = new ArrayList<String>();
1006 }
1007
1008 @Override
1009 public int getColumnCount() {
1010 return 1;
1011 }
1012
1013 @Override
1014 public int getRowCount() {
1015 return data == null ? 0 : data.size();
1016 }
1017
1018 @Override
1019 public Object getValueAt(int rowIndex, int columnIndex) {
1020 return data.get(rowIndex);
1021 }
1022
1023 @Override
1024 public boolean isCellEditable(int rowIndex, int columnIndex) {
1025 return true;
1026 }
1027
1028 @Override
1029 public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
1030 updatePath(rowIndex, (String)aValue);
1031 }
1032
1033 public void setIconPaths(Collection<String> paths) {
1034 data.clear();
1035 if (paths !=null) {
1036 data.addAll(paths);
1037 }
1038 sort();
1039 fireTableDataChanged();
1040 }
1041
1042 public void addPath(String path) {
1043 if (path == null) return;
1044 data.add(path);
1045 sort();
1046 fireTableDataChanged();
1047 int idx = data.indexOf(path);
1048 if (idx >= 0) {
1049 selectionModel.setSelectionInterval(idx, idx);
1050 }
1051 }
1052
1053 public void updatePath(int pos, String path) {
1054 if (path == null) return;
1055 if (pos < 0 || pos >= getRowCount()) return;
1056 data.set(pos, path);
1057 sort();
1058 fireTableDataChanged();
1059 int idx = data.indexOf(path);
1060 if (idx >= 0) {
1061 selectionModel.setSelectionInterval(idx, idx);
1062 }
1063 }
1064
1065 public void removeSelected() {
1066 Iterator<String> it = data.iterator();
1067 int i=0;
1068 while(it.hasNext()) {
1069 it.next();
1070 if (selectionModel.isSelectedIndex(i)) {
1071 it.remove();
1072 }
1073 i++;
1074 }
1075 fireTableDataChanged();
1076 selectionModel.clearSelection();
1077 }
1078
1079 protected void sort() {
1080 Collections.sort(
1081 data,
1082 new Comparator<String>() {
1083 @Override
1084 public int compare(String o1, String o2) {
1085 if (o1.isEmpty() && o2.isEmpty())
1086 return 0;
1087 if (o1.isEmpty()) return 1;
1088 if (o2.isEmpty()) return -1;
1089 return o1.compareTo(o2);
1090 }
1091 }
1092 );
1093 }
1094
1095 public List<String> getIconPaths() {
1096 return new ArrayList<String>(data);
1097 }
1098 }
1099
1100 class NewIconPathAction extends AbstractAction {
1101 public NewIconPathAction() {
1102 putValue(NAME, tr("New"));
1103 putValue(SHORT_DESCRIPTION, tr("Add a new icon path"));
1104 putValue(SMALL_ICON, ImageProvider.get("dialogs", "add"));
1105 }
1106
1107 @Override
1108 public void actionPerformed(ActionEvent e) {
1109 iconPathsModel.addPath("");
1110 tblIconPaths.editCellAt(iconPathsModel.getRowCount() -1,0);
1111 }
1112 }
1113
1114 class RemoveIconPathAction extends AbstractAction implements ListSelectionListener {
1115 public RemoveIconPathAction() {
1116 putValue(NAME, tr("Remove"));
1117 putValue(SHORT_DESCRIPTION, tr("Remove the selected icon paths"));
1118 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
1119 updateEnabledState();
1120 }
1121
1122 protected void updateEnabledState() {
1123 setEnabled(tblIconPaths.getSelectedRowCount() > 0);
1124 }
1125
1126 @Override
1127 public void valueChanged(ListSelectionEvent e) {
1128 updateEnabledState();
1129 }
1130
1131 @Override
1132 public void actionPerformed(ActionEvent e) {
1133 iconPathsModel.removeSelected();
1134 }
1135 }
1136
1137 class EditIconPathAction extends AbstractAction implements ListSelectionListener {
1138 public EditIconPathAction() {
1139 putValue(NAME, tr("Edit"));
1140 putValue(SHORT_DESCRIPTION, tr("Edit the selected icon path"));
1141 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
1142 updateEnabledState();
1143 }
1144
1145 protected void updateEnabledState() {
1146 setEnabled(tblIconPaths.getSelectedRowCount() == 1);
1147 }
1148
1149 @Override
1150 public void valueChanged(ListSelectionEvent e) {
1151 updateEnabledState();
1152 }
1153
1154 @Override
1155 public void actionPerformed(ActionEvent e) {
1156 int row = tblIconPaths.getSelectedRow();
1157 tblIconPaths.editCellAt(row, 0);
1158 }
1159 }
1160
1161 static class SourceEntryListCellRenderer extends JLabel implements ListCellRenderer {
1162 @Override
1163 public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
1164 boolean cellHasFocus) {
1165 String s = value.toString();
1166 setText(s);
1167 if (isSelected) {
1168 setBackground(list.getSelectionBackground());
1169 setForeground(list.getSelectionForeground());
1170 } else {
1171 setBackground(list.getBackground());
1172 setForeground(list.getForeground());
1173 }
1174 setEnabled(list.isEnabled());
1175 setFont(list.getFont());
1176 setFont(getFont().deriveFont(Font.PLAIN));
1177 setOpaque(true);
1178 setToolTipText(((ExtendedSourceEntry) value).getTooltip());
1179 return this;
1180 }
1181 }
1182
1183 class SourceLoader extends PleaseWaitRunnable {
1184 private final String url;
1185 private final List<SourceProvider> sourceProviders;
1186 private BufferedReader reader;
1187 private boolean canceled;
1188 private final List<ExtendedSourceEntry> sources = new ArrayList<ExtendedSourceEntry>();
1189
1190 public SourceLoader(String url, List<SourceProvider> sourceProviders) {
1191 super(tr(getStr(I18nString.LOADING_SOURCES_FROM), url));
1192 this.url = url;
1193 this.sourceProviders = sourceProviders;
1194 }
1195
1196 @Override
1197 protected void cancel() {
1198 canceled = true;
1199 Utils.close(reader);
1200 }
1201
1202 protected void warn(Exception e) {
1203 String emsg = e.getMessage() != null ? e.getMessage() : e.toString();
1204 emsg = emsg.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
1205 String msg = tr(getStr(I18nString.FAILED_TO_LOAD_SOURCES_FROM), url, emsg);
1206
1207 HelpAwareOptionPane.showOptionDialog(
1208 Main.parent,
1209 msg,
1210 tr("Error"),
1211 JOptionPane.ERROR_MESSAGE,
1212 ht(getStr(I18nString.FAILED_TO_LOAD_SOURCES_FROM_HELP_TOPIC))
1213 );
1214 }
1215
1216 @Override
1217 protected void realRun() throws SAXException, IOException, OsmTransferException {
1218 String lang = LanguageInfo.getLanguageCodeXML();
1219 try {
1220 sources.addAll(getDefault());
1221
1222 for (SourceProvider provider : sourceProviders) {
1223 for (SourceEntry src : provider.getSources()) {
1224 if (src instanceof ExtendedSourceEntry) {
1225 sources.add((ExtendedSourceEntry) src);
1226 }
1227 }
1228 }
1229
1230 MirroredInputStream stream = new MirroredInputStream(url);
1231 InputStreamReader r;
1232 try {
1233 r = new InputStreamReader(stream, "UTF-8");
1234 } catch (UnsupportedEncodingException e) {
1235 r = new InputStreamReader(stream);
1236 }
1237 reader = new BufferedReader(r);
1238
1239 String line;
1240 ExtendedSourceEntry last = null;
1241
1242 while ((line = reader.readLine()) != null && !canceled) {
1243 if (line.trim().isEmpty()) {
1244 continue; // skip empty lines
1245 }
1246 if (line.startsWith("\t")) {
1247 Matcher m = Pattern.compile("^\t([^:]+): *(.+)$").matcher(line);
1248 if (! m.matches()) {
1249 Main.error(tr(getStr(I18nString.ILLEGAL_FORMAT_OF_ENTRY), url, line));
1250 continue;
1251 }
1252 if (last != null) {
1253 String key = m.group(1);
1254 String value = m.group(2);
1255 if ("author".equals(key) && last.author == null) {
1256 last.author = value;
1257 } else if ("version".equals(key)) {
1258 last.version = value;
1259 } else if ("link".equals(key) && last.link == null) {
1260 last.link = value;
1261 } else if ("description".equals(key) && last.description == null) {
1262 last.description = value;
1263 } else if ((lang + "shortdescription").equals(key) && last.title == null) {
1264 last.title = value;
1265 } else if ("shortdescription".equals(key) && last.title == null) {
1266 last.title = value;
1267 } else if ((lang + "title").equals(key) && last.title == null) {
1268 last.title = value;
1269 } else if ("title".equals(key) && last.title == null) {
1270 last.title = value;
1271 } else if ("name".equals(key) && last.name == null) {
1272 last.name = value;
1273 } else if ((lang + "author").equals(key)) {
1274 last.author = value;
1275 } else if ((lang + "link").equals(key)) {
1276 last.link = value;
1277 } else if ((lang + "description").equals(key)) {
1278 last.description = value;
1279 } else if ("min-josm-version".equals(key)) {
1280 try {
1281 last.minJosmVersion = Integer.parseInt(value);
1282 } catch (NumberFormatException e) {
1283 // ignore
1284 }
1285 }
1286 }
1287 } else {
1288 last = null;
1289 Matcher m = Pattern.compile("^(.+);(.+)$").matcher(line);
1290 if (m.matches()) {
1291 sources.add(last = new ExtendedSourceEntry(m.group(1), m.group(2)));
1292 } else {
1293 Main.error(tr(getStr(I18nString.ILLEGAL_FORMAT_OF_ENTRY), url, line));
1294 }
1295 }
1296 }
1297 } catch (Exception e) {
1298 if (canceled)
1299 // ignore the exception and return
1300 return;
1301 OsmTransferException ex = new OsmTransferException(e);
1302 ex.setUrl(url);
1303 warn(ex);
1304 return;
1305 }
1306 }
1307
1308 @Override
1309 protected void finish() {
1310 Collections.sort(sources);
1311 availableSourcesModel.setSources(sources);
1312 }
1313 }
1314
1315 static class SourceEntryTableCellRenderer extends DefaultTableCellRenderer {
1316 @Override
1317 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
1318 if (value == null)
1319 return this;
1320 SourceEntry se = (SourceEntry) value;
1321 JLabel label = (JLabel)super.getTableCellRendererComponent(table,
1322 fromSourceEntry(se), isSelected, hasFocus, row, column);
1323 return label;
1324 }
1325
1326 private String fromSourceEntry(SourceEntry entry) {
1327 if (entry == null)
1328 return null;
1329 StringBuilder s = new StringBuilder("<html><b>");
1330 if (entry.title != null) {
1331 s.append(entry.title).append("</b> <span color=\"gray\">");
1332 }
1333 s.append(entry.url);
1334 if (entry.title != null) {
1335 s.append("</span>");
1336 }
1337 s.append("</html>");
1338 return s.toString();
1339 }
1340 }
1341
1342 class FileOrUrlCellEditor extends JPanel implements TableCellEditor {
1343 private JosmTextField tfFileName;
1344 private CopyOnWriteArrayList<CellEditorListener> listeners;
1345 private String value;
1346 private boolean isFile;
1347
1348 /**
1349 * build the GUI
1350 */
1351 protected void build() {
1352 setLayout(new GridBagLayout());
1353 GridBagConstraints gc = new GridBagConstraints();
1354 gc.gridx = 0;
1355 gc.gridy = 0;
1356 gc.fill = GridBagConstraints.BOTH;
1357 gc.weightx = 1.0;
1358 gc.weighty = 1.0;
1359 add(tfFileName = new JosmTextField(), gc);
1360
1361 gc.gridx = 1;
1362 gc.gridy = 0;
1363 gc.fill = GridBagConstraints.BOTH;
1364 gc.weightx = 0.0;
1365 gc.weighty = 1.0;
1366 add(new JButton(new LaunchFileChooserAction()));
1367
1368 tfFileName.addFocusListener(
1369 new FocusAdapter() {
1370 @Override
1371 public void focusGained(FocusEvent e) {
1372 tfFileName.selectAll();
1373 }
1374 }
1375 );
1376 }
1377
1378 public FileOrUrlCellEditor(boolean isFile) {
1379 this.isFile = isFile;
1380 listeners = new CopyOnWriteArrayList<CellEditorListener>();
1381 build();
1382 }
1383
1384 @Override
1385 public void addCellEditorListener(CellEditorListener l) {
1386 if (l != null) {
1387 listeners.addIfAbsent(l);
1388 }
1389 }
1390
1391 protected void fireEditingCanceled() {
1392 for (CellEditorListener l: listeners) {
1393 l.editingCanceled(new ChangeEvent(this));
1394 }
1395 }
1396
1397 protected void fireEditingStopped() {
1398 for (CellEditorListener l: listeners) {
1399 l.editingStopped(new ChangeEvent(this));
1400 }
1401 }
1402
1403 @Override
1404 public void cancelCellEditing() {
1405 fireEditingCanceled();
1406 }
1407
1408 @Override
1409 public Object getCellEditorValue() {
1410 return value;
1411 }
1412
1413 @Override
1414 public boolean isCellEditable(EventObject anEvent) {
1415 if (anEvent instanceof MouseEvent)
1416 return ((MouseEvent)anEvent).getClickCount() >= 2;
1417 return true;
1418 }
1419
1420 @Override
1421 public void removeCellEditorListener(CellEditorListener l) {
1422 listeners.remove(l);
1423 }
1424
1425 @Override
1426 public boolean shouldSelectCell(EventObject anEvent) {
1427 return true;
1428 }
1429
1430 @Override
1431 public boolean stopCellEditing() {
1432 value = tfFileName.getText();
1433 fireEditingStopped();
1434 return true;
1435 }
1436
1437 public void setInitialValue(String initialValue) {
1438 this.value = initialValue;
1439 if (initialValue == null) {
1440 this.tfFileName.setText("");
1441 } else {
1442 this.tfFileName.setText(initialValue);
1443 }
1444 }
1445
1446 @Override
1447 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
1448 setInitialValue((String)value);
1449 tfFileName.selectAll();
1450 return this;
1451 }
1452
1453 class LaunchFileChooserAction extends AbstractAction {
1454 public LaunchFileChooserAction() {
1455 putValue(NAME, "...");
1456 putValue(SHORT_DESCRIPTION, tr("Launch a file chooser to select a file"));
1457 }
1458
1459 protected void prepareFileChooser(String url, JFileChooser fc) {
1460 if (url == null || url.trim().length() == 0) return;
1461 URL sourceUrl = null;
1462 try {
1463 sourceUrl = new URL(url);
1464 } catch(MalformedURLException e) {
1465 File f = new File(url);
1466 if (f.isFile()) {
1467 f = f.getParentFile();
1468 }
1469 if (f != null) {
1470 fc.setCurrentDirectory(f);
1471 }
1472 return;
1473 }
1474 if (sourceUrl.getProtocol().startsWith("file")) {
1475 File f = new File(sourceUrl.getPath());
1476 if (f.isFile()) {
1477 f = f.getParentFile();
1478 }
1479 if (f != null) {
1480 fc.setCurrentDirectory(f);
1481 }
1482 }
1483 }
1484
1485 @Override
1486 public void actionPerformed(ActionEvent e) {
1487 JFileChooserManager fcm = new JFileChooserManager(true).createFileChooser();
1488 if (!isFile) {
1489 fcm.getFileChooser().setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
1490 }
1491 prepareFileChooser(tfFileName.getText(), fcm.getFileChooser());
1492 JFileChooser fc = fcm.openFileChooser(JOptionPane.getFrameForComponent(SourceEditor.this));
1493 if (fc != null) {
1494 tfFileName.setText(fc.getSelectedFile().toString());
1495 }
1496 }
1497 }
1498 }
1499
1500 abstract public static class SourcePrefHelper {
1501
1502 private final String prefOld;
1503 private final String pref;
1504
1505 public SourcePrefHelper(String pref, String prefOld) {
1506 this.pref = pref;
1507 this.prefOld = prefOld;
1508 }
1509
1510 abstract public Collection<ExtendedSourceEntry> getDefault();
1511
1512 abstract public Map<String, String> serialize(SourceEntry entry);
1513
1514 abstract public SourceEntry deserialize(Map<String, String> entryStr);
1515
1516 public List<SourceEntry> get() {
1517
1518 Collection<Map<String, String>> src = Main.pref.getListOfStructs(pref, (Collection<Map<String, String>>) null);
1519 if (src == null)
1520 return new ArrayList<SourceEntry>(getDefault());
1521
1522 List<SourceEntry> entries = new ArrayList<SourceEntry>();
1523 for (Map<String, String> sourcePref : src) {
1524 SourceEntry e = deserialize(new HashMap<String, String>(sourcePref));
1525 if (e != null) {
1526 entries.add(e);
1527 }
1528 }
1529 return entries;
1530 }
1531
1532 public boolean put(Collection<? extends SourceEntry> entries) {
1533 Collection<Map<String, String>> setting = new ArrayList<Map<String, String>>(entries.size());
1534 for (SourceEntry e : entries) {
1535 setting.add(serialize(e));
1536 }
1537 return Main.pref.putListOfStructs(pref, setting);
1538 }
1539 }
1540
1541}
Note: See TracBrowser for help on using the repository browser.