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

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

Allow plugins to register custom map painting styles and tagging presets

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