001package jmri.util.swing;
002
003import java.awt.FlowLayout;
004import java.awt.event.ActionEvent;
005import java.awt.event.ItemEvent;
006
007import javax.annotation.Nonnull;
008import javax.swing.BoxLayout;
009import javax.swing.ButtonGroup;
010import javax.swing.JPanel;
011import javax.swing.JRadioButton;
012import javax.swing.JTextField;
013import jmri.InstanceManager;
014import jmri.JmriException;
015import jmri.Manager;
016import jmri.NamedBean;
017import jmri.NamedBean.DisplayOptions;
018import jmri.ProvidingManager;
019import jmri.UserPreferencesManager;
020import jmri.ProxyManager;
021import jmri.swing.ManagerComboBox;
022import jmri.swing.NamedBeanComboBox;
023import jmri.swing.SystemNameValidator;
024import org.slf4j.Logger;
025import org.slf4j.LoggerFactory;
026
027public class BeanSelectCreatePanel<E extends NamedBean> extends JPanel {
028
029    //Manager<E> _manager;
030    E _defaultSelect;
031    String _reference = null;
032    JRadioButton existingItem = new JRadioButton();
033    JRadioButton newItem;
034    ButtonGroup selectcreate = new ButtonGroup();
035
036    NamedBeanComboBox<E> existingCombo;
037    JTextField hardwareAddress = new JTextField(8);
038    ManagerComboBox<E> prefixBox = new ManagerComboBox<>();
039    String systemSelectionCombo = this.getClass().getName() + ".SystemSelected";
040
041    /**
042     * Create a JPanel that provides the option to the user to either select an
043     * already created bean, or to create one on the fly. This only currently
044     * works with Turnouts, Sensors, Memories and Blocks.
045     *
046     * @param manager       the bean manager
047     * @param defaultSelect the bean that is selected by default
048     */
049    public BeanSelectCreatePanel(@Nonnull Manager<E> manager, E defaultSelect) {
050        _defaultSelect = defaultSelect;
051        UserPreferencesManager p = InstanceManager.getDefault(UserPreferencesManager.class);
052        existingItem = new JRadioButton(Bundle.getMessage("UseExisting"), true);
053        newItem = new JRadioButton(Bundle.getMessage("CreateNew"));
054        existingItem.addActionListener((ActionEvent e) -> {
055            update();
056        });
057        newItem.addActionListener((ActionEvent e) -> {
058            update();
059        });
060
061        selectcreate.add(existingItem);
062        selectcreate.add(newItem);
063        existingCombo = new NamedBeanComboBox<>(manager, defaultSelect, DisplayOptions.USERNAME_SYSTEMNAME);
064        // If the combo list is empty we go straight to creation.
065        if (existingCombo.getItemCount() == 0) {
066            newItem.setSelected(true);
067        }
068        existingCombo.setAllowNull(true);
069        JComboBoxUtil.setupComboBoxMaxRows(existingCombo);
070
071        JPanel radio = new JPanel();
072        radio.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 0));
073        JPanel bean = new JPanel();
074        bean.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 0));
075        radio.add(existingItem);
076        radio.add(newItem);
077
078        if (manager instanceof ProxyManager) {
079            ProxyManager<E> proxy = (ProxyManager<E>) manager;
080            prefixBox.setManagers(proxy.getManagerList(), proxy.getDefaultManager());
081            if (p.getComboBoxLastSelection(systemSelectionCombo) != null) {
082                prefixBox.setSelectedItem(p.getComboBoxLastSelection(systemSelectionCombo));
083            }
084        } else { // not a proxy, just one
085            prefixBox.setManagers(manager);
086        }
087
088        bean.add(existingCombo);
089        bean.add(prefixBox);
090        bean.add(hardwareAddress);
091        hardwareAddress.setToolTipText(Bundle.getMessage("EnterHWaddressAsIntTooltip"));
092        SystemNameValidator validator = new SystemNameValidator(hardwareAddress, prefixBox.getSelectedItem());
093        prefixBox.addItemListener((ItemEvent e) -> {
094            validator.setManager(prefixBox.getSelectedItem());
095        });
096        hardwareAddress.setInputVerifier(validator);
097        super.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
098        super.add(radio);
099        super.add(bean);
100        BeanSelectCreatePanel.this.update();
101    }
102
103    void update() {
104        boolean select = true;
105        if (newItem.isSelected()) {
106            select = false;
107        }
108        prefixBox.setVisible(false);
109        hardwareAddress.setVisible(false);
110        existingCombo.setVisible(false);
111        if (select) {
112            existingCombo.setVisible(true);
113        } else {
114            prefixBox.setVisible(true);
115            hardwareAddress.setVisible(true);
116        }
117    }
118
119    @Override
120    public void setEnabled(boolean enabled) {
121        existingItem.setEnabled(enabled);
122        hardwareAddress.setEnabled(enabled);
123        prefixBox.setEnabled(enabled);
124        newItem.setEnabled(enabled);
125        existingCombo.setEnabled(enabled);
126        super.setEnabled(enabled);
127    }
128
129    /**
130     * Get the display name of the bean that has either been selected in the
131     * drop down list or was asked to be created.
132     *
133     * @return the name of the bean
134     */
135    public String getDisplayName() {
136        if (existingItem.isSelected()) {
137            return existingCombo.getSelectedItemDisplayName();
138        } else {
139            try {
140                E nBean = createBean();
141                return nBean.getDisplayName();
142            } catch (JmriException e) {
143                return "";
144            }
145        }
146    }
147
148    /**
149     * Is a bean either selected or has the user entered a new name in the combo box?
150     * If either is false, the caller should not try to create a new bean.
151     * @return true if a bean is selected or a name is entered
152     */
153    public boolean hasBeanOrBeanName() {
154        return existingItem.isSelected() || !hardwareAddress.getText().trim().isEmpty();
155    }
156
157    /**
158     * Get the named bean that has either been selected in the drop down list or
159     * was asked to be created.
160     *
161     * @return the selected bean or a new bean
162     * @throws JmriException if a bean needs to be created but can't be
163     */
164    public E getNamedBean() throws JmriException {
165        if (existingItem.isSelected()) {
166            return existingCombo.getSelectedItem();
167        }
168        try {
169            return createBean();
170        } catch (JmriException e) {
171            throw e;
172        }
173    }
174
175    private E createBean() throws JmriException {
176        Manager<E> manager = prefixBox.getSelectedItem();
177        E nBean = null;
178        if (manager instanceof ProvidingManager) {
179            ProvidingManager<E> provider = (ProvidingManager<E>) manager;
180            try {
181                nBean = provider.provide(provider.makeSystemName(hardwareAddress.getText()));
182            } catch (IllegalArgumentException ex) {
183                throw new JmriException(ex);
184            }
185        }
186        if (nBean == null) {
187            throw new JmriException("Unable to create bean");
188        }
189        updateComment(nBean, _reference);
190        setDefaultNamedBean(nBean);
191        return nBean;
192    }
193
194    /**
195     * Set a reference that can be set against the comment for a bean.
196     *
197     * @param ref the default comment for a bean without a comment
198     */
199    public void setReference(String ref) {
200        _reference = ref;
201    }
202
203    /**
204     * Set the default selected item in the combo box. After it has been set,
205     * the combo box becomes active and the Add Hardware box details are then
206     * hidden.
207     *
208     * @param nBean the bean that is selected by default
209     */
210    public void setDefaultNamedBean(E nBean) {
211        _defaultSelect = nBean;
212        existingCombo.setSelectedItem(_defaultSelect);
213        existingItem.setSelected(true);
214        update();
215    }
216
217    /**
218     * Check that the user selected something in this BeanSelectCreatePanel.
219     *
220     * @return true if not empty
221     */
222    public boolean isEmpty() {
223        if (existingItem.isSelected() && existingCombo.getSelectedItem() != null) { // use existing
224            log.debug("existingCombo.getSelectedBean() = {}", existingCombo.getSelectedItem().getDisplayName());
225            return false;
226        } else if (newItem.isSelected() && // create new
227                !hardwareAddress.getText().isEmpty() && hardwareAddress.getText() != null) {
228            log.debug("newBeanEntry = {}", hardwareAddress.getText());
229            return false;
230        }
231        return true;
232    }
233
234    /**
235     * Update comment on bean if there's content AND there's not already a comment.
236     *
237     * @param nBean   the bean to edit
238     * @param content comment to add
239     */
240    public void updateComment(@Nonnull E nBean, String content) {
241        String comment = nBean.getComment();
242        log.debug("comment {}", (comment == null || comment.isEmpty()) ? "was empty" : "already filled");
243        if((content != null && !content.isEmpty()) && (comment ==null || comment.isEmpty())) {
244            log.debug("new comment added to bean {}", nBean.getDisplayName());
245            nBean.setComment(content);
246        } else {
247            log.debug("empty _reference received");
248        }
249    }
250
251    public void dispose() {
252        existingCombo.dispose();
253    }
254
255    //initialize logging
256    private final static Logger log = LoggerFactory.getLogger(BeanSelectCreatePanel.class.getName());
257
258}