source: josm/trunk/src/org/openstreetmap/josm/gui/tagging/TagTable.java@ 2657

Last change on this file since 2657 was 2626, checked in by jttt, 14 years ago

Fixed some of the warnings found by FindBugs

File size: 13.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.tagging;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.AWTException;
7import java.awt.Container;
8import java.awt.Dimension;
9import java.awt.MouseInfo;
10import java.awt.Point;
11import java.awt.Rectangle;
12import java.awt.Robot;
13import java.awt.event.ActionEvent;
14import java.awt.event.InputEvent;
15import java.awt.event.KeyEvent;
16import java.awt.event.KeyListener;
17import java.util.logging.Level;
18import java.util.logging.Logger;
19
20import javax.swing.AbstractAction;
21import javax.swing.Action;
22import javax.swing.JComponent;
23import javax.swing.JTable;
24import javax.swing.JViewport;
25import javax.swing.KeyStroke;
26import javax.swing.ListSelectionModel;
27import javax.swing.SwingUtilities;
28import javax.swing.event.ListSelectionEvent;
29import javax.swing.event.ListSelectionListener;
30import javax.swing.table.DefaultTableColumnModel;
31import javax.swing.table.TableColumn;
32import javax.swing.table.TableModel;
33
34import org.openstreetmap.josm.gui.dialogs.relation.RunnableAction;
35import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionCache;
36import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
37
38/**
39 * This is the tabular editor component for OSM tags.
40 *
41 */
42@SuppressWarnings("serial")
43public class TagTable extends JTable {
44
45 private static Logger logger = Logger.getLogger(TagTable.class.getName());
46
47 /** the table cell editor used by this table */
48 private TagCellEditor editor = null;
49
50 /**
51 * The table has two columns. The first column is used for editing rendering and
52 * editing tag keys, the second for rendering and editing tag values.
53 *
54 */
55 static class TagTableColumnModel extends DefaultTableColumnModel {
56
57 public TagTableColumnModel() {
58 TableColumn col = null;
59 TagCellRenderer renderer = new TagCellRenderer();
60
61 // column 0 - tag key
62 col = new TableColumn(0);
63 col.setHeaderValue(tr("Key"));
64 col.setResizable(true);
65 col.setCellRenderer(renderer);
66 addColumn(col);
67
68 // column 1 - tag value
69 col = new TableColumn(1);
70 col.setHeaderValue(tr("Value"));
71 col.setResizable(true);
72 col.setCellRenderer(renderer);
73 addColumn(col);
74
75 }
76 }
77
78 /**
79 * Action to be run when the user navigates to the next cell in the table,
80 * for instance by pressing TAB or ENTER. The action alters the standard
81 * navigation path from cell to cell:
82 * <ul>
83 * <li>it jumps over cells in the first column</li>
84 * <li>it automatically add a new empty row when the user leaves the
85 * last cell in the table</li>
86 * <ul>
87 *
88 * @author gubaer
89 *
90 */
91 class SelectNextColumnCellAction extends AbstractAction {
92 public void actionPerformed(ActionEvent e) {
93 run();
94 }
95
96 public void run() {
97 int col = getSelectedColumn();
98 int row = getSelectedRow();
99 if (getCellEditor() != null) {
100 getCellEditor().stopCellEditing();
101 }
102
103 if (col == 0) {
104 col++;
105 } else if (col == 1 && row < getRowCount()-1) {
106 col=0;
107 row++;
108 } else if (col == 1 && row == getRowCount()-1){
109 // we are at the end. Append an empty row and move the focus
110 // to its second column
111 TagEditorModel model = (TagEditorModel)getModel();
112 model.appendNewTag();
113 col=0;
114 row++;
115 }
116 changeSelection(row, col, false, false);
117 }
118 }
119
120 /**
121 * Action to be run when the user navigates to the previous cell in the table,
122 * for instance by pressing Shift-TAB
123 *
124 */
125 class SelectPreviousColumnCellAction extends AbstractAction {
126
127 public void actionPerformed(ActionEvent e) {
128 int col = getSelectedColumn();
129 int row = getSelectedRow();
130 if (getCellEditor() != null) {
131 getCellEditor().stopCellEditing();
132 }
133
134 if (col <= 0 && row <= 0) {
135 // change nothing
136 } else if (col == 1) {
137 col--;
138 } else {
139 col = 1;
140 row--;
141 }
142 changeSelection(row, col, false, false);
143 }
144 }
145
146 /**
147 * Action to be run when the user invokes a delete action on the table, for
148 * instance by pressing DEL.
149 *
150 * Depending on the shape on the current selection the action deletes individual
151 * values or entire tags from the model.
152 *
153 * If the current selection consists of cells in the second column only, the keys of
154 * the selected tags are set to the empty string.
155 *
156 * If the current selection consists of cell in the third column only, the values of the
157 * selected tags are set to the empty string.
158 *
159 * If the current selection consists of cells in the second and the third column,
160 * the selected tags are removed from the model.
161 *
162 * This action listens to the table selection. It becomes enabled when the selection
163 * is non-empty, otherwise it is disabled.
164 *
165 *
166 */
167 class DeleteAction extends RunnableAction implements ListSelectionListener {
168
169 /**
170 * delete a selection of tag names
171 */
172 protected void deleteTagNames() {
173 int[] rows = getSelectedRows();
174 TagEditorModel model = (TagEditorModel)getModel();
175 model.deleteTagNames(rows);
176 }
177
178 /**
179 * delete a selection of tag values
180 */
181 protected void deleteTagValues() {
182 int[] rows = getSelectedRows();
183 TagEditorModel model = (TagEditorModel)getModel();
184 model.deleteTagValues(rows);
185 }
186
187 /**
188 * delete a selection of tags
189 */
190 protected void deleteTags() {
191 int[] rows = getSelectedRows();
192 TagEditorModel model = (TagEditorModel)getModel();
193 model.deleteTags(rows);
194 }
195
196 /**
197 * constructor
198 */
199 public DeleteAction() {
200 putValue(Action.NAME, tr("Delete"));
201 getSelectionModel().addListSelectionListener(this);
202 getColumnModel().getSelectionModel().addListSelectionListener(this);
203 }
204
205 @Override
206 public void run() {
207 if (!isEnabled())
208 return;
209 getCellEditor().stopCellEditing();
210 if (getSelectedColumnCount() == 1) {
211 if (getSelectedColumn() == 0) {
212 deleteTagNames();
213 } else if (getSelectedColumn() == 1) {
214 deleteTagValues();
215 } else
216 // should not happen
217 //
218 throw new IllegalStateException("unexpected selected clolumn: getSelectedColumn() is " + getSelectedColumn());
219 } else if (getSelectedColumnCount() == 2) {
220 deleteTags();
221 }
222 TagEditorModel model = (TagEditorModel)getModel();
223 if (model.getRowCount() == 0) {
224 model.ensureOneTag();
225 requestFocusInCell(0, 0);
226 }
227 }
228
229 /**
230 * listens to the table selection model
231 */
232 public void valueChanged(ListSelectionEvent e) {
233 if (isEditing() && getSelectedColumnCount() == 1 && getSelectedRowCount() == 1) {
234 setEnabled(false);
235 } else if (!isEditing() && getSelectedColumnCount() == 1 && getSelectedRowCount() == 1) {
236 setEnabled(true);
237 } else if (getSelectedColumnCount() > 1 || getSelectedRowCount() > 1) {
238 setEnabled(true);
239 } else {
240 setEnabled(false);
241 }
242
243 }
244 }
245
246 /**
247 * Action to be run when the user adds a new tag.
248 *
249 *
250 */
251 class AddAction extends RunnableAction {
252
253 public AddAction() {
254 putValue(Action.NAME, tr("Add"));
255 }
256
257 @Override
258 public void run() {
259 getCellEditor().stopCellEditing();
260 ((TagEditorModel)getModel()).appendNewTag();
261 final int rowIdx = getModel().getRowCount()-1;
262 requestFocusInCell(rowIdx, 0);
263 }
264 }
265
266 /** the delete action */
267 private RunnableAction deleteAction = null;
268
269 /** the add action */
270 private RunnableAction addAction = null;
271
272 /**
273 *
274 * @return the delete action used by this table
275 */
276 public RunnableAction getDeleteAction() {
277 return deleteAction;
278 }
279
280 public RunnableAction getAddAction() {
281 return addAction;
282 }
283
284 /**
285 * initialize the table
286 */
287 protected void init() {
288 setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
289 setCellSelectionEnabled(true);
290 setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
291 putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
292
293 // make ENTER behave like TAB
294 //
295 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
296 .put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "selectNextColumnCell");
297
298 // install custom navigation actions
299 //
300 getActionMap().put("selectNextColumnCell", new SelectNextColumnCellAction());
301 getActionMap().put("selectPreviousColumnCell", new SelectPreviousColumnCellAction());
302
303 // create a delete action. Installing this action in the input and action map
304 // didn't work. We therefore handle delete requests in processKeyBindings(...)
305 //
306 deleteAction = new DeleteAction();
307
308 // create the add action
309 //
310 addAction = new AddAction();
311 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
312 .put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, KeyEvent.CTRL_MASK), "addTag");
313 getActionMap().put("addTag", addAction);
314
315 // create the table cell editor and set it to key and value columns
316 //
317 editor = new TagCellEditor();
318 editor.setTagEditorModel((TagEditorModel)getModel());
319 getColumnModel().getColumn(0).setCellEditor(editor);
320 getColumnModel().getColumn(1).setCellEditor(editor);
321 }
322
323 /**
324 * constructor
325 *
326 * @param model
327 * @param columnModel
328 */
329 public TagTable(TableModel model) {
330 super(model, new TagTableColumnModel());
331 init();
332 }
333
334 @Override
335 public Dimension getPreferredSize(){
336 Container c = getParent();
337 while(c != null && ! (c instanceof JViewport)) {
338 c = c.getParent();
339 }
340 if (c != null) {
341 Dimension d = super.getPreferredSize();
342 d.width = c.getSize().width;
343 return d;
344 }
345 return super.getPreferredSize();
346 }
347
348 @Override protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
349 int condition, boolean pressed) {
350
351 // handle delete key
352 //
353 if (e.getKeyCode() == KeyEvent.VK_DELETE) {
354 getDeleteAction().run();
355 }
356 return super.processKeyBinding(ks, e, condition, pressed);
357 }
358
359 /**
360 * @param autoCompletionList
361 */
362 public void setAutoCompletionList(AutoCompletionList autoCompletionList) {
363 if (autoCompletionList == null)
364 return;
365 if (editor != null) {
366 editor.setAutoCompletionList(autoCompletionList);
367 }
368 }
369
370 public void setAutoCompletionCache(AutoCompletionCache acCache) {
371 if (acCache == null) {
372 logger.warning("argument acCache should not be null. Aborting.");
373 return;
374 }
375 if (editor != null) {
376 editor.setAutoCompletionCache(acCache);
377 }
378 }
379
380 public AutoCompletionList getAutoCompletionList() {
381 if (editor != null)
382 return editor.getAutoCompletionList();
383 else
384 return null;
385 }
386
387 public TagCellEditor getTableCellEditor() {
388 return editor;
389 }
390
391 public void addOKAccelatorListener(KeyListener l) {
392 addKeyListener(l);
393 if (editor == null) {
394 logger.warning("editor is null. cannot register OK accelator listener.");
395 } else {
396 editor.getEditor().addKeyListener(l);
397 }
398 }
399
400 public void requestFocusInCell(final int row, final int col) {
401
402 // the following code doesn't work reliably. If a table cell
403 // gains focus using editCellAt() and requestFocusInWindow()
404 // it isn't possible to tab to the next table cell using TAB or
405 // ENTER. Don't know why.
406 //
407 // tblTagEditor.editCellAt(row, col);
408 // if (tblTagEditor.getEditorComponent() != null) {
409 // tblTagEditor.getEditorComponent().requestFocusInWindow();
410 // }
411
412 // this is a workaround. We move the focus to the respective cell
413 // using a simulated mouse click. In this case one can tab out of
414 // the cell using TAB and ENTER.
415 //
416 Rectangle r = getCellRect(row,col, false);
417 Point p = new Point(r.x + r.width/2, r.y + r.height/2);
418 SwingUtilities.convertPointToScreen(p, this);
419 Point before = MouseInfo.getPointerInfo().getLocation();
420
421 try {
422 Robot robot = new Robot();
423 robot.mouseMove(p.x,p.y);
424 robot.mousePress(InputEvent.BUTTON1_MASK);
425 robot.mouseRelease(InputEvent.BUTTON1_MASK);
426 robot.mouseMove(before.x, before.y);
427 } catch(AWTException e) {
428 logger.log(Level.SEVERE, "failed to simulate mouse click event at (" + r.x + "," + r.y + "). Exception: " + e.toString());
429 return;
430 }
431 }
432}
Note: See TracBrowser for help on using the repository browser.