001package jmri.jmrit.beantable.signalmast;
002
003import java.util.HashMap;
004import java.util.Vector;
005import javax.annotation.Nonnull;
006import javax.swing.JButton;
007import javax.swing.JComboBox;
008import javax.swing.JTable;
009import javax.swing.JTextField;
010import javax.swing.SwingUtilities;
011import jmri.InstanceManager;
012import jmri.Manager;
013import jmri.NamedBean;
014import jmri.SignalMast;
015import jmri.SignalMastManager;
016import jmri.jmrit.beantable.BeanTableDataModel;
017import jmri.jmrit.beantable.RowComboBoxPanel;
018import jmri.jmrit.signalling.SignallingSourceAction;
019
020/**
021 * Data model for a SignalMastTable
022 *
023 * @author Bob Jacobsen Copyright (C) 2003, 2009
024 * @author Egbert Broerse Copyright (C) 2016
025 */
026public class SignalMastTableDataModel extends BeanTableDataModel<SignalMast> {
027
028    public static final int EDITMASTCOL = NUMCOLUMN;
029    public static final int EDITLOGICCOL = EDITMASTCOL + 1;
030    public static final int LITCOL = EDITLOGICCOL + 1;
031    public static final int HELDCOL = LITCOL + 1;
032
033    @Override
034    public String getValue(String name) {
035        SignalMast sm = InstanceManager.getDefault(SignalMastManager.class).getBySystemName(name);
036        if (sm != null) {
037            return sm.getAspect();
038        } else {
039            return null;
040        }
041    }
042
043    @Override
044    public int getColumnCount() {
045        return NUMCOLUMN + 4;
046    }
047
048    @Override
049    public String getColumnName(int col) {
050        switch (col) {
051            case VALUECOL:
052                return Bundle.getMessage("LabelAspectType");
053            case EDITMASTCOL:
054                return ""; // override default, no title for Edit column
055            case EDITLOGICCOL:
056                return ""; // override default, no title for Edit Logic column
057            case LITCOL:
058                return Bundle.getMessage("ColumnHeadLit");
059            case HELDCOL:
060                return Bundle.getMessage("ColumnHeadHeld");
061            default:
062                return super.getColumnName(col);
063        }
064    }
065
066    @Override
067    public Class<?> getColumnClass(int col) {
068        switch (col) {
069            case VALUECOL:
070                return RowComboBoxPanel.class; // Use a JPanel containing a custom Aspect ComboBox
071            case EDITMASTCOL:
072            case EDITLOGICCOL:
073                return JButton.class;
074            case LITCOL:
075            case HELDCOL:
076                return Boolean.class;
077            default:
078                return super.getColumnClass(col);
079        }
080    }
081
082    @Override
083    public int getPreferredWidth(int col) {
084        switch (col) {
085            case LITCOL:
086                // I18N use Bundle.getMessage() + length() for PreferredWidth size
087                return new JTextField(Bundle.getMessage("ColumnHeadLit").length()).getPreferredSize().width;
088            case HELDCOL:
089                return new JTextField(Bundle.getMessage("ColumnHeadHeld").length()).getPreferredSize().width;
090            case EDITLOGICCOL:
091                return new JTextField(Bundle.getMessage("EditSignalLogicButton").length()).getPreferredSize().width;
092            case EDITMASTCOL:
093                return new JTextField(Bundle.getMessage("ButtonEdit").length()).getPreferredSize().width;
094            default:
095                return super.getPreferredWidth(col);
096        }
097    }
098
099    @Override
100    public boolean isCellEditable(int row, int col) {
101        switch (col) {
102            case LITCOL:
103            case EDITLOGICCOL:
104            case EDITMASTCOL:
105            case HELDCOL:
106                return true;
107            default:
108                return super.isCellEditable(row, col);
109        }
110    }
111
112    @Override
113    protected Manager<SignalMast> getManager() {
114        return InstanceManager.getDefault(SignalMastManager.class);
115    }
116
117    @Override
118    protected SignalMast getBySystemName(@Nonnull String name) {
119        return InstanceManager.getDefault(SignalMastManager.class).getBySystemName(name);
120    }
121
122    @Override
123    protected SignalMast getByUserName(@Nonnull String name) {
124        return InstanceManager.getDefault(SignalMastManager.class).getByUserName(name);
125    }
126
127    @Override
128    protected String getMasterClassName() {
129        return getClassName();
130    }
131
132    @Override
133    protected void clickOn(SignalMast t) {
134        log.debug("No action for click on {}",t.getDisplayName());
135    }
136
137    @Override
138    public Object getValueAt(int row, int col) {
139        // some error checking
140        if (row >= sysNameList.size()) {
141            log.debug("row index is greater than name list");
142            return "error";
143        }
144        String name = sysNameList.get(row);
145        SignalMast s = InstanceManager.getDefault(SignalMastManager.class).getBySystemName(name);
146        if (s == null) {
147            return false; // if due to race condition, the device is going away
148        }
149        switch (col) {
150            case LITCOL:
151                return s.getLit();
152            case HELDCOL:
153                return s.getHeld();
154            case EDITLOGICCOL:
155                return Bundle.getMessage("EditSignalLogicButton");
156            case EDITMASTCOL:
157                return Bundle.getMessage("ButtonEdit");
158            case VALUECOL:
159                String aspect = s.getAspect();
160                if ( aspect != null) {
161                    return aspect;
162                } else {
163                    //Aspect not set,  - too verbose, even at trace
164                    //log.trace("Aspect not set, NULL aspect returned for mast in row {}", row);
165                    return Bundle.getMessage("BeanStateUnknown"); // use place holder string in table
166                }
167            default:
168                return super.getValueAt(row, col);
169        }
170    }
171
172    @Override
173    public void setValueAt(Object value, int row, int col) {
174        String name = sysNameList.get(row);
175        SignalMast s = InstanceManager.getDefault(SignalMastManager.class).getBySystemName(name);
176        if (s == null) {
177            return;  // device is going away anyway
178        }
179        switch (col) {
180            case VALUECOL:
181                if ((String) value != null) {
182                    log.debug("setValueAt (rowConverted={}; value={})", row, value);
183                    s.setAspect((String) value);
184                    fireTableRowsUpdated(row, row);
185                }
186                break;
187            case LITCOL: {
188                boolean b = ((Boolean) value);
189                s.setLit(b);
190                break;
191            }
192            case HELDCOL: {
193                boolean b = ((Boolean) value);
194                s.setHeld(b);
195                break;
196            }
197            case EDITLOGICCOL:
198                editLogic(row, col);
199                break;
200            case EDITMASTCOL:
201                editMast(row, col);
202                break;
203            default:
204                super.setValueAt(value, row, col);
205                break;
206        }
207    }
208
209    void editLogic(int row, int col) {
210        SwingUtilities.invokeLater(() -> {
211            SignallingSourceAction action = new SignallingSourceAction(Bundle.getMessage(
212                "TitleSignalMastLogicTable"), getBySystemName(sysNameList.get(row)));
213            action.actionPerformed(null);
214        });
215    }
216
217    void editMast(int row, int col) {
218        SwingUtilities.invokeLater(() -> {
219            AddSignalMastJFrame editFrame = new AddSignalMastJFrame(getBySystemName(sysNameList.get(row)));
220            editFrame.setVisible(true);
221        });
222    }
223
224    /**
225     * Respond to change from bean.
226     *
227     * @param e the change event to respond to
228     */
229    @Override
230    public void propertyChange(java.beans.PropertyChangeEvent e) {
231        if ( (e.getPropertyName().contains("aspectEnabled") || e.getPropertyName().contains("aspectDisabled"))
232            && (e.getSource() instanceof NamedBean ) ) {
233
234            String name = ((NamedBean) e.getSource()).getSystemName();
235            if (log.isDebugEnabled()) {
236                log.debug("Update cell {}, {} for {}", sysNameList.indexOf(name), VALUECOL, name);
237            }
238            // since we can add columns, the entire row is marked as updated
239            int row = sysNameList.indexOf(name);
240            this.fireTableRowsUpdated(row, row);
241            clearAspectVector(row);
242        }
243        super.propertyChange(e);
244    }
245
246    @Override
247    protected boolean matchPropertyName(java.beans.PropertyChangeEvent e) {
248        if (e.getPropertyName().contains("Aspect") || e.getPropertyName().contains("Lit")
249                || e.getPropertyName().contains("Held") || e.getPropertyName().contains("aspectDisabled")
250                || e.getPropertyName().contains("aspectEnabled")) {
251
252            return true;
253        }
254        return super.matchPropertyName(e);
255    }
256
257    /**
258     * Customize the SignalMast Value (Aspect) column to show an appropriate
259     * ComboBox of available Aspects when the TableDataModel is being called
260     * from ListedTableAction.
261     *
262     * @param table a JTable of Signal Masts
263     */
264    @Override
265    protected void configValueColumn(JTable table) {
266        // have the value column hold a JPanel with a JComboBox for Aspects
267        setColumnToHoldButton(table, VALUECOL, configureButton());
268        // add extras, override BeanTableDataModel
269        log.debug("Mast configValueColumn (I am {})", super.toString());
270        table.setDefaultEditor(RowComboBoxPanel.class, new AspectComboBoxPanel());
271        // create a separate class for the renderer
272        table.setDefaultRenderer(RowComboBoxPanel.class, new AspectComboBoxPanel());
273        // Set more things?
274    }
275
276    /**
277     * Set column width.
278     *
279     * @return a button to fit inside the VALUE column
280     */
281    @Override
282    public JButton configureButton() {
283        // pick a large size
284        JButton b = new JButton("Diverging Approach Medium"); // about the longest Aspect string
285        b.putClientProperty("JComponent.sizeVariant", "small");
286        b.putClientProperty("JButton.buttonType", "square");
287        return b;
288    }
289
290    /**
291     * A row specific Aspect combobox cell editor/renderer
292     */
293    public class AspectComboBoxPanel extends RowComboBoxPanel {
294
295        @Override
296        protected final void eventEditorMousePressed() {
297            this.editor.add(getEditorBox(table.convertRowIndexToModel(this.currentRow))); // add eb to JPanel
298            this.editor.revalidate();
299            SwingUtilities.invokeLater(this.comboBoxFocusRequester);
300            log.debug("eventEditorMousePressed in row: {}; me = {})", this.currentRow, this.toString());
301        }
302
303        /**
304         * Call method getAspectEditorBox() in the surrounding method for the SignalMastTable.
305         * @param row Index of the row clicked in the table
306         * @return an appropriate combobox for this signal mast
307         */
308        @Override
309        protected JComboBox<String> getEditorBox(int row) {
310            return getAspectEditorBox(row);
311        }
312
313    }
314
315    /**
316     * Clear the old aspect comboboxes and force them to be rebuilt
317     *
318     * @param row Index of the signal mast (in TableDataModel) to be rebuilt in
319     *            the HashMaps
320     */
321    public void clearAspectVector(int row) {
322        boxMap.remove(this.getValueAt(row, SYSNAMECOL));
323        editorMap.remove(this.getValueAt(row, SYSNAMECOL));
324    }
325
326    // HashMaps for Editors; not used for Renderer)
327    /**
328     * Provide a JComboBox element to display inside the JPanel CellEditor. When
329     * not yet present, create, store and return a new one.
330     *
331     * @param row Index number (in TableDataModel)
332     * @return A combobox containing the valid aspect names for this mast
333     */
334    JComboBox<String> getAspectEditorBox(int row) {
335        JComboBox<String> editCombo = editorMap.get(this.getValueAt(row, SYSNAMECOL));
336        if (editCombo == null) {
337            // create a new one with correct aspects
338            editCombo = new JComboBox<>(getAspectVector(row));
339            editorMap.put(this.getValueAt(row, SYSNAMECOL), editCombo);
340        }
341        return editCombo;
342    }
343    HashMap<Object, JComboBox<String>> editorMap = new HashMap<>();
344
345    /**
346     * Holds a HashMap of valid aspects per signal mast used by getEditorBox()
347     *
348     * @param row Index number (in TableDataModel)
349     * @return The Vector of valid aspect names for this mast to show in the
350     *         JComboBox
351     */
352    Vector<String> getAspectVector(int row) {
353        Vector<String> comboaspects = boxMap.get(this.getValueAt(row, SYSNAMECOL));
354        if (comboaspects == null) {
355            // create a new one with right aspects
356            Vector<String> v = ((SignalMast)this.getValueAt(row, SYSNAMECOL)).getValidAspects();
357            comboaspects = v;
358            boxMap.put(this.getValueAt(row, SYSNAMECOL), comboaspects);
359        }
360        return comboaspects;
361    }
362
363    HashMap<Object, Vector<String>> boxMap = new HashMap<>();
364
365    // end of methods to display VALUECOL (Aspect) ComboBox
366    protected String getClassName() {
367        return jmri.jmrit.beantable.SignalMastTableAction.class.getName();
368    }
369
370    public String getClassDescription() {
371        return Bundle.getMessage("TitleSignalMastTable");
372    }
373
374    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SignalMastTableDataModel.class);
375
376}