source: josm/trunk/src/org/openstreetmap/josm/gui/io/LayerNameAndFilePathTableCell.java@ 17318

Last change on this file since 17318 was 13115, checked in by Don-vip, 6 years ago

fix #12086 - fix EDT violation when no file exporter is found + choose note exporter by default when saving a note layer

  • Property svn:eol-style set to native
File size: 9.4 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.io;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Color;
7import java.awt.Component;
8import java.awt.Dimension;
9import java.awt.Font;
10import java.awt.GridBagLayout;
11import java.awt.event.ActionEvent;
12import java.awt.event.FocusAdapter;
13import java.awt.event.FocusEvent;
14import java.io.File;
15import java.util.EventObject;
16
17import javax.swing.AbstractAction;
18import javax.swing.BorderFactory;
19import javax.swing.JButton;
20import javax.swing.JLabel;
21import javax.swing.JPanel;
22import javax.swing.JTable;
23import javax.swing.event.CellEditorListener;
24import javax.swing.table.TableCellEditor;
25import javax.swing.table.TableCellRenderer;
26
27import org.openstreetmap.josm.actions.SaveActionBase;
28import org.openstreetmap.josm.gui.layer.NoteLayer;
29import org.openstreetmap.josm.gui.util.CellEditorSupport;
30import org.openstreetmap.josm.gui.widgets.JosmTextField;
31import org.openstreetmap.josm.tools.GBC;
32
33/**
34 * Display and edit layer name and file path in a <code>JTable</code>.
35 *
36 * Note: Do not use the same object both as <code>TableCellRenderer</code> and
37 * <code>TableCellEditor</code> - this can mess up the current editor component
38 * by subsequent calls to the renderer (#12462).
39 */
40class LayerNameAndFilePathTableCell extends JPanel implements TableCellRenderer, TableCellEditor {
41 private static final Color COLOR_ERROR = new Color(255, 197, 197);
42 private static final String ELLIPSIS = '…' + File.separator;
43
44 private final JLabel lblLayerName = new JLabel();
45 private final JLabel lblFilename = new JLabel("");
46 private final JosmTextField tfFilename = new JosmTextField();
47 private final JButton btnFileChooser = new JButton(new LaunchFileChooserAction());
48
49 private static final GBC DEFAULT_CELL_STYLE = GBC.eol().fill(GBC.HORIZONTAL).insets(2, 0, 2, 0);
50
51 private final transient CellEditorSupport cellEditorSupport = new CellEditorSupport(this);
52 private String extension = "osm";
53 private File value;
54
55 /** constructor that sets the default on each element **/
56 LayerNameAndFilePathTableCell() {
57 setLayout(new GridBagLayout());
58
59 lblLayerName.setPreferredSize(new Dimension(lblLayerName.getPreferredSize().width, 19));
60 lblLayerName.setFont(lblLayerName.getFont().deriveFont(Font.BOLD));
61
62 lblFilename.setPreferredSize(new Dimension(lblFilename.getPreferredSize().width, 19));
63 lblFilename.setOpaque(true);
64 lblFilename.setLabelFor(btnFileChooser);
65
66 tfFilename.setToolTipText(tr("Either edit the path manually in the text field or click the \"...\" button to open a file chooser."));
67 tfFilename.setPreferredSize(new Dimension(tfFilename.getPreferredSize().width, 19));
68 tfFilename.addFocusListener(
69 new FocusAdapter() {
70 @Override
71 public void focusGained(FocusEvent e) {
72 tfFilename.selectAll();
73 }
74 }
75 );
76 // hide border
77 tfFilename.setBorder(BorderFactory.createLineBorder(getBackground()));
78
79 btnFileChooser.setPreferredSize(new Dimension(20, 19));
80 btnFileChooser.setOpaque(true);
81 }
82
83 /** renderer used while not editing the file path **/
84 @Override
85 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
86 boolean hasFocus, int row, int column) {
87 removeAll();
88 if (value == null) return this;
89 SaveLayerInfo info = (SaveLayerInfo) value;
90 StringBuilder sb = new StringBuilder();
91 sb.append("<html>")
92 .append(addLblLayerName(info));
93 if (info.isSavable()) {
94 extension = info.getLayer() instanceof NoteLayer ? "osn" : "osm";
95 add(btnFileChooser, GBC.std());
96 sb.append("<br>")
97 .append(addLblFilename(info));
98 }
99 sb.append("</html>");
100 setToolTipText(sb.toString());
101 return this;
102 }
103
104 @Override
105 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
106 removeAll();
107 SaveLayerInfo info = (SaveLayerInfo) value;
108 value = info.getFile();
109 tfFilename.setText(value == null ? "" : value.toString());
110
111 StringBuilder sb = new StringBuilder();
112 sb.append("<html>")
113 .append(addLblLayerName(info));
114
115 if (info.isSavable()) {
116 extension = info.getLayer() instanceof NoteLayer ? "osn" : "osm";
117 add(btnFileChooser, GBC.std());
118 add(tfFilename, GBC.eol().fill(GBC.HORIZONTAL).insets(1, 0, 0, 0));
119 tfFilename.selectAll();
120
121 sb.append("<br>")
122 .append(tfFilename.getToolTipText());
123 }
124 sb.append("</html>");
125 setToolTipText(sb.toString());
126 return this;
127 }
128
129 private static boolean canWrite(File f) {
130 if (f == null || f.isDirectory()) return false;
131 if (f.exists() && f.canWrite()) return true;
132 return !f.exists() && f.getParentFile() != null && f.getParentFile().canWrite();
133 }
134
135 /**
136 * Adds layer name label to (this) using the given info. Returns tooltip that should be added to the panel
137 * @param info information, user preferences and save/upload states of the layer
138 * @return tooltip that should be added to the panel
139 */
140 private String addLblLayerName(SaveLayerInfo info) {
141 lblLayerName.setIcon(info.getLayer().getIcon());
142 lblLayerName.setText(info.getName());
143 add(lblLayerName, DEFAULT_CELL_STYLE);
144 return tr("The bold text is the name of the layer.");
145 }
146
147 /**
148 * Adds filename label to (this) using the given info. Returns tooltip that should be added to the panel
149 * @param info information, user preferences and save/upload states of the layer
150 * @return tooltip that should be added to the panel
151 */
152 private String addLblFilename(SaveLayerInfo info) {
153 String tooltip;
154 boolean error = false;
155 if (info.getFile() == null) {
156 error = info.isDoSaveToFile();
157 lblFilename.setText(tr("Click here to choose save path"));
158 lblFilename.setFont(lblFilename.getFont().deriveFont(Font.ITALIC));
159 tooltip = tr("Layer ''{0}'' is not backed by a file", info.getName());
160 } else {
161 String t = info.getFile().getPath();
162 lblFilename.setText(makePathFit(t));
163 tooltip = info.getFile().getAbsolutePath();
164 if (info.isDoSaveToFile() && !canWrite(info.getFile())) {
165 error = true;
166 tooltip = tr("File ''{0}'' is not writable. Please enter another file name.", info.getFile().getPath());
167 }
168 }
169
170 lblFilename.setBackground(error ? COLOR_ERROR : getBackground());
171 btnFileChooser.setBackground(error ? COLOR_ERROR : getBackground());
172
173 add(lblFilename, DEFAULT_CELL_STYLE);
174 return tr("Click cell to change the file path.") + "<br/>" + tooltip;
175 }
176
177 /**
178 * Makes the given path fit lblFilename, appends ellipsis on the left if it doesn't fit.
179 * Idea: /home/user/josm → …/user/josm → …/josm; and take the first one that fits
180 * @param t complete path
181 * @return shorter path
182 */
183 private String makePathFit(String t) {
184 boolean hasEllipsis = false;
185 while (t != null && !t.isEmpty()) {
186 int txtwidth = lblFilename.getFontMetrics(lblFilename.getFont()).stringWidth(t);
187 if (txtwidth < lblFilename.getWidth() || t.lastIndexOf(File.separator) < ELLIPSIS.length()) {
188 break;
189 }
190 // remove ellipsis, if present
191 t = hasEllipsis ? t.substring(ELLIPSIS.length()) : t;
192 // cut next block, and re-add ellipsis
193 t = ELLIPSIS + t.substring(t.indexOf(File.separator) + 1);
194 hasEllipsis = true;
195 }
196 return t;
197 }
198
199 @Override
200 public void addCellEditorListener(CellEditorListener l) {
201 cellEditorSupport.addCellEditorListener(l);
202 }
203
204 @Override
205 public void cancelCellEditing() {
206 cellEditorSupport.fireEditingCanceled();
207 }
208
209 @Override
210 public Object getCellEditorValue() {
211 return value;
212 }
213
214 @Override
215 public boolean isCellEditable(EventObject anEvent) {
216 return true;
217 }
218
219 @Override
220 public void removeCellEditorListener(CellEditorListener l) {
221 cellEditorSupport.removeCellEditorListener(l);
222 }
223
224 @Override
225 public boolean shouldSelectCell(EventObject anEvent) {
226 return true;
227 }
228
229 @Override
230 public boolean stopCellEditing() {
231 if (tfFilename.getText() == null || tfFilename.getText().trim().isEmpty()) {
232 value = null;
233 } else {
234 value = new File(tfFilename.getText());
235 }
236 cellEditorSupport.fireEditingStopped();
237 return true;
238 }
239
240 private class LaunchFileChooserAction extends AbstractAction {
241 LaunchFileChooserAction() {
242 putValue(NAME, "...");
243 putValue(SHORT_DESCRIPTION, tr("Launch a file chooser to select a file"));
244 }
245
246 @Override
247 public void actionPerformed(ActionEvent e) {
248 File f = SaveActionBase.createAndOpenSaveFileChooser(tr("Select filename"), extension);
249 if (f != null) {
250 tfFilename.setText(f.toString());
251 stopCellEditing();
252 }
253 }
254 }
255}
Note: See TracBrowser for help on using the repository browser.