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