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