001package jmri.jmrit.beantable;
002
003import java.awt.event.ActionEvent;
004import java.text.MessageFormat;
005import java.util.Arrays;
006import java.util.HashMap;
007import javax.annotation.Nonnull;
008import javax.swing.AbstractAction;
009import javax.swing.JButton;
010import javax.swing.JPanel;
011import javax.swing.JTable;
012import javax.swing.table.TableRowSorter;
013
014import jmri.InstanceManager;
015import jmri.Manager;
016import jmri.NamedBean;
017import jmri.ProxyManager;
018import jmri.UserPreferencesManager;
019import jmri.SystemConnectionMemo;
020import jmri.jmrix.SystemConnectionMemoManager;
021import jmri.swing.ManagerComboBox;
022
023import org.slf4j.Logger;
024import org.slf4j.LoggerFactory;
025
026/**
027 * Swing action to create and register a NamedBeanTable GUI.
028 *
029 * @param <E> type of NamedBean supported in this table
030 * @author Bob Jacobsen Copyright (C) 2003
031 */
032public abstract class AbstractTableAction<E extends NamedBean> extends AbstractAction {
033
034    public AbstractTableAction(String actionName) {
035        super(actionName);
036    }
037
038    public AbstractTableAction(String actionName, Object option) {
039        super(actionName);
040    }
041
042    protected BeanTableDataModel<E> m;
043
044    /**
045     * Create the JTable DataModel, along with the changes for the specific
046     * NamedBean type.
047     */
048    protected abstract void createModel();
049
050    /**
051     * Include the correct title.
052     */
053    protected abstract void setTitle();
054
055    protected BeanTableFrame<E> f;
056
057    @Override
058    public void actionPerformed(ActionEvent e) {
059        // create the JTable model, with changes for specific NamedBean
060        createModel();
061        TableRowSorter<BeanTableDataModel<E>> sorter = new TableRowSorter<>(m);
062        JTable dataTable = m.makeJTable(m.getMasterClassName(), m, sorter);
063
064        // allow reordering of the columns
065        dataTable.getTableHeader().setReorderingAllowed(true);
066
067        // create the frame
068        f = new BeanTableFrame<E>(m, helpTarget(), dataTable) {
069
070            /**
071             * Include an "Add..." button
072             */
073            @Override
074            void extras() {
075                if (includeAddButton) {
076                    JButton addButton = new JButton(Bundle.getMessage("ButtonAdd"));
077                    addToBottomBox(addButton, this.getClass().getName());
078                    addButton.addActionListener((ActionEvent e1) -> {
079                        addPressed(e1);
080                    });
081                }
082            }
083        };
084        setMenuBar(f); // comes after the Help menu is added by f = new
085                       // BeanTableFrame(etc.) in stand alone application
086        setTitle();
087        addToFrame(f);
088        f.pack();
089        f.setVisible(true);
090    }
091
092    public BeanTableDataModel<E> getTableDataModel() {
093        createModel();
094        return m;
095    }
096
097    public void setFrame(@Nonnull BeanTableFrame<E> frame) {
098        f = frame;
099    }
100
101    public BeanTableFrame<E> getFrame() {
102        return f;
103    }
104
105    /**
106     * Allow subclasses to add to the frame without having to actually subclass
107     * the BeanTableDataFrame.
108     *
109     * @param f the Frame to add to
110     */
111    public void addToFrame(@Nonnull BeanTableFrame<E> f) {
112    }
113
114    /**
115     * If the subClass is being included in a greater tabbed frame, then this
116     * method is used to add the details to the tabbed frame.
117     *
118     * @param f AbstractTableTabAction for the containing frame containing these
119     *          and other tabs
120     */
121    public void addToPanel(AbstractTableTabAction<E> f) {
122    }
123
124    /**
125     * If the subClass is being included in a greater tabbed frame, then this is
126     * used to specify which manager the subclass should be using.
127     *
128     * @param man Manager for this table tab
129     */
130    protected void setManager(@Nonnull Manager<E> man) {
131    }
132
133    /**
134     * Allow subclasses to alter the frame's Menubar without having to actually
135     * subclass the BeanTableDataFrame.
136     *
137     * @param f the Frame to attach the menubar to
138     */
139    public void setMenuBar(BeanTableFrame<E> f) {
140    }
141
142    public JPanel getPanel() {
143        return null;
144    }
145
146    public void dispose() {
147        if (m != null) {
148            m.dispose();
149        }
150        // should this also dispose of the frame f?
151    }
152
153    /**
154     * Increments trailing digits of a system/user name (string) I.E. "Geo7"
155     * returns "Geo8" Note: preserves leading zeros: "Geo007" returns "Geo008"
156     * Also, if no trailing digits, appends "1": "Geo" returns "Geo1"
157     *
158     * @param name the system or user name string
159     * @return the same name with trailing digits incremented by one
160     */
161    protected @Nonnull String nextName(@Nonnull String name) {
162        final String[] parts = name.split("(?=\\d+$)", 2);
163        String numString = "0";
164        if (parts.length == 2) {
165            numString = parts[1];
166        }
167        final int numStringLength = numString.length();
168        final int num = Integer.parseInt(numString) + 1;
169        return parts[0] + String.format("%0" + numStringLength + "d", num);
170    }
171
172    /**
173     * Specify the JavaHelp target for this specific panel.
174     *
175     * @return a fixed default string "index" pointing to to highest level in
176     *         JMRI Help
177     */
178    protected String helpTarget() {
179        return "index"; // by default, go to the top
180    }
181
182    public String getClassDescription() {
183        return "Abstract Table Action";
184    }
185
186    public void setMessagePreferencesDetails() {
187        HashMap<Integer, String> options = new HashMap<>(3);
188        options.put(0x00, Bundle.getMessage("DeleteAsk"));
189        options.put(0x01, Bundle.getMessage("DeleteNever"));
190        options.put(0x02, Bundle.getMessage("DeleteAlways"));
191        jmri.InstanceManager.getDefault(jmri.UserPreferencesManager.class).setMessageItemDetails(getClassName(),
192                "deleteInUse", Bundle.getMessage("DeleteItemInUse"), options, 0x00);
193    }
194
195    protected abstract String getClassName();
196
197    public boolean includeAddButton() {
198        return includeAddButton;
199    }
200
201    protected boolean includeAddButton = true;
202
203    /**
204     * Used with the Tabbed instances of table action, so that the print option
205     * is handled via that on the appropriate tab.
206     *
207     * @param mode         table print mode
208     * @param headerFormat messageFormat for header
209     * @param footerFormat messageFormat for footer
210     */
211    public void print(JTable.PrintMode mode, MessageFormat headerFormat, MessageFormat footerFormat) {
212        log.error("Printing not handled for {} tables.", m.getBeanType());
213    }
214
215    protected abstract void addPressed(ActionEvent e);
216
217    /**
218     * Configure the combo box listing managers.
219     * Can be placed on Add New pane to select a connection for the new item.
220     *
221     * @param comboBox     the combo box to configure
222     * @param manager      the current manager
223     * @param managerClass the implemented manager class for the current
224     *                     manager; this is the class used by
225     *                     {@link InstanceManager#getDefault(Class)} to get the
226     *                     default manager, which may or may not be the current
227     *                     manager
228     */
229    protected void configureManagerComboBox(ManagerComboBox<E> comboBox, Manager<E> manager,
230            Class<? extends Manager<E>> managerClass) {
231        Manager<E> defaultManager = InstanceManager.getDefault(managerClass);
232        // populate comboBox
233        if (defaultManager instanceof ProxyManager) {
234            comboBox.setManagers(defaultManager);
235        } else {
236            comboBox.setManagers(manager);
237        }
238        // set current selection
239        if (manager instanceof ProxyManager) {
240            UserPreferencesManager upm = InstanceManager.getDefault(UserPreferencesManager.class);
241            String systemSelectionCombo = this.getClass().getName() + ".SystemSelected";
242            if (upm.getComboBoxLastSelection(systemSelectionCombo) != null) {
243                SystemConnectionMemo memo = SystemConnectionMemoManager.getDefault()
244                        .getSystemConnectionMemoForUserName(upm.getComboBoxLastSelection(systemSelectionCombo));
245                comboBox.setSelectedItem(memo.get(managerClass));
246            } else {
247                ProxyManager<E> proxy = (ProxyManager<E>) manager;
248                comboBox.setSelectedItem(proxy.getDefaultManager());
249            }
250        } else {
251            comboBox.setSelectedItem(manager);
252        }
253    }
254
255    /**
256     * Remove the Add panel prefixBox listener before disposal.
257     * The listener is created when the Add panel is defined.  It persists after the
258     * the Add panel has been disposed.  When the next Add is created, AbstractTableAction
259     * sets the default connection as the current selection.  This triggers validation before
260     * the new Add panel is created.
261     * <p>
262     * The listener is removed by the controlling table action before disposing of the Add
263     * panel after Close or Create.
264     * @param prefixBox The prefix combobox that might contain the listener.
265     */
266    protected void removePrefixBoxListener(ManagerComboBox<E> prefixBox) {
267        Arrays.asList(prefixBox.getActionListeners()).forEach((l) -> {
268            prefixBox.removeActionListener(l);
269        });
270    }
271
272    /**
273     * Display a warning to user about invalid entry. Needed as entry validation
274     * does not disable the Create button when full system name eg "LT1" is entered.
275     *
276     * @param curAddress address as entered in Add new... pane address field
277     * @param ex the exception that occurred
278     */
279    protected void displayHwError(String curAddress, Exception ex) {
280        jmri.InstanceManager.getDefault(jmri.UserPreferencesManager .class).
281                showErrorMessage(Bundle.getMessage("ErrorTitle"),
282                        Bundle.getMessage("ErrorConvertHW", curAddress),"" + ex,"",
283                        true,false);
284    }
285
286    private static final Logger log = LoggerFactory.getLogger(AbstractTableAction.class);
287
288}