[2512] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
| 2 | package org.openstreetmap.josm.gui.tagging;
|
---|
| 3 |
|
---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 5 |
|
---|
[2974] | 6 | import java.awt.Component;
|
---|
[2512] | 7 | import java.awt.Dimension;
|
---|
[2974] | 8 | import java.awt.KeyboardFocusManager;
|
---|
| 9 | import java.awt.Window;
|
---|
[2512] | 10 | import java.awt.event.ActionEvent;
|
---|
| 11 | import java.awt.event.KeyEvent;
|
---|
[2974] | 12 | import java.beans.PropertyChangeEvent;
|
---|
| 13 | import java.beans.PropertyChangeListener;
|
---|
[6092] | 14 | import java.util.Collections;
|
---|
[2974] | 15 | import java.util.EventObject;
|
---|
| 16 | import java.util.concurrent.CopyOnWriteArrayList;
|
---|
[2512] | 17 |
|
---|
| 18 | import javax.swing.AbstractAction;
|
---|
[2974] | 19 | import javax.swing.CellEditor;
|
---|
[2512] | 20 | import javax.swing.JComponent;
|
---|
| 21 | import javax.swing.JTable;
|
---|
| 22 | import javax.swing.KeyStroke;
|
---|
| 23 | import javax.swing.ListSelectionModel;
|
---|
| 24 | import javax.swing.SwingUtilities;
|
---|
| 25 | import javax.swing.event.ListSelectionEvent;
|
---|
| 26 | import javax.swing.event.ListSelectionListener;
|
---|
[6063] | 27 | import javax.swing.text.JTextComponent;
|
---|
[6248] | 28 |
|
---|
[6092] | 29 | import org.openstreetmap.josm.Main;
|
---|
| 30 | import org.openstreetmap.josm.data.osm.Relation;
|
---|
[10604] | 31 | import org.openstreetmap.josm.data.osm.TagMap;
|
---|
| 32 | import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
|
---|
| 33 | import org.openstreetmap.josm.gui.tagging.TagEditorModel.EndEditListener;
|
---|
[6248] | 34 | import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
|
---|
[3210] | 35 | import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager;
|
---|
[10070] | 36 | import org.openstreetmap.josm.gui.widgets.JosmTable;
|
---|
[2974] | 37 | import org.openstreetmap.josm.tools.ImageProvider;
|
---|
[2512] | 38 |
|
---|
| 39 | /**
|
---|
| 40 | * This is the tabular editor component for OSM tags.
|
---|
[9847] | 41 | * @since 1762
|
---|
[2512] | 42 | */
|
---|
[10604] | 43 | public class TagTable extends JosmTable implements EndEditListener {
|
---|
[2512] | 44 | /** the table cell editor used by this table */
|
---|
[8840] | 45 | private TagCellEditor editor;
|
---|
[6064] | 46 | private final TagEditorModel model;
|
---|
| 47 | private Component nextFocusComponent;
|
---|
[6070] | 48 |
|
---|
[4310] | 49 | /** a list of components to which focus can be transferred without stopping
|
---|
[2974] | 50 | * cell editing this table.
|
---|
| 51 | */
|
---|
[7005] | 52 | private final CopyOnWriteArrayList<Component> doNotStopCellEditingWhenFocused = new CopyOnWriteArrayList<>();
|
---|
[8308] | 53 | private transient CellEditorRemover editorRemover;
|
---|
[2974] | 54 |
|
---|
[2512] | 55 | /**
|
---|
| 56 | * Action to be run when the user navigates to the next cell in the table,
|
---|
| 57 | * for instance by pressing TAB or ENTER. The action alters the standard
|
---|
| 58 | * navigation path from cell to cell:
|
---|
| 59 | * <ul>
|
---|
| 60 | * <li>it jumps over cells in the first column</li>
|
---|
| 61 | * <li>it automatically add a new empty row when the user leaves the
|
---|
| 62 | * last cell in the table</li>
|
---|
[6524] | 63 | * </ul>
|
---|
[2512] | 64 | */
|
---|
[10378] | 65 | class SelectNextColumnCellAction extends AbstractAction {
|
---|
[6064] | 66 | @Override
|
---|
[2512] | 67 | public void actionPerformed(ActionEvent e) {
|
---|
| 68 | run();
|
---|
| 69 | }
|
---|
| 70 |
|
---|
| 71 | public void run() {
|
---|
| 72 | int col = getSelectedColumn();
|
---|
| 73 | int row = getSelectedRow();
|
---|
| 74 | if (getCellEditor() != null) {
|
---|
| 75 | getCellEditor().stopCellEditing();
|
---|
| 76 | }
|
---|
[6070] | 77 |
|
---|
[8510] | 78 | if (row == -1 && col == -1) {
|
---|
[6064] | 79 | requestFocusInCell(0, 0);
|
---|
| 80 | return;
|
---|
| 81 | }
|
---|
[6070] | 82 |
|
---|
[2512] | 83 | if (col == 0) {
|
---|
| 84 | col++;
|
---|
| 85 | } else if (col == 1 && row < getRowCount()-1) {
|
---|
[8510] | 86 | col = 0;
|
---|
[2512] | 87 | row++;
|
---|
[8510] | 88 | } else if (col == 1 && row == getRowCount()-1) {
|
---|
| 89 | // we are at the end. Append an empty row and move the focus to its second column
|
---|
| 90 | String key = ((TagModel) model.getValueAt(row, 0)).getName();
|
---|
[6064] | 91 | if (!key.trim().isEmpty()) {
|
---|
| 92 | model.appendNewTag();
|
---|
[8510] | 93 | col = 0;
|
---|
[6064] | 94 | row++;
|
---|
| 95 | } else {
|
---|
| 96 | clearSelection();
|
---|
[8510] | 97 | if (nextFocusComponent != null)
|
---|
[6064] | 98 | nextFocusComponent.requestFocusInWindow();
|
---|
| 99 | return;
|
---|
| 100 | }
|
---|
[2512] | 101 | }
|
---|
[8510] | 102 | requestFocusInCell(row, col);
|
---|
[2512] | 103 | }
|
---|
| 104 | }
|
---|
| 105 |
|
---|
| 106 | /**
|
---|
| 107 | * Action to be run when the user navigates to the previous cell in the table,
|
---|
| 108 | * for instance by pressing Shift-TAB
|
---|
| 109 | */
|
---|
[10378] | 110 | class SelectPreviousColumnCellAction extends AbstractAction {
|
---|
[2512] | 111 |
|
---|
[6063] | 112 | @Override
|
---|
[2512] | 113 | public void actionPerformed(ActionEvent e) {
|
---|
| 114 | int col = getSelectedColumn();
|
---|
| 115 | int row = getSelectedRow();
|
---|
| 116 | if (getCellEditor() != null) {
|
---|
| 117 | getCellEditor().stopCellEditing();
|
---|
| 118 | }
|
---|
| 119 |
|
---|
| 120 | if (col <= 0 && row <= 0) {
|
---|
| 121 | // change nothing
|
---|
| 122 | } else if (col == 1) {
|
---|
| 123 | col--;
|
---|
| 124 | } else {
|
---|
| 125 | col = 1;
|
---|
| 126 | row--;
|
---|
| 127 | }
|
---|
[8510] | 128 | requestFocusInCell(row, col);
|
---|
[2512] | 129 | }
|
---|
| 130 | }
|
---|
| 131 |
|
---|
| 132 | /**
|
---|
| 133 | * Action to be run when the user invokes a delete action on the table, for
|
---|
| 134 | * instance by pressing DEL.
|
---|
| 135 | *
|
---|
| 136 | * Depending on the shape on the current selection the action deletes individual
|
---|
| 137 | * values or entire tags from the model.
|
---|
| 138 | *
|
---|
| 139 | * If the current selection consists of cells in the second column only, the keys of
|
---|
| 140 | * the selected tags are set to the empty string.
|
---|
| 141 | *
|
---|
| 142 | * If the current selection consists of cell in the third column only, the values of the
|
---|
| 143 | * selected tags are set to the empty string.
|
---|
| 144 | *
|
---|
| 145 | * If the current selection consists of cells in the second and the third column,
|
---|
| 146 | * the selected tags are removed from the model.
|
---|
| 147 | *
|
---|
| 148 | * This action listens to the table selection. It becomes enabled when the selection
|
---|
| 149 | * is non-empty, otherwise it is disabled.
|
---|
| 150 | *
|
---|
| 151 | *
|
---|
| 152 | */
|
---|
[10082] | 153 | class DeleteAction extends AbstractAction implements ListSelectionListener {
|
---|
[2512] | 154 |
|
---|
[8836] | 155 | DeleteAction() {
|
---|
[10369] | 156 | new ImageProvider("dialogs", "delete").getResource().attachImageIcon(this);
|
---|
[2974] | 157 | putValue(SHORT_DESCRIPTION, tr("Delete the selection in the tag table"));
|
---|
| 158 | getSelectionModel().addListSelectionListener(this);
|
---|
| 159 | getColumnModel().getSelectionModel().addListSelectionListener(this);
|
---|
| 160 | updateEnabledState();
|
---|
| 161 | }
|
---|
| 162 |
|
---|
[2512] | 163 | /**
|
---|
| 164 | * delete a selection of tag names
|
---|
| 165 | */
|
---|
| 166 | protected void deleteTagNames() {
|
---|
| 167 | int[] rows = getSelectedRows();
|
---|
| 168 | model.deleteTagNames(rows);
|
---|
| 169 | }
|
---|
| 170 |
|
---|
| 171 | /**
|
---|
| 172 | * delete a selection of tag values
|
---|
| 173 | */
|
---|
| 174 | protected void deleteTagValues() {
|
---|
| 175 | int[] rows = getSelectedRows();
|
---|
| 176 | model.deleteTagValues(rows);
|
---|
| 177 | }
|
---|
| 178 |
|
---|
| 179 | /**
|
---|
| 180 | * delete a selection of tags
|
---|
| 181 | */
|
---|
| 182 | protected void deleteTags() {
|
---|
| 183 | int[] rows = getSelectedRows();
|
---|
| 184 | model.deleteTags(rows);
|
---|
| 185 | }
|
---|
| 186 |
|
---|
| 187 | @Override
|
---|
[10082] | 188 | public void actionPerformed(ActionEvent e) {
|
---|
[2512] | 189 | if (!isEnabled())
|
---|
| 190 | return;
|
---|
[2974] | 191 | switch(getSelectedColumnCount()) {
|
---|
| 192 | case 1:
|
---|
[3068] | 193 | if (getSelectedColumn() == 0) {
|
---|
[2512] | 194 | deleteTagNames();
|
---|
| 195 | } else if (getSelectedColumn() == 1) {
|
---|
| 196 | deleteTagValues();
|
---|
[3068] | 197 | }
|
---|
[2974] | 198 | break;
|
---|
| 199 | case 2:
|
---|
[2512] | 200 | deleteTags();
|
---|
[2974] | 201 | break;
|
---|
[10217] | 202 | default: // Do nothing
|
---|
[2512] | 203 | }
|
---|
[2974] | 204 |
|
---|
[10604] | 205 | endCellEditing();
|
---|
[2974] | 206 |
|
---|
[2512] | 207 | if (model.getRowCount() == 0) {
|
---|
| 208 | model.ensureOneTag();
|
---|
| 209 | requestFocusInCell(0, 0);
|
---|
| 210 | }
|
---|
| 211 | }
|
---|
| 212 |
|
---|
| 213 | /**
|
---|
| 214 | * listens to the table selection model
|
---|
| 215 | */
|
---|
[6063] | 216 | @Override
|
---|
[2512] | 217 | public void valueChanged(ListSelectionEvent e) {
|
---|
[2974] | 218 | updateEnabledState();
|
---|
| 219 | }
|
---|
| 220 |
|
---|
[6890] | 221 | protected final void updateEnabledState() {
|
---|
[10604] | 222 | if (getSelectedColumnCount() >= 1 && getSelectedRowCount() >= 1) {
|
---|
[2997] | 223 | setEnabled(true);
|
---|
[2512] | 224 | } else {
|
---|
| 225 | setEnabled(false);
|
---|
| 226 | }
|
---|
| 227 | }
|
---|
| 228 | }
|
---|
| 229 |
|
---|
| 230 | /**
|
---|
| 231 | * Action to be run when the user adds a new tag.
|
---|
| 232 | *
|
---|
| 233 | */
|
---|
[10082] | 234 | class AddAction extends AbstractAction implements PropertyChangeListener {
|
---|
[8836] | 235 | AddAction() {
|
---|
[10369] | 236 | new ImageProvider("dialogs", "add").getResource().attachImageIcon(this);
|
---|
[2974] | 237 | putValue(SHORT_DESCRIPTION, tr("Add a new tag"));
|
---|
| 238 | TagTable.this.addPropertyChangeListener(this);
|
---|
| 239 | updateEnabledState();
|
---|
[2512] | 240 | }
|
---|
| 241 |
|
---|
| 242 | @Override
|
---|
[10082] | 243 | public void actionPerformed(ActionEvent e) {
|
---|
[10217] | 244 | CellEditor cEditor = getCellEditor();
|
---|
| 245 | if (cEditor != null) {
|
---|
| 246 | cEditor.stopCellEditing();
|
---|
[2974] | 247 | }
|
---|
[6064] | 248 | final int rowIdx = model.getRowCount()-1;
|
---|
[9319] | 249 | if (rowIdx < 0 || !((TagModel) model.getValueAt(rowIdx, 0)).getName().trim().isEmpty()) {
|
---|
[6064] | 250 | model.appendNewTag();
|
---|
[6070] | 251 | }
|
---|
[6064] | 252 | requestFocusInCell(model.getRowCount()-1, 0);
|
---|
[2512] | 253 | }
|
---|
[6070] | 254 |
|
---|
[6890] | 255 | protected final void updateEnabledState() {
|
---|
[2974] | 256 | setEnabled(TagTable.this.isEnabled());
|
---|
| 257 | }
|
---|
| 258 |
|
---|
[6063] | 259 | @Override
|
---|
[2974] | 260 | public void propertyChange(PropertyChangeEvent evt) {
|
---|
| 261 | updateEnabledState();
|
---|
| 262 | }
|
---|
[2512] | 263 | }
|
---|
| 264 |
|
---|
[9059] | 265 | /**
|
---|
[6092] | 266 | * Action to be run when the user wants to paste tags from buffer
|
---|
| 267 | */
|
---|
[10082] | 268 | class PasteAction extends AbstractAction implements PropertyChangeListener {
|
---|
[8836] | 269 | PasteAction() {
|
---|
[10369] | 270 | new ImageProvider("pastetags").getResource().attachImageIcon(this);
|
---|
[6092] | 271 | putValue(SHORT_DESCRIPTION, tr("Paste tags from buffer"));
|
---|
| 272 | TagTable.this.addPropertyChangeListener(this);
|
---|
| 273 | updateEnabledState();
|
---|
| 274 | }
|
---|
| 275 |
|
---|
| 276 | @Override
|
---|
[10082] | 277 | public void actionPerformed(ActionEvent e) {
|
---|
[6092] | 278 | Relation relation = new Relation();
|
---|
| 279 | model.applyToPrimitive(relation);
|
---|
[10604] | 280 | new OsmTransferHandler().pasteTags(Collections.singleton(relation));
|
---|
| 281 | model.updateTags(new TagMap(relation.getKeys()).getTags());
|
---|
[6092] | 282 | }
|
---|
[7509] | 283 |
|
---|
[6890] | 284 | protected final void updateEnabledState() {
|
---|
[6092] | 285 | setEnabled(TagTable.this.isEnabled());
|
---|
| 286 | }
|
---|
| 287 |
|
---|
| 288 | @Override
|
---|
| 289 | public void propertyChange(PropertyChangeEvent evt) {
|
---|
| 290 | updateEnabledState();
|
---|
| 291 | }
|
---|
| 292 | }
|
---|
[7509] | 293 |
|
---|
[2512] | 294 | /** the delete action */
|
---|
[10082] | 295 | private DeleteAction deleteAction;
|
---|
[2512] | 296 |
|
---|
| 297 | /** the add action */
|
---|
[10082] | 298 | private AddAction addAction;
|
---|
[2512] | 299 |
|
---|
[6092] | 300 | /** the tag paste action */
|
---|
[10082] | 301 | private PasteAction pasteAction;
|
---|
[6092] | 302 |
|
---|
[2512] | 303 | /**
|
---|
[10082] | 304 | * Returns the delete action.
|
---|
[2512] | 305 | * @return the delete action used by this table
|
---|
| 306 | */
|
---|
[10082] | 307 | public DeleteAction getDeleteAction() {
|
---|
[2512] | 308 | return deleteAction;
|
---|
| 309 | }
|
---|
| 310 |
|
---|
[10082] | 311 | /**
|
---|
| 312 | * Returns the add action.
|
---|
| 313 | * @return the add action used by this table
|
---|
| 314 | */
|
---|
| 315 | public AddAction getAddAction() {
|
---|
[2512] | 316 | return addAction;
|
---|
| 317 | }
|
---|
| 318 |
|
---|
[10082] | 319 | /**
|
---|
| 320 | * Returns the paste action.
|
---|
| 321 | * @return the paste action used by this table
|
---|
| 322 | */
|
---|
| 323 | public PasteAction getPasteAction() {
|
---|
[6092] | 324 | return pasteAction;
|
---|
| 325 | }
|
---|
| 326 |
|
---|
[2512] | 327 | /**
|
---|
| 328 | * initialize the table
|
---|
[8760] | 329 | * @param maxCharacters maximum number of characters allowed for keys and values, 0 for unlimited
|
---|
[2512] | 330 | */
|
---|
[8760] | 331 | protected final void init(final int maxCharacters) {
|
---|
[2512] | 332 | setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
---|
[2974] | 333 | setRowSelectionAllowed(true);
|
---|
| 334 | setColumnSelectionAllowed(true);
|
---|
[2512] | 335 | setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
|
---|
| 336 |
|
---|
| 337 | // make ENTER behave like TAB
|
---|
| 338 | //
|
---|
| 339 | getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
|
---|
| 340 | .put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "selectNextColumnCell");
|
---|
| 341 |
|
---|
| 342 | // install custom navigation actions
|
---|
| 343 | //
|
---|
| 344 | getActionMap().put("selectNextColumnCell", new SelectNextColumnCellAction());
|
---|
| 345 | getActionMap().put("selectPreviousColumnCell", new SelectPreviousColumnCellAction());
|
---|
| 346 |
|
---|
| 347 | // create a delete action. Installing this action in the input and action map
|
---|
| 348 | // didn't work. We therefore handle delete requests in processKeyBindings(...)
|
---|
| 349 | //
|
---|
| 350 | deleteAction = new DeleteAction();
|
---|
| 351 |
|
---|
| 352 | // create the add action
|
---|
| 353 | //
|
---|
| 354 | addAction = new AddAction();
|
---|
| 355 | getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
|
---|
| 356 | .put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, KeyEvent.CTRL_MASK), "addTag");
|
---|
| 357 | getActionMap().put("addTag", addAction);
|
---|
| 358 |
|
---|
[6092] | 359 | pasteAction = new PasteAction();
|
---|
| 360 |
|
---|
[2512] | 361 | // create the table cell editor and set it to key and value columns
|
---|
| 362 | //
|
---|
[8760] | 363 | TagCellEditor tmpEditor = new TagCellEditor(maxCharacters);
|
---|
[4120] | 364 | setRowHeight(tmpEditor.getEditor().getPreferredSize().height);
|
---|
| 365 | setTagCellEditor(tmpEditor);
|
---|
[2512] | 366 | }
|
---|
| 367 |
|
---|
| 368 | /**
|
---|
[3141] | 369 | * Creates a new tag table
|
---|
[2512] | 370 | *
|
---|
[3141] | 371 | * @param model the tag editor model
|
---|
[8760] | 372 | * @param maxCharacters maximum number of characters allowed for keys and values, 0 for unlimited
|
---|
[2512] | 373 | */
|
---|
[8760] | 374 | public TagTable(TagEditorModel model, final int maxCharacters) {
|
---|
[9847] | 375 | super(model, new TagTableColumnModelBuilder(new TagCellRenderer(), tr("Key"), tr("Value"))
|
---|
| 376 | .setSelectionModel(model.getColumnSelectionModel()).build(),
|
---|
| 377 | model.getRowSelectionModel());
|
---|
[6064] | 378 | this.model = model;
|
---|
[10604] | 379 | model.setEndEditListener(this);
|
---|
[8760] | 380 | init(maxCharacters);
|
---|
[2512] | 381 | }
|
---|
| 382 |
|
---|
| 383 | @Override
|
---|
[8510] | 384 | public Dimension getPreferredSize() {
|
---|
[10070] | 385 | return getPreferredFullWidthSize();
|
---|
[2512] | 386 | }
|
---|
| 387 |
|
---|
[10070] | 388 | @Override
|
---|
| 389 | protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
|
---|
[2512] | 390 |
|
---|
| 391 | // handle delete key
|
---|
| 392 | //
|
---|
| 393 | if (e.getKeyCode() == KeyEvent.VK_DELETE) {
|
---|
[3068] | 394 | if (isEditing() && getSelectedColumnCount() == 1 && getSelectedRowCount() == 1)
|
---|
| 395 | // if DEL was pressed and only the currently edited cell is selected,
|
---|
| 396 | // don't run the delete action. DEL is handled by the CellEditor as normal
|
---|
| 397 | // DEL in the text input.
|
---|
| 398 | //
|
---|
| 399 | return super.processKeyBinding(ks, e, condition, pressed);
|
---|
[10082] | 400 | getDeleteAction().actionPerformed(null);
|
---|
[2512] | 401 | }
|
---|
| 402 | return super.processKeyBinding(ks, e, condition, pressed);
|
---|
| 403 | }
|
---|
| 404 |
|
---|
| 405 | /**
|
---|
[8470] | 406 | * Sets the editor autocompletion list
|
---|
| 407 | * @param autoCompletionList autocompletion list
|
---|
[2512] | 408 | */
|
---|
| 409 | public void setAutoCompletionList(AutoCompletionList autoCompletionList) {
|
---|
| 410 | if (autoCompletionList == null)
|
---|
| 411 | return;
|
---|
| 412 | if (editor != null) {
|
---|
| 413 | editor.setAutoCompletionList(autoCompletionList);
|
---|
| 414 | }
|
---|
| 415 | }
|
---|
| 416 |
|
---|
[3210] | 417 | public void setAutoCompletionManager(AutoCompletionManager autocomplete) {
|
---|
| 418 | if (autocomplete == null) {
|
---|
[6248] | 419 | Main.warn("argument autocomplete should not be null. Aborting.");
|
---|
[3214] | 420 | Thread.dumpStack();
|
---|
[2512] | 421 | return;
|
---|
| 422 | }
|
---|
| 423 | if (editor != null) {
|
---|
[3210] | 424 | editor.setAutoCompletionManager(autocomplete);
|
---|
[2512] | 425 | }
|
---|
| 426 | }
|
---|
| 427 |
|
---|
| 428 | public AutoCompletionList getAutoCompletionList() {
|
---|
| 429 | if (editor != null)
|
---|
| 430 | return editor.getAutoCompletionList();
|
---|
| 431 | else
|
---|
| 432 | return null;
|
---|
| 433 | }
|
---|
| 434 |
|
---|
[9231] | 435 | /**
|
---|
| 436 | * Sets the next component to request focus after navigation (with tab or enter).
|
---|
| 437 | * @param nextFocusComponent next component to request focus after navigation (with tab or enter)
|
---|
| 438 | */
|
---|
[6064] | 439 | public void setNextFocusComponent(Component nextFocusComponent) {
|
---|
| 440 | this.nextFocusComponent = nextFocusComponent;
|
---|
| 441 | }
|
---|
[6070] | 442 |
|
---|
[2512] | 443 | public TagCellEditor getTableCellEditor() {
|
---|
| 444 | return editor;
|
---|
| 445 | }
|
---|
| 446 |
|
---|
[3015] | 447 | /**
|
---|
| 448 | * Inject a tag cell editor in the tag table
|
---|
[3530] | 449 | *
|
---|
[8470] | 450 | * @param editor tag cell editor
|
---|
[3015] | 451 | */
|
---|
| 452 | public void setTagCellEditor(TagCellEditor editor) {
|
---|
[10604] | 453 | endCellEditing();
|
---|
[3015] | 454 | this.editor = editor;
|
---|
| 455 | getColumnModel().getColumn(0).setCellEditor(editor);
|
---|
| 456 | getColumnModel().getColumn(1).setCellEditor(editor);
|
---|
| 457 | }
|
---|
| 458 |
|
---|
[2512] | 459 | public void requestFocusInCell(final int row, final int col) {
|
---|
[6064] | 460 | changeSelection(row, col, false, false);
|
---|
[6063] | 461 | editCellAt(row, col);
|
---|
| 462 | Component c = getEditorComponent();
|
---|
[8510] | 463 | if (c != null) {
|
---|
[6063] | 464 | c.requestFocusInWindow();
|
---|
[8444] | 465 | if (c instanceof JTextComponent) {
|
---|
[8510] | 466 | ((JTextComponent) c).selectAll();
|
---|
[6063] | 467 | }
|
---|
[2512] | 468 | }
|
---|
[6063] | 469 | // there was a bug here - on older 1.6 Java versions Tab was not working
|
---|
[6070] | 470 | // after such activation. In 1.7 it works OK,
|
---|
[8444] | 471 | // previous solution of using awt.Robot was resetting mouse speed on Windows
|
---|
[2512] | 472 | }
|
---|
[2974] | 473 |
|
---|
| 474 | public void addComponentNotStoppingCellEditing(Component component) {
|
---|
| 475 | if (component == null) return;
|
---|
| 476 | doNotStopCellEditingWhenFocused.addIfAbsent(component);
|
---|
| 477 | }
|
---|
| 478 |
|
---|
| 479 | public void removeComponentNotStoppingCellEditing(Component component) {
|
---|
| 480 | if (component == null) return;
|
---|
| 481 | doNotStopCellEditingWhenFocused.remove(component);
|
---|
| 482 | }
|
---|
| 483 |
|
---|
| 484 | @Override
|
---|
[8444] | 485 | public boolean editCellAt(int row, int column, EventObject e) {
|
---|
[2974] | 486 |
|
---|
| 487 | // a snipped copied from the Java 1.5 implementation of JTable
|
---|
| 488 | //
|
---|
| 489 | if (cellEditor != null && !cellEditor.stopCellEditing())
|
---|
| 490 | return false;
|
---|
| 491 |
|
---|
| 492 | if (row < 0 || row >= getRowCount() ||
|
---|
| 493 | column < 0 || column >= getColumnCount())
|
---|
| 494 | return false;
|
---|
| 495 |
|
---|
| 496 | if (!isCellEditable(row, column))
|
---|
| 497 | return false;
|
---|
| 498 |
|
---|
| 499 | // make sure our custom implementation of CellEditorRemover is created
|
---|
| 500 | if (editorRemover == null) {
|
---|
| 501 | KeyboardFocusManager fm =
|
---|
| 502 | KeyboardFocusManager.getCurrentKeyboardFocusManager();
|
---|
| 503 | editorRemover = new CellEditorRemover(fm);
|
---|
| 504 | fm.addPropertyChangeListener("permanentFocusOwner", editorRemover);
|
---|
| 505 | }
|
---|
| 506 |
|
---|
| 507 | // delegate to the default implementation
|
---|
[8510] | 508 | return super.editCellAt(row, column, e);
|
---|
[2974] | 509 | }
|
---|
| 510 |
|
---|
| 511 | @Override
|
---|
[10604] | 512 | public void endCellEditing() {
|
---|
| 513 | if (isEditing()) {
|
---|
| 514 | CellEditor cEditor = getCellEditor();
|
---|
| 515 | if (cEditor != null) {
|
---|
| 516 | // First attempt to commit. If this does not work, cancel.
|
---|
| 517 | cEditor.stopCellEditing();
|
---|
| 518 | cEditor.cancelCellEditing();
|
---|
| 519 | }
|
---|
| 520 | }
|
---|
| 521 | }
|
---|
| 522 |
|
---|
| 523 | @Override
|
---|
[2974] | 524 | public void removeEditor() {
|
---|
| 525 | // make sure we unregister our custom implementation of CellEditorRemover
|
---|
| 526 | KeyboardFocusManager.getCurrentKeyboardFocusManager().
|
---|
| 527 | removePropertyChangeListener("permanentFocusOwner", editorRemover);
|
---|
| 528 | editorRemover = null;
|
---|
| 529 | super.removeEditor();
|
---|
| 530 | }
|
---|
| 531 |
|
---|
| 532 | @Override
|
---|
| 533 | public void removeNotify() {
|
---|
| 534 | // make sure we unregister our custom implementation of CellEditorRemover
|
---|
| 535 | KeyboardFocusManager.getCurrentKeyboardFocusManager().
|
---|
| 536 | removePropertyChangeListener("permanentFocusOwner", editorRemover);
|
---|
| 537 | editorRemover = null;
|
---|
| 538 | super.removeNotify();
|
---|
| 539 | }
|
---|
| 540 |
|
---|
| 541 | /**
|
---|
| 542 | * This is a custom implementation of the CellEditorRemover used in JTable
|
---|
| 543 | * to handle the client property <tt>terminateEditOnFocusLost</tt>.
|
---|
[3530] | 544 | *
|
---|
[2974] | 545 | * This implementation also checks whether focus is transferred to one of a list
|
---|
[5266] | 546 | * of dedicated components, see {@link TagTable#doNotStopCellEditingWhenFocused}.
|
---|
| 547 | * A typical example for such a component is a button in {@link TagEditorPanel}
|
---|
| 548 | * which isn't a child component of {@link TagTable} but which should respond to
|
---|
[2974] | 549 | * to focus transfer in a similar way to a child of TagTable.
|
---|
| 550 | *
|
---|
| 551 | */
|
---|
| 552 | class CellEditorRemover implements PropertyChangeListener {
|
---|
[9078] | 553 | private final KeyboardFocusManager focusManager;
|
---|
[2974] | 554 |
|
---|
[8836] | 555 | CellEditorRemover(KeyboardFocusManager fm) {
|
---|
[2974] | 556 | this.focusManager = fm;
|
---|
| 557 | }
|
---|
| 558 |
|
---|
[6063] | 559 | @Override
|
---|
[2974] | 560 | public void propertyChange(PropertyChangeEvent ev) {
|
---|
| 561 | if (!isEditing())
|
---|
| 562 | return;
|
---|
| 563 |
|
---|
| 564 | Component c = focusManager.getPermanentFocusOwner();
|
---|
| 565 | while (c != null) {
|
---|
| 566 | if (c == TagTable.this)
|
---|
| 567 | // focus remains inside the table
|
---|
| 568 | return;
|
---|
| 569 | if (doNotStopCellEditingWhenFocused.contains(c))
|
---|
| 570 | // focus remains on one of the associated components
|
---|
| 571 | return;
|
---|
[7026] | 572 | else if (c instanceof Window) {
|
---|
[10217] | 573 | if (c == SwingUtilities.getRoot(TagTable.this) && !getCellEditor().stopCellEditing()) {
|
---|
| 574 | getCellEditor().cancelCellEditing();
|
---|
[2974] | 575 | }
|
---|
| 576 | break;
|
---|
| 577 | }
|
---|
| 578 | c = c.getParent();
|
---|
| 579 | }
|
---|
| 580 | }
|
---|
| 581 | }
|
---|
[2512] | 582 | }
|
---|