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

Last change on this file since 10242 was 10021, checked in by bastiK, 8 years ago

see #6301 - fix issues detected by the test

  • Property svn:eol-style set to native
File size: 9.3 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.util.CellEditorSupport;
29import org.openstreetmap.josm.gui.widgets.JosmTextField;
30import org.openstreetmap.josm.tools.GBC;
31
32/**
33 * Display and edit layer name and file path in a <code>JTable</code>.
34 *
35 * Note: Do not use the same object both as <code>TableCellRenderer</code> and
36 * <code>TableCellEditor</code> - this can mess up the current editor component
37 * by subsequent calls to the renderer (#12462).
38 */
39class LayerNameAndFilePathTableCell extends JPanel implements TableCellRenderer, TableCellEditor {
40 private static final Color colorError = new Color(255, 197, 197);
41 private static final String separator = System.getProperty("file.separator");
42 private static final String ellipsis = '…' + 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 defaultCellStyle = GBC.eol().fill(GBC.HORIZONTAL).insets(2, 0, 2, 0);
50
51 private final transient CellEditorSupport cellEditorSupport = new CellEditorSupport(this);
52 private File value;
53
54 /** constructor that sets the default on each element **/
55 LayerNameAndFilePathTableCell() {
56 setLayout(new GridBagLayout());
57
58 lblLayerName.setPreferredSize(new Dimension(lblLayerName.getPreferredSize().width, 19));
59 lblLayerName.setFont(lblLayerName.getFont().deriveFont(Font.BOLD));
60
61 lblFilename.setPreferredSize(new Dimension(lblFilename.getPreferredSize().width, 19));
62 lblFilename.setOpaque(true);
63 lblFilename.setLabelFor(btnFileChooser);
64
65 tfFilename.setToolTipText(tr("Either edit the path manually in the text field or click the \"...\" button to open a file chooser."));
66 tfFilename.setPreferredSize(new Dimension(tfFilename.getPreferredSize().width, 19));
67 tfFilename.addFocusListener(
68 new FocusAdapter() {
69 @Override
70 public void focusGained(FocusEvent e) {
71 tfFilename.selectAll();
72 }
73 }
74 );
75 // hide border
76 tfFilename.setBorder(BorderFactory.createLineBorder(getBackground()));
77
78 btnFileChooser.setPreferredSize(new Dimension(20, 19));
79 btnFileChooser.setOpaque(true);
80 }
81
82 /** renderer used while not editing the file path **/
83 @Override
84 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
85 boolean hasFocus, int row, int column) {
86 removeAll();
87 if (value == null) return this;
88 SaveLayerInfo info = (SaveLayerInfo) value;
89 StringBuilder sb = new StringBuilder();
90 sb.append("<html>")
91 .append(addLblLayerName(info));
92 if (info.isSavable()) {
93 add(btnFileChooser, GBC.std());
94 sb.append("<br>")
95 .append(addLblFilename(info));
96 }
97 sb.append("</html>");
98 setToolTipText(sb.toString());
99 return this;
100 }
101
102 @Override
103 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
104 removeAll();
105 SaveLayerInfo info = (SaveLayerInfo) value;
106 value = info.getFile();
107 tfFilename.setText(value == null ? "" : value.toString());
108
109 StringBuilder sb = new StringBuilder();
110 sb.append("<html>")
111 .append(addLblLayerName(info));
112
113 if (info.isSavable()) {
114 add(btnFileChooser, GBC.std());
115 add(tfFilename, GBC.eol().fill(GBC.HORIZONTAL).insets(1, 0, 0, 0));
116 tfFilename.selectAll();
117
118 sb.append("<br>")
119 .append(tfFilename.getToolTipText());
120 }
121 sb.append("</html>");
122 setToolTipText(sb.toString());
123 return this;
124 }
125
126 private static boolean canWrite(File f) {
127 if (f == null) return false;
128 if (f.isDirectory()) return false;
129 if (f.exists() && f.canWrite()) return true;
130 if (!f.exists() && f.getParentFile() != null && f.getParentFile().canWrite())
131 return true;
132 return false;
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, defaultCellStyle);
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 ? colorError : getBackground());
171 btnFileChooser.setBackground(error ? colorError : getBackground());
172
173 add(lblFilename, defaultCellStyle);
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(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(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"), "osm");
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.