001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.tagging; 003 004import java.awt.BorderLayout; 005import java.awt.Component; 006import java.awt.GridBagConstraints; 007import java.awt.GridBagLayout; 008import java.awt.Insets; 009import java.awt.event.FocusAdapter; 010import java.awt.event.FocusEvent; 011import java.util.Collections; 012 013import javax.swing.AbstractAction; 014import javax.swing.BoxLayout; 015import javax.swing.JButton; 016import javax.swing.JComponent; 017import javax.swing.JPanel; 018import javax.swing.JScrollPane; 019 020import org.openstreetmap.josm.data.osm.OsmPrimitive; 021import org.openstreetmap.josm.gui.dialogs.properties.HelpAction; 022import org.openstreetmap.josm.gui.dialogs.properties.HelpTagAction; 023import org.openstreetmap.josm.gui.dialogs.properties.PresetListPanel; 024import org.openstreetmap.josm.gui.layer.OsmDataLayer; 025import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList; 026import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionManager; 027import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetHandler; 028import org.openstreetmap.josm.spi.preferences.Config; 029import org.openstreetmap.josm.tools.CheckParameterUtil; 030 031/** 032 * TagEditorPanel is a {@link JPanel} which can be embedded as UI component in 033 * UIs. It provides a spreadsheet like tabular control for editing tag names 034 * and tag values. Two action buttons are placed on the left, one for adding 035 * a new tag and one for deleting the currently selected tags. 036 * @since 2040 037 */ 038public class TagEditorPanel extends JPanel { 039 /** the tag editor model */ 040 private TagEditorModel model; 041 /** the tag table */ 042 private final TagTable tagTable; 043 044 private PresetListPanel presetListPanel; 045 private final transient TaggingPresetHandler presetHandler; 046 047 /** 048 * builds the panel with the table for editing tags 049 * 050 * @return the panel 051 */ 052 protected JPanel buildTagTableEditorPanel() { 053 JPanel pnl = new JPanel(new BorderLayout()); 054 pnl.add(new JScrollPane(tagTable), BorderLayout.CENTER); 055 if (presetHandler != null) { 056 presetListPanel = new PresetListPanel(); 057 if (Config.getPref().getBoolean("relation.editor.presets.visible", true)) { 058 pnl.add(presetListPanel, BorderLayout.NORTH); 059 } 060 } 061 return pnl; 062 } 063 064 /** 065 * Sets the next component to request focus after navigation (with tab or enter). 066 * @param nextFocusComponent next component to request focus after navigation (with tab or enter) 067 * @see TagTable#setNextFocusComponent 068 */ 069 public void setNextFocusComponent(Component nextFocusComponent) { 070 tagTable.setNextFocusComponent(nextFocusComponent); 071 } 072 073 /** 074 * builds the panel with the button row 075 * 076 * @return the panel 077 */ 078 protected JPanel buildButtonsPanel() { 079 JPanel pnl = new JPanel(); 080 pnl.setLayout(new BoxLayout(pnl, BoxLayout.Y_AXIS)); 081 082 buildButton(pnl, tagTable.getAddAction()); 083 buildButton(pnl, tagTable.getDeleteAction()); 084 buildButton(pnl, tagTable.getPasteAction()); 085 086 return pnl; 087 } 088 089 private void buildButton(JPanel pnl, AbstractAction action) { 090 JButton btn = new JButton(action); 091 pnl.add(btn); 092 btn.setMargin(new Insets(0, 0, 0, 0)); 093 tagTable.addComponentNotStoppingCellEditing(btn); 094 } 095 096 /** 097 * Returns the paste action. 098 * @return the paste action 099 */ 100 public AbstractAction getPasteAction() { 101 return tagTable.getPasteAction(); 102 } 103 104 /** 105 * builds the GUI 106 */ 107 protected final void build() { 108 setLayout(new GridBagLayout()); 109 JPanel tablePanel = buildTagTableEditorPanel(); 110 JPanel buttonPanel = buildButtonsPanel(); 111 112 GridBagConstraints gc = new GridBagConstraints(); 113 114 // -- buttons panel 115 // 116 gc.fill = GridBagConstraints.VERTICAL; 117 gc.weightx = 0.0; 118 gc.weighty = 1.0; 119 gc.anchor = GridBagConstraints.NORTHWEST; 120 add(buttonPanel, gc); 121 122 // -- the panel with the editor table 123 // 124 gc.gridx = 1; 125 gc.fill = GridBagConstraints.BOTH; 126 gc.weightx = 1.0; 127 gc.weighty = 1.0; 128 gc.anchor = GridBagConstraints.CENTER; 129 add(tablePanel, gc); 130 131 if (presetHandler != null) { 132 model.addTableModelListener(e -> updatePresets()); 133 } 134 135 addFocusListener(new FocusAdapter() { 136 @Override 137 public void focusGained(FocusEvent e) { 138 tagTable.requestFocusInCell(0, 0); 139 } 140 }); 141 } 142 143 /** 144 * Creates a new tag editor panel. The editor model is created 145 * internally and can be retrieved with {@link #getModel()}. 146 * @param primitive primitive to consider 147 * @param presetHandler tagging preset handler 148 */ 149 public TagEditorPanel(OsmPrimitive primitive, TaggingPresetHandler presetHandler) { 150 this(new TagEditorModel().forPrimitive(primitive), presetHandler, 0); 151 } 152 153 /** 154 * Creates a new tag editor panel with a supplied model. If {@code model} is null, a new model is created. 155 * 156 * @param model the tag editor model 157 * @param presetHandler tagging preset handler 158 * @param maxCharacters maximum number of characters allowed, 0 for unlimited 159 */ 160 public TagEditorPanel(TagEditorModel model, TaggingPresetHandler presetHandler, final int maxCharacters) { 161 this.model = model; 162 this.presetHandler = presetHandler; 163 if (this.model == null) { 164 this.model = new TagEditorModel(); 165 } 166 this.tagTable = new TagTable(this.model, maxCharacters); 167 168 setupKeyboardShortcuts(); 169 build(); 170 } 171 172 private void setupKeyboardShortcuts() { 173 // F1 button = custom help action 174 final HelpAction helpTagAction = new HelpTagAction(tagTable, 175 viewRow -> this.model.get(tagTable.convertRowIndexToModel(viewRow)).getName(), 176 viewRow -> Collections.singletonMap(this.model.get(tagTable.convertRowIndexToModel(viewRow)).getValue(), 1)); 177 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(HelpAction.getKeyStroke(), "onHelp"); 178 getActionMap().put("onHelp", helpTagAction); 179 } 180 181 /** 182 * Replies the tag editor model used by this panel. 183 * 184 * @return the tag editor model used by this panel 185 */ 186 public TagEditorModel getModel() { 187 return model; 188 } 189 190 /** 191 * Initializes the auto completion infrastructure used in this 192 * tag editor panel. {@code layer} is the data layer from whose data set 193 * tag values are proposed as auto completion items. 194 * 195 * @param layer the data layer. Must not be null. 196 * @throws IllegalArgumentException if {@code layer} is null 197 */ 198 public void initAutoCompletion(OsmDataLayer layer) { 199 CheckParameterUtil.ensureParameterNotNull(layer, "layer"); 200 201 AutoCompletionManager autocomplete = AutoCompletionManager.of(layer.data); 202 AutoCompletionList acList = new AutoCompletionList(); 203 204 TagCellEditor editor = (TagCellEditor) tagTable.getColumnModel().getColumn(0).getCellEditor(); 205 editor.setAutoCompletionManager(autocomplete); 206 editor.setAutoCompletionList(acList); 207 editor = (TagCellEditor) tagTable.getColumnModel().getColumn(1).getCellEditor(); 208 editor.setAutoCompletionManager(autocomplete); 209 editor.setAutoCompletionList(acList); 210 } 211 212 @Override 213 public void setEnabled(boolean enabled) { 214 tagTable.setEnabled(enabled); 215 super.setEnabled(enabled); 216 } 217 218 private void updatePresets() { 219 presetListPanel.updatePresets( 220 model.getTaggingPresetTypes(), 221 model.getTags(), presetHandler); 222 validate(); 223 } 224 225 /** 226 * Save all outstanding edits to the model. 227 * @see org.openstreetmap.josm.gui.io.UploadDialog#saveEdits 228 * @since 18173 229 */ 230 public void saveEdits() { 231 tagTable.endCellEditing(); 232 } 233}