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        // add action
078        //
079        JButton btn;
080        pnl.add(btn = new JButton(tagTable.getAddAction()));
081        btn.setMargin(new Insets(0, 0, 0, 0));
082        tagTable.addComponentNotStoppingCellEditing(btn);
083
084        // delete action
085        pnl.add(btn = new JButton(tagTable.getDeleteAction()));
086        btn.setMargin(new Insets(0, 0, 0, 0));
087        tagTable.addComponentNotStoppingCellEditing(btn);
088
089        // paste action
090        pnl.add(btn = new JButton(tagTable.getPasteAction()));
091        btn.setMargin(new Insets(0, 0, 0, 0));
092        tagTable.addComponentNotStoppingCellEditing(btn);
093        return pnl;
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(new TableModelListener() {
133                @Override
134                public void tableChanged(TableModelEvent e) {
135                    updatePresets();
136                }
137            });
138        }
139
140        addFocusListener(new FocusAdapter() {
141            @Override
142            public void focusGained(FocusEvent e) {
143                tagTable.requestFocusInCell(0, 0);
144            }
145        });
146    }
147
148    /**
149     * Creates a new tag editor panel. The editor model is created
150     * internally and can be retrieved with {@link #getModel()}.
151     * @param primitive primitive to consider
152     * @param presetHandler tagging preset handler
153     */
154    public TagEditorPanel(OsmPrimitive primitive, TaggingPresetHandler presetHandler) {
155        this(new TagEditorModel().forPrimitive(primitive), presetHandler, 0);
156    }
157
158    /**
159     * Creates a new tag editor panel with a supplied model. If {@code model} is null, a new model is created.
160     *
161     * @param model the tag editor model
162     * @param presetHandler tagging preset handler
163     * @param maxCharacters maximum number of characters allowed, 0 for unlimited
164     */
165    public TagEditorPanel(TagEditorModel model, TaggingPresetHandler presetHandler, final int maxCharacters) {
166        this.model = model;
167        this.presetHandler = presetHandler;
168        if (this.model == null) {
169            this.model = new TagEditorModel();
170        }
171        this.tagTable = new TagTable(this.model, maxCharacters);
172        build();
173    }
174
175    /**
176     * Replies the tag editor model used by this panel.
177     *
178     * @return the tag editor model used by this panel
179     */
180    public TagEditorModel getModel() {
181        return model;
182    }
183
184    /**
185     * Initializes the auto completion infrastructure used in this
186     * tag editor panel. {@code layer} is the data layer from whose data set
187     * tag values are proposed as auto completion items.
188     *
189     * @param layer the data layer. Must not be null.
190     * @throws IllegalArgumentException if {@code layer} is null
191     */
192    public void initAutoCompletion(OsmDataLayer layer) {
193        CheckParameterUtil.ensureParameterNotNull(layer, "layer");
194
195        AutoCompletionManager autocomplete = layer.data.getAutoCompletionManager();
196        AutoCompletionList acList = new AutoCompletionList();
197
198        TagCellEditor editor = (TagCellEditor) tagTable.getColumnModel().getColumn(0).getCellEditor();
199        editor.setAutoCompletionManager(autocomplete);
200        editor.setAutoCompletionList(acList);
201        editor = (TagCellEditor) tagTable.getColumnModel().getColumn(1).getCellEditor();
202        editor.setAutoCompletionManager(autocomplete);
203        editor.setAutoCompletionList(acList);
204    }
205
206    @Override
207    public void setEnabled(boolean enabled) {
208        tagTable.setEnabled(enabled);
209        super.setEnabled(enabled);
210    }
211
212    private void updatePresets() {
213        presetListPanel.updatePresets(
214                model.getTaggingPresetTypes(),
215                model.getTags(), presetHandler);
216        validate();
217    }
218}