source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/relation/TagTable.java@ 1762

Last change on this file since 1762 was 1762, checked in by Gubaer, 15 years ago

added: improved tag editor grid in relation editor (borrowed code from tag editor plugin)
removed: realEqual() on OsmPrimitive, Node, etc.

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