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.EnumSet;
012
013import javax.swing.AbstractAction;
014import javax.swing.BoxLayout;
015import javax.swing.JButton;
016import javax.swing.JPanel;
017import javax.swing.JScrollPane;
018import javax.swing.event.TableModelEvent;
019import javax.swing.event.TableModelListener;
020
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.gui.tagging.presets.TaggingPresetType;
027import org.openstreetmap.josm.tools.CheckParameterUtil;
028
029/**
030 * TagEditorPanel is a {@link JPanel} which can be embedded as UI component in
031 * UIs. It provides a spreadsheet like tabular control for editing tag names
032 * and tag values. Two action buttons are placed on the left, one for adding
033 * a new tag and one for deleting the currently selected tags.
034 *
035 */
036public class TagEditorPanel extends JPanel {
037    /** the tag editor model */
038    private TagEditorModel model;
039    /** the tag table */
040    private final TagTable tagTable;
041
042    private PresetListPanel presetListPanel;
043    private final transient TaggingPresetHandler presetHandler;
044
045    /**
046     * builds the panel with the table for editing tags
047     *
048     * @return the panel
049     */
050    protected JPanel buildTagTableEditorPanel() {
051        JPanel pnl = new JPanel();
052        pnl.setLayout(new BorderLayout());
053        pnl.add(new JScrollPane(tagTable), BorderLayout.CENTER);
054        if (presetHandler != null) {
055            presetListPanel = new PresetListPanel();
056            pnl.add(presetListPanel, BorderLayout.NORTH);
057        }
058        return pnl;
059    }
060
061    /**
062     * Sets the next component to request focus after navigation (with tab or enter).
063     * @param nextFocusComponent next component to request focus after navigation (with tab or enter)
064     * @see TagTable#setNextFocusComponent
065     */
066    public void setNextFocusComponent(Component nextFocusComponent) {
067        tagTable.setNextFocusComponent(nextFocusComponent);
068    }
069
070    /**
071     * builds the panel with the button row
072     *
073     * @return the panel
074     */
075    protected JPanel buildButtonsPanel() {
076        JPanel pnl = new JPanel();
077        pnl.setLayout(new BoxLayout(pnl, BoxLayout.Y_AXIS));
078
079        // add action
080        //
081        JButton btn;
082        pnl.add(btn = new JButton(tagTable.getAddAction()));
083        btn.setMargin(new Insets(0, 0, 0, 0));
084        tagTable.addComponentNotStoppingCellEditing(btn);
085
086        // delete action
087        pnl.add(btn = new JButton(tagTable.getDeleteAction()));
088        btn.setMargin(new Insets(0, 0, 0, 0));
089        tagTable.addComponentNotStoppingCellEditing(btn);
090
091        // paste action
092        pnl.add(btn = new JButton(tagTable.getPasteAction()));
093        btn.setMargin(new Insets(0, 0, 0, 0));
094        tagTable.addComponentNotStoppingCellEditing(btn);
095        return pnl;
096    }
097
098    /**
099     * Returns the paste action.
100     * @return the paste action
101     */
102    public AbstractAction getPasteAction() {
103        return tagTable.getPasteAction();
104    }
105
106    /**
107     * builds the GUI
108     */
109    protected final void build() {
110        setLayout(new GridBagLayout());
111        JPanel tablePanel = buildTagTableEditorPanel();
112        JPanel buttonPanel = buildButtonsPanel();
113
114        GridBagConstraints gc = new GridBagConstraints();
115
116        // -- buttons panel
117        //
118        gc.fill = GridBagConstraints.VERTICAL;
119        gc.weightx = 0.0;
120        gc.weighty = 1.0;
121        gc.anchor = GridBagConstraints.NORTHWEST;
122        add(buttonPanel, gc);
123
124        // -- the panel with the editor table
125        //
126        gc.gridx = 1;
127        gc.fill = GridBagConstraints.BOTH;
128        gc.weightx = 1.0;
129        gc.weighty = 1.0;
130        gc.anchor = GridBagConstraints.CENTER;
131        add(tablePanel, gc);
132
133        if (presetHandler != null) {
134            model.addTableModelListener(new TableModelListener() {
135                @Override
136                public void tableChanged(TableModelEvent e) {
137                    updatePresets();
138                }
139            });
140        }
141
142        addFocusListener(new FocusAdapter() {
143            @Override public void focusGained(FocusEvent e) {
144                tagTable.requestFocusInCell(0, 0);
145            }
146        });
147    }
148
149    /**
150     * Creates a new tag editor panel. The editor model is created
151     * internally and can be retrieved with {@link #getModel()}.
152     * @param presetHandler tagging preset handler
153     */
154    public TagEditorPanel(TaggingPresetHandler presetHandler) {
155        this(null, 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                EnumSet.of(TaggingPresetType.RELATION),
215                model.getTags(), presetHandler);
216        validate();
217    }
218}