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

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

fix #7386 - Major rework of preferences GUI settings in order to speed up preferences dialog startup time. The building of each preferences tab is delayed until this tab is selected. Plugins that use preferences will need to make some (minor) changes.

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