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

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

improved upload dialog
new: tags for changesets
new: multiple uploads to the same changeset
fixed #3381: simple imput of a changeset source
fixed #2491: Allow arbitrary key-value pairs in changesets
fixed #2436: Allow multiple uploads to one changeset

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