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

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

see #8570, #7406 - I/O refactorization:

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