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
070        JPanel radio = new JPanel();
071        radio.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 0));
072        JPanel bean = new JPanel();
073        bean.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 0));
074        radio.add(existingItem);
075        radio.add(newItem);
076
077        if (manager instanceof ProxyManager) {
078            ProxyManager<E> proxy = (ProxyManager<E>) manager;
079            prefixBox.setManagers(proxy.getManagerList(), proxy.getDefaultManager());
080            if (p.getComboBoxLastSelection(systemSelectionCombo) != null) {
081                prefixBox.setSelectedItem(p.getComboBoxLastSelection(systemSelectionCombo));
082            }
083        } else { // not a proxy, just one
084            prefixBox.setManagers(manager);
085        }
086        
087        bean.add(existingCombo);
088        bean.add(prefixBox);
089        bean.add(hardwareAddress);
090        hardwareAddress.setToolTipText(Bundle.getMessage("EnterHWaddressAsIntTooltip"));
091        SystemNameValidator validator = new SystemNameValidator(hardwareAddress, prefixBox.getSelectedItem());
092        prefixBox.addItemListener((ItemEvent e) -> {
093            validator.setManager(prefixBox.getSelectedItem());
094        });
095        hardwareAddress.setInputVerifier(validator);
096        super.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
097        super.add(radio);
098        super.add(bean);
099        BeanSelectCreatePanel.this.update();
100    }
101
102    void update() {
103        boolean select = true;
104        if (newItem.isSelected()) {
105            select = false;
106        }
107        prefixBox.setVisible(false);
108        hardwareAddress.setVisible(false);
109        existingCombo.setVisible(false);
110        if (select) {
111            existingCombo.setVisible(true);
112        } else {
113            prefixBox.setVisible(true);
114            hardwareAddress.setVisible(true);
115        }
116    }
117
118    @Override
119    public void setEnabled(boolean enabled) {
120        existingItem.setEnabled(enabled);
121        hardwareAddress.setEnabled(enabled);
122        prefixBox.setEnabled(enabled);
123        newItem.setEnabled(enabled);
124        existingCombo.setEnabled(enabled);
125        super.setEnabled(enabled);
126    }
127
128    /**
129     * Does nothing.
130     * 
131     * @deprecated since 4.17.2 without direct replacement
132     */
133    @Deprecated
134    public void refresh() {
135        // do nothing
136    }
137
138    /**
139     * Get the display name of the bean that has either been selected in the
140     * drop down list or was asked to be created.
141     *
142     * @return the name of the bean
143     */
144    public String getDisplayName() {
145        if (existingItem.isSelected()) {
146            return existingCombo.getSelectedItemDisplayName();
147        } else {
148            try {
149                E nBean = createBean();
150                return nBean.getDisplayName();
151            } catch (JmriException e) {
152                return "";
153            }
154        }
155    }
156
157    /**
158     * Is a bean either selected or has the user entered a new name in the combo box?
159     * If either is false, the caller should not try to create a new bean.
160     * @return true if a bean is selected or a name is entered
161     */
162    public boolean hasBeanOrBeanName() {
163        return existingItem.isSelected() || !hardwareAddress.getText().trim().isEmpty();
164    }
165
166    /**
167     * Get the named bean that has either been selected in the drop down list or
168     * was asked to be created.
169     *
170     * @return the selected bean or a new bean
171     * @throws JmriException if a bean needs to be created but can't be
172     */
173    public E getNamedBean() throws JmriException {
174        if (existingItem.isSelected()) {
175            return existingCombo.getSelectedItem();
176        }
177        try {
178            return createBean();
179        } catch (JmriException e) {
180            throw e;
181        }
182    }
183
184    private E createBean() throws JmriException {
185        Manager<E> manager = prefixBox.getSelectedItem();
186        E nBean = null;
187        if (manager instanceof ProvidingManager) {
188            ProvidingManager<E> provider = (ProvidingManager<E>) manager;
189            try {
190                nBean = provider.provide(provider.makeSystemName(hardwareAddress.getText()));
191            } catch (IllegalArgumentException ex) {
192                throw new JmriException(ex);
193            }
194        }
195        if (nBean == null) {
196            throw new JmriException("Unable to create bean");
197        }
198        updateComment(nBean, _reference);
199        setDefaultNamedBean(nBean);
200        return nBean;
201    }
202
203    /**
204     * Set a reference that can be set against the comment for a bean.
205     *
206     * @param ref the default comment for a bean without a comment
207     */
208    public void setReference(String ref) {
209        _reference = ref;
210    }
211
212    /**
213     * Set the default selected item in the combo box. After it has been set,
214     * the combo box becomes active and the Add Hardware box details are then
215     * hidden.
216     *
217     * @param nBean the bean that is selected by default
218     */
219    public void setDefaultNamedBean(E nBean) {
220        _defaultSelect = nBean;
221        existingCombo.setSelectedItem(_defaultSelect);
222        existingItem.setSelected(true);
223        update();
224    }
225
226    /**
227     * Check that the user selected something in this BeanSelectCreatePanel.
228     *
229     * @return true if not empty
230     */
231    public boolean isEmpty() {
232        if (existingItem.isSelected() && existingCombo.getSelectedItem() != null) { // use existing
233            log.debug("existingCombo.getSelectedBean() = {}", existingCombo.getSelectedItem().getDisplayName());
234            return false;
235        } else if (newItem.isSelected() && // create new
236                !hardwareAddress.getText().isEmpty() && hardwareAddress.getText() != null) {
237            log.debug("newBeanEntry = {}", hardwareAddress.getText());
238            return false;
239        }
240        return true;
241    }
242
243    /**
244     * Update comment on bean if there's content AND there's not already a comment.
245     *
246     * @param nBean   the bean to edit
247     * @param content comment to add
248     */
249    public void updateComment(@Nonnull E nBean, String content) {
250        String comment = nBean.getComment();
251        log.debug((comment == null || comment.isEmpty()) ? "comment was empty" : "comment already filled");
252        if((content != null && !content.isEmpty()) && (comment ==null || comment.isEmpty())) {
253            log.debug("new comment added to bean {}", nBean.getDisplayName());
254            nBean.setComment(content);
255        } else {
256            log.debug("empty _reference received");
257        }
258    }
259
260    public void dispose() {
261        existingCombo.dispose();
262    }
263
264    //initialize logging
265    private final static Logger log = LoggerFactory.getLogger(BeanSelectCreatePanel.class.getName());
266
267}