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    public void setNextFocusComponent(Component nextFocusComponent) {
062        tagTable.setNextFocusComponent(nextFocusComponent);
063    }
064
065    /**
066     * builds the panel with the button row
067     *
068     * @return the panel
069     */
070    protected JPanel buildButtonsPanel() {
071        JPanel pnl = new JPanel();
072        pnl.setLayout(new BoxLayout(pnl, BoxLayout.Y_AXIS));
073
074        // add action
075        //
076        JButton btn;
077        pnl.add(btn = new JButton(tagTable.getAddAction()));
078        btn.setMargin(new Insets(0, 0, 0, 0));
079        tagTable.addComponentNotStoppingCellEditing(btn);
080
081        // delete action
082        pnl.add(btn = new JButton(tagTable.getDeleteAction()));
083        btn.setMargin(new Insets(0, 0, 0, 0));
084        tagTable.addComponentNotStoppingCellEditing(btn);
085
086        // paste action
087        pnl.add(btn = new JButton(tagTable.getPasteAction()));
088        btn.setMargin(new Insets(0, 0, 0, 0));
089        tagTable.addComponentNotStoppingCellEditing(btn);
090        return pnl;
091    }
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(new TableModelListener() {
126                @Override
127                public void tableChanged(TableModelEvent e) {
128                    updatePresets();
129                }
130            });
131        }
132
133        addFocusListener(new FocusAdapter() {
134            @Override public void focusGained(FocusEvent e) {
135                tagTable.requestFocusInCell(0, 0);
136            }
137        });
138    }
139
140    /**
141     * Creates a new tag editor panel. The editor model is created
142     * internally and can be retrieved with {@link #getModel()}.
143     */
144    public TagEditorPanel(TaggingPresetHandler presetHandler) {
145        this(null, presetHandler, 0);
146    }
147
148    /**
149     * Creates a new tag editor panel with a supplied model. If
150     * {@code model} is null, a new model is created.
151     *
152     * @param model the tag editor model
153     * @param maxCharacters maximum number of characters allowed, 0 for unlimited
154     */
155    public TagEditorPanel(TagEditorModel model, TaggingPresetHandler presetHandler, final int maxCharacters) {
156        this.model = model;
157        this.presetHandler = presetHandler;
158        if (this.model == null) {
159            this.model = new TagEditorModel();
160        }
161        this.tagTable = new TagTable(this.model, maxCharacters);
162        build();
163    }
164
165    /**
166     * Replies the tag editor model used by this panel.
167     *
168     * @return the tag editor model used by this panel
169     */
170    public TagEditorModel getModel() {
171        return model;
172    }
173
174    /**
175     * Initializes the auto completion infrastructure used in this
176     * tag editor panel. {@code layer} is the data layer from whose data set
177     * tag values are proposed as auto completion items.
178     *
179     * @param layer the data layer. Must not be null.
180     * @throws IllegalArgumentException if {@code layer} is null
181     */
182    public void initAutoCompletion(OsmDataLayer layer) {
183        CheckParameterUtil.ensureParameterNotNull(layer, "layer");
184
185        AutoCompletionManager autocomplete = layer.data.getAutoCompletionManager();
186        AutoCompletionList acList = new AutoCompletionList();
187
188        TagCellEditor editor = (TagCellEditor) tagTable.getColumnModel().getColumn(0).getCellEditor();
189        editor.setAutoCompletionManager(autocomplete);
190        editor.setAutoCompletionList(acList);
191        editor = ((TagCellEditor) tagTable.getColumnModel().getColumn(1).getCellEditor());
192        editor.setAutoCompletionManager(autocomplete);
193        editor.setAutoCompletionList(acList);
194    }
195
196    @Override
197    public void setEnabled(boolean enabled) {
198        tagTable.setEnabled(enabled);
199        super.setEnabled(enabled);
200    }
201
202    private void updatePresets() {
203        presetListPanel.updatePresets(
204                EnumSet.of(TaggingPresetType.RELATION),
205                model.getTags(), presetHandler);
206        validate();
207    }
208}