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