// License: GPL. Copyright 2007 by Immanuel Scholz and others package org.openstreetmap.josm.gui.preferences; import static org.openstreetmap.josm.gui.help.HelpUtil.ht; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.Component; import java.awt.FlowLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.EventObject; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.DefaultListModel; import javax.swing.DefaultListSelectionModel; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.ListCellRenderer; import javax.swing.ListSelectionModel; import javax.swing.event.CellEditorListener; import javax.swing.event.ChangeEvent; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableCellEditor; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.gui.HelpAwareOptionPane; import org.openstreetmap.josm.gui.PleaseWaitRunnable; import org.openstreetmap.josm.io.MirroredInputStream; import org.openstreetmap.josm.io.OsmTransferException; import org.openstreetmap.josm.tools.GBC; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.LanguageInfo; import org.xml.sax.SAXException; public class StyleSourceEditor extends JPanel { private JTable tblActiveStyles; private ActiveStylesModel activeStylesModel; private JList lstAvailableStyles; private AvailableStylesListModel availableStylesModel; private JTable tblIconPaths = null; private IconPathTableModel iconPathsModel; private String pref; private String iconpref; private boolean stylesInitiallyLoaded; private String availableStylesUrl; /** * * @param stylesPreferencesKey the preferences key with the list of active style sources (filenames and URLs) * @param iconsPreferenceKey the preference key with the list of icon sources (can be null) * @param availableStylesUrl the URL to the list of available style sources */ public StyleSourceEditor(String stylesPreferencesKey, String iconsPreferenceKey, final String availableStylesUrl) { DefaultListSelectionModel selectionModel = new DefaultListSelectionModel(); tblActiveStyles = new JTable(activeStylesModel = new ActiveStylesModel(selectionModel)); tblActiveStyles.putClientProperty("terminateEditOnFocusLost", true); tblActiveStyles.setSelectionModel(selectionModel); tblActiveStyles.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); tblActiveStyles.setTableHeader(null); tblActiveStyles.getColumnModel().getColumn(0).setCellEditor(new FileOrUrlCellEditor()); tblActiveStyles.setRowHeight(20); activeStylesModel.setActiveStyles(Main.pref.getCollection(stylesPreferencesKey, null)); selectionModel = new DefaultListSelectionModel(); lstAvailableStyles = new JList(availableStylesModel =new AvailableStylesListModel(selectionModel)); lstAvailableStyles.setSelectionModel(selectionModel); lstAvailableStyles.setCellRenderer(new StyleSourceCellRenderer()); //availableStylesModel.setStyleSources(reloadAvailableStyles(availableStylesUrl)); this.availableStylesUrl = availableStylesUrl; this.pref = stylesPreferencesKey; this.iconpref = iconsPreferenceKey; JButton iconadd = null; JButton iconedit = null; JButton icondelete = null; if (iconsPreferenceKey != null) { selectionModel = new DefaultListSelectionModel(); tblIconPaths = new JTable(iconPathsModel = new IconPathTableModel(selectionModel)); tblIconPaths.setSelectionModel(selectionModel); tblIconPaths.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); tblIconPaths.setTableHeader(null); tblIconPaths.getColumnModel().getColumn(0).setCellEditor(new FileOrUrlCellEditor()); tblIconPaths.setRowHeight(20); iconPathsModel.setIconPaths(Main.pref.getCollection(iconsPreferenceKey, null)); iconadd = new JButton(new NewIconPathAction()); EditIconPathAction editIconPathAction = new EditIconPathAction(); tblIconPaths.getSelectionModel().addListSelectionListener(editIconPathAction); iconedit = new JButton(editIconPathAction); RemoveIconPathAction removeIconPathAction = new RemoveIconPathAction(); tblIconPaths.getSelectionModel().addListSelectionListener(removeIconPathAction); icondelete = new JButton(removeIconPathAction); tblIconPaths.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,0), "delete"); tblIconPaths.getActionMap().put("delete", removeIconPathAction); } JButton add = new JButton(new NewActiveStyleAction()); EditActiveStyleAction editActiveStyleAction = new EditActiveStyleAction(); tblActiveStyles.getSelectionModel().addListSelectionListener(editActiveStyleAction); JButton edit = new JButton(editActiveStyleAction); RemoveActiveStylesAction removeActiveStylesAction = new RemoveActiveStylesAction(); tblActiveStyles.getSelectionModel().addListSelectionListener(removeActiveStylesAction); tblActiveStyles.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,0), "delete"); tblActiveStyles.getActionMap().put("delete", removeActiveStylesAction); JButton delete = new JButton(removeActiveStylesAction); ActivateStylesAction activateStylesAction = new ActivateStylesAction(); lstAvailableStyles.addListSelectionListener(activateStylesAction); JButton copy = new JButton(activateStylesAction); JButton update = new JButton(new ReloadStylesAction(availableStylesUrl)); setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); setLayout(new GridBagLayout()); add(new JLabel(tr("Active styles")), GBC.eol().insets(5, 5, 5, 0)); JScrollPane sp; add(sp = new JScrollPane(tblActiveStyles), GBC.eol().insets(5, 0, 5, 0).fill(GBC.BOTH)); sp.setColumnHeaderView(null); JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); add(buttonPanel, GBC.eol().insets(5, 0, 5, 5).fill(GBC.HORIZONTAL)); buttonPanel.add(add, GBC.std().insets(0, 5, 0, 0)); buttonPanel.add(edit, GBC.std().insets(5, 5, 5, 0)); buttonPanel.add(delete, GBC.std().insets(0, 5, 5, 0)); buttonPanel.add(copy, GBC.std().insets(0, 5, 5, 0)); add(new JLabel(tr("Available styles (from {0})", availableStylesUrl)), GBC.eol().insets(5, 5, 5, 0)); add(new JScrollPane(lstAvailableStyles), GBC.eol().insets(5, 0, 5, 0).fill(GBC.BOTH)); buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); add(buttonPanel, GBC.eol().insets(5, 0, 5, 5).fill(GBC.HORIZONTAL)); buttonPanel.add(update, GBC.std().insets(0, 5, 0, 0)); if (tblIconPaths != null) { add(new JLabel(tr("Icon paths")), GBC.eol().insets(5, -5, 5, 0)); add(sp = new JScrollPane(tblIconPaths), GBC.eol().insets(5, 0, 5, 0).fill(GBC.BOTH)); sp.setColumnHeaderView(null); buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); add(buttonPanel, GBC.eol().insets(5, 0, 5, 5).fill(GBC.HORIZONTAL)); buttonPanel.add(iconadd); buttonPanel.add(iconedit); buttonPanel.add(icondelete); } } public boolean finish() { boolean changed = false; List<String> activeStyles = activeStylesModel.getStyles(); if (activeStyles.size() > 0) { if (Main.pref.putCollection(pref, activeStyles)) { changed = true; } } else if (Main.pref.putCollection(pref, null)) { changed = true; } if (tblIconPaths != null) { List<String> iconPaths = iconPathsModel.getIconPaths(); if (!iconPaths.isEmpty()) { if (Main.pref.putCollection(iconpref, iconPaths)) { changed = true; } } else if (Main.pref.putCollection(iconpref, null)) { changed = true; } } return changed; } protected void reloadAvailableStyles(String url) { Main.worker.submit(new StyleSourceLoader(url)); } public void initiallyLoadAvailableStyles() { if (!stylesInitiallyLoaded) { reloadAvailableStyles(this.availableStylesUrl); } stylesInitiallyLoaded = true; } static class AvailableStylesListModel extends DefaultListModel { private ArrayList<StyleSourceInfo> data; private DefaultListSelectionModel selectionModel; public AvailableStylesListModel(DefaultListSelectionModel selectionModel) { data = new ArrayList<StyleSourceInfo>(); this.selectionModel = selectionModel; } public void setStyleSources(List<StyleSourceInfo> styleSources) { data.clear(); if (styleSources != null) { data.addAll(styleSources); } fireContentsChanged(this, 0, data.size()); } @Override public Object getElementAt(int index) { return data.get(index); } @Override public int getSize() { if (data == null) return 0; return data.size(); } public void deleteSelected() { Iterator<StyleSourceInfo> it = data.iterator(); int i=0; while(it.hasNext()) { it.next(); if (selectionModel.isSelectedIndex(i)) { it.remove(); } i++; } fireContentsChanged(this, 0, data.size()); } public List<StyleSourceInfo> getSelected() { ArrayList<StyleSourceInfo> ret = new ArrayList<StyleSourceInfo>(); for(int i=0; i<data.size();i++) { if (selectionModel.isSelectedIndex(i)) { ret.add(data.get(i)); } } return ret; } } static class ActiveStylesModel extends AbstractTableModel { private ArrayList<String> data; private DefaultListSelectionModel selectionModel; public ActiveStylesModel(DefaultListSelectionModel selectionModel) { this.selectionModel = selectionModel; this.data = new ArrayList<String>(); } public int getColumnCount() { return 1; } public int getRowCount() { return data == null ? 0 : data.size(); } public Object getValueAt(int rowIndex, int columnIndex) { return data.get(rowIndex); } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return true; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { updateStyle(rowIndex, (String)aValue); } public void setActiveStyles(Collection<String> styles) { data.clear(); if (styles !=null) { data.addAll(styles); } sort(); fireTableDataChanged(); } public void addStyle(String style) { if (style == null) return; data.add(style); sort(); fireTableDataChanged(); int idx = data.indexOf(style); if (idx >= 0) { selectionModel.setSelectionInterval(idx, idx); } } public void updateStyle(int pos, String style) { if (style == null) return; if (pos < 0 || pos >= getRowCount()) return; data.set(pos, style); sort(); fireTableDataChanged(); int idx = data.indexOf(style); if (idx >= 0) { selectionModel.setSelectionInterval(idx, idx); } } public void removeSelected() { Iterator<String> it = data.iterator(); int i=0; while(it.hasNext()) { it.next(); if (selectionModel.isSelectedIndex(i)) { it.remove(); } i++; } fireTableDataChanged(); } protected void sort() { Collections.sort( data, new Comparator<String>() { public int compare(String o1, String o2) { if (o1.equals("") && o2.equals("")) return 0; if (o1.equals("")) return 1; if (o2.equals("")) return -1; return o1.compareTo(o2); } } ); } public void addStylesFromSources(List<StyleSourceInfo> sources) { if (sources == null) return; for (StyleSourceInfo info: sources) { data.add(info.url); } sort(); fireTableDataChanged(); selectionModel.clearSelection(); for (StyleSourceInfo info: sources) { int pos = data.indexOf(info.url); if (pos >=0) { selectionModel.addSelectionInterval(pos, pos); } } } public List<String> getStyles() { return new ArrayList<String>(data); } public String getStyle(int pos) { return data.get(pos); } } public static class StyleSourceInfo { String version; String name; String url; String author; String link; String description; String shortdescription; public StyleSourceInfo(String name, String url) { this.name = name; this.url = url; version = author = link = description = shortdescription = null; } public String getName() { return shortdescription == null ? name : shortdescription; } public String getTooltip() { String s = tr("Short Description: {0}", getName()) + "<br>" + tr("URL: {0}", url); if (author != null) { s += "<br>" + tr("Author: {0}", author); } if (link != null) { s += "<br>" + tr("Webpage: {0}", link); } if (description != null) { s += "<br>" + tr("Description: {0}", description); } if (version != null) { s += "<br>" + tr("Version: {0}", version); } return "<html>" + s + "</html>"; } @Override public String toString() { return getName() + " (" + url + ")"; } } class NewActiveStyleAction extends AbstractAction { public NewActiveStyleAction() { putValue(NAME, tr("New")); putValue(SHORT_DESCRIPTION, tr("Add a filename or an URL of an active style")); putValue(SMALL_ICON, ImageProvider.get("dialogs", "add")); } public void actionPerformed(ActionEvent e) { activeStylesModel.addStyle(""); tblActiveStyles.requestFocusInWindow(); tblActiveStyles.editCellAt(activeStylesModel.getRowCount()-1, 0); } } class RemoveActiveStylesAction extends AbstractAction implements ListSelectionListener { public RemoveActiveStylesAction() { putValue(NAME, tr("Remove")); putValue(SHORT_DESCRIPTION, tr("Remove the selected styles from the list of active styles")); putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete")); updateEnabledState(); } protected void updateEnabledState() { setEnabled(tblActiveStyles.getSelectedRowCount() > 0); } public void valueChanged(ListSelectionEvent e) { updateEnabledState(); } public void actionPerformed(ActionEvent e) { activeStylesModel.removeSelected(); } } class EditActiveStyleAction extends AbstractAction implements ListSelectionListener { public EditActiveStyleAction() { putValue(NAME, tr("Edit")); putValue(SHORT_DESCRIPTION, tr("Edit the filename or URL for the selected active style")); putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit")); updateEnabledState(); } protected void updateEnabledState() { setEnabled(tblActiveStyles.getSelectedRowCount() == 1); } public void valueChanged(ListSelectionEvent e) { updateEnabledState(); } public void actionPerformed(ActionEvent e) { int pos = tblActiveStyles.getSelectedRow(); tblActiveStyles.editCellAt(pos, 0); } } class ActivateStylesAction extends AbstractAction implements ListSelectionListener { public ActivateStylesAction() { putValue(NAME, tr("Activate")); putValue(SHORT_DESCRIPTION, tr("Add the selected available styles to the list of active styles")); putValue(SMALL_ICON, ImageProvider.get("preferences", "activatestyle")); updateEnabledState(); } protected void updateEnabledState() { setEnabled(lstAvailableStyles.getSelectedIndices().length > 0); } public void valueChanged(ListSelectionEvent e) { updateEnabledState(); } public void actionPerformed(ActionEvent e) { List<StyleSourceInfo> styleSources = availableStylesModel.getSelected(); activeStylesModel.addStylesFromSources(styleSources); } } class ReloadStylesAction extends AbstractAction { private String url; public ReloadStylesAction(String url) { putValue(NAME, tr("Reload")); putValue(SHORT_DESCRIPTION, tr("Reloads the list of available styles from ''{0}''", url)); putValue(SMALL_ICON, ImageProvider.get("download")); this.url = url; } public void actionPerformed(ActionEvent e) { MirroredInputStream.cleanup(url); reloadAvailableStyles(url); } } static class IconPathTableModel extends AbstractTableModel { private ArrayList<String> data; private DefaultListSelectionModel selectionModel; public IconPathTableModel(DefaultListSelectionModel selectionModel) { this.selectionModel = selectionModel; this.data = new ArrayList<String>(); } public int getColumnCount() { return 1; } public int getRowCount() { return data == null ? 0 : data.size(); } public Object getValueAt(int rowIndex, int columnIndex) { return data.get(rowIndex); } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return true; } @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { updatePath(rowIndex, (String)aValue); } public void setIconPaths(Collection<String> styles) { data.clear(); if (styles !=null) { data.addAll(styles); } sort(); fireTableDataChanged(); } public void addPath(String path) { if (path == null) return; data.add(path); sort(); fireTableDataChanged(); int idx = data.indexOf(path); if (idx >= 0) { selectionModel.setSelectionInterval(idx, idx); } } public void updatePath(int pos, String path) { if (path == null) return; if (pos < 0 || pos >= getRowCount()) return; data.set(pos, path); sort(); fireTableDataChanged(); int idx = data.indexOf(path); if (idx >= 0) { selectionModel.setSelectionInterval(idx, idx); } } public void removeSelected() { Iterator<String> it = data.iterator(); int i=0; while(it.hasNext()) { it.next(); if (selectionModel.isSelectedIndex(i)) { it.remove(); } i++; } fireTableDataChanged(); selectionModel.clearSelection(); } protected void sort() { Collections.sort( data, new Comparator<String>() { public int compare(String o1, String o2) { if (o1.equals("") && o2.equals("")) return 0; if (o1.equals("")) return 1; if (o2.equals("")) return -1; return o1.compareTo(o2); } } ); } public List<String> getIconPaths() { return new ArrayList<String>(data); } } class NewIconPathAction extends AbstractAction { public NewIconPathAction() { putValue(NAME, tr("New")); putValue(SHORT_DESCRIPTION, tr("Add a new icon path")); putValue(SMALL_ICON, ImageProvider.get("dialogs", "add")); } public void actionPerformed(ActionEvent e) { iconPathsModel.addPath(""); tblIconPaths.editCellAt(iconPathsModel.getRowCount() -1,0); } } class RemoveIconPathAction extends AbstractAction implements ListSelectionListener { public RemoveIconPathAction() { putValue(NAME, tr("Remove")); putValue(SHORT_DESCRIPTION, tr("Remove the selected icon paths")); putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete")); updateEnabledState(); } protected void updateEnabledState() { setEnabled(tblIconPaths.getSelectedRowCount() > 0); } public void valueChanged(ListSelectionEvent e) { updateEnabledState(); } public void actionPerformed(ActionEvent e) { iconPathsModel.removeSelected(); } } class EditIconPathAction extends AbstractAction implements ListSelectionListener { public EditIconPathAction() { putValue(NAME, tr("Edit")); putValue(SHORT_DESCRIPTION, tr("Edit the selected icon path")); putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit")); updateEnabledState(); } protected void updateEnabledState() { setEnabled(tblIconPaths.getSelectedRowCount() == 1); } public void valueChanged(ListSelectionEvent e) { updateEnabledState(); } public void actionPerformed(ActionEvent e) { int row = tblIconPaths.getSelectedRow(); tblIconPaths.editCellAt(row, 0); } } static class StyleSourceCellRenderer extends JLabel implements ListCellRenderer { public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { String s = value.toString(); setText(s); if (isSelected) { setBackground(list.getSelectionBackground()); setForeground(list.getSelectionForeground()); } else { setBackground(list.getBackground()); setForeground(list.getForeground()); } setEnabled(list.isEnabled()); setFont(list.getFont()); setOpaque(true); setToolTipText(((StyleSourceInfo) value).getTooltip()); return this; } } class StyleSourceLoader extends PleaseWaitRunnable { private String url; private BufferedReader reader; private boolean canceled; public StyleSourceLoader(String url) { super(tr("Loading style sources from ''{0}''", url)); this.url = url; } @Override protected void cancel() { canceled = true; if (reader!= null) { try { reader.close(); } catch(IOException e) { // ignore } } } @Override protected void finish() {} protected void warn(Exception e) { String emsg = e.getMessage() != null ? e.getMessage() : e.toString(); emsg = emsg.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">"); String msg = tr("<html>Failed to load the list of style sources from<br>" + "''{0}''.<br>" + "<br>" + "Details (untranslated):<br>{1}</html>", url, emsg ); HelpAwareOptionPane.showOptionDialog( Main.parent, msg, tr("Error"), JOptionPane.ERROR_MESSAGE, ht("Preferences/Styles#FailedToLoadStyleSources") ); } @Override protected void realRun() throws SAXException, IOException, OsmTransferException { LinkedList<StyleSourceInfo> styles = new LinkedList<StyleSourceInfo>(); String lang = LanguageInfo.getLanguageCodeXML(); try { MirroredInputStream stream = new MirroredInputStream(url); InputStreamReader r; try { r = new InputStreamReader(stream, "UTF-8"); } catch (UnsupportedEncodingException e) { r = new InputStreamReader(stream); } BufferedReader reader = new BufferedReader(r); String line; StyleSourceInfo last = null; while ((line = reader.readLine()) != null && !canceled) { if (line.trim().equals("")) { continue; // skip empty lines } if (line.startsWith("\t")) { Matcher m = Pattern.compile("^\t([^:]+): *(.+)$").matcher(line); if (! m.matches()) { System.err.println(tr("Warning: illegal format of entry in style list ''{0}''. Got ''{1}''", url, line)); continue; } if (last != null) { String key = m.group(1); String value = m.group(2); if ("author".equals(key) && last.author == null) { last.author = value; } else if ("version".equals(key)) { last.version = value; } else if ("link".equals(key) && last.link == null) { last.link = value; } else if ("description".equals(key) && last.description == null) { last.description = value; } else if ("shortdescription".equals(key) && last.shortdescription == null) { last.shortdescription = value; } else if ((lang + "author").equals(key)) { last.author = value; } else if ((lang + "link").equals(key)) { last.link = value; } else if ((lang + "description").equals(key)) { last.description = value; } else if ((lang + "shortdescription").equals(key)) { last.shortdescription = value; } } } else { last = null; Matcher m = Pattern.compile("^(.+);(.+)$").matcher(line); if (m.matches()) { styles.add(last = new StyleSourceInfo(m.group(1), m.group(2))); } else { System.err.println(tr("Warning: illegal format of entry in style list ''{0}''. Got ''{1}''", url, line)); } } } } catch (Exception e) { if (canceled) // ignore the exception and return return; warn(e); return; } availableStylesModel.setStyleSources(styles); } } class FileOrUrlCellEditor extends JPanel implements TableCellEditor { private JTextField tfFileName; private CopyOnWriteArrayList<CellEditorListener> listeners; private String value; private JFileChooser fileChooser; protected JFileChooser getFileChooser() { if (fileChooser == null) { this.fileChooser = new JFileChooser(); } return fileChooser; } /** * build the GUI */ protected void build() { setLayout(new GridBagLayout()); GridBagConstraints gc = new GridBagConstraints(); gc.gridx = 0; gc.gridy = 0; gc.fill = GridBagConstraints.BOTH; gc.weightx = 1.0; gc.weighty = 1.0; add(tfFileName = new JTextField(), gc); gc.gridx = 1; gc.gridy = 0; gc.fill = GridBagConstraints.BOTH; gc.weightx = 0.0; gc.weighty = 1.0; add(new JButton(new LaunchFileChooserAction())); tfFileName.addFocusListener( new FocusAdapter() { @Override public void focusGained(FocusEvent e) { tfFileName.selectAll(); } } ); } public FileOrUrlCellEditor() { listeners = new CopyOnWriteArrayList<CellEditorListener>(); build(); } public void addCellEditorListener(CellEditorListener l) { if (l != null) { listeners.addIfAbsent(l); } } protected void fireEditingCanceled() { for (CellEditorListener l: listeners) { l.editingCanceled(new ChangeEvent(this)); } } protected void fireEditingStopped() { for (CellEditorListener l: listeners) { l.editingStopped(new ChangeEvent(this)); } } public void cancelCellEditing() { fireEditingCanceled(); } public Object getCellEditorValue() { return value; } public boolean isCellEditable(EventObject anEvent) { if (anEvent instanceof MouseEvent) return ((MouseEvent)anEvent).getClickCount() >= 2; return true; } public void removeCellEditorListener(CellEditorListener l) { listeners.remove(l); } public boolean shouldSelectCell(EventObject anEvent) { return true; } public boolean stopCellEditing() { value = tfFileName.getText(); fireEditingStopped(); return true; } public void setInitialValue(String initialValue) { this.value = initialValue; if (initialValue == null) { this.tfFileName.setText(""); } else { this.tfFileName.setText(initialValue); } } public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { setInitialValue((String)value); tfFileName.selectAll(); return this; } class LaunchFileChooserAction extends AbstractAction { public LaunchFileChooserAction() { putValue(NAME, "..."); putValue(SHORT_DESCRIPTION, tr("Launch a file chooser to select a file")); } protected void prepareFileChooser(String url, JFileChooser fc) { if (url == null || url.trim().length() == 0) return; URL sourceUrl = null; try { sourceUrl = new URL(url); } catch(MalformedURLException e) { File f = new File(url); if (f.isFile()) { f = f.getParentFile(); } if (f != null) { fc.setCurrentDirectory(f); } return; } if (sourceUrl.getProtocol().startsWith("file")) { File f = new File(sourceUrl.getPath()); if (f.isFile()) { f = f.getParentFile(); } if (f != null) { fc.setCurrentDirectory(f); } } } public void actionPerformed(ActionEvent e) { JFileChooser fc = getFileChooser(); prepareFileChooser(tfFileName.getText(), fc); int ret = fc.showOpenDialog(JOptionPane.getFrameForComponent(StyleSourceEditor.this)); if (ret != JFileChooser.APPROVE_OPTION) return; tfFileName.setText(fc.getSelectedFile().toString()); } } } }