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