001package jmri.jmrit.beantable.turnout;
002
003import java.awt.Component;
004import java.awt.event.KeyEvent;
005import java.awt.event.MouseEvent;
006
007import java.util.EventObject;
008import java.util.Hashtable;
009
010import javax.annotation.Nonnull;
011import javax.swing.border.Border;
012import javax.swing.BorderFactory;
013import javax.swing.DefaultCellEditor;
014import javax.swing.JTable;
015import javax.swing.table.*;
016import javax.swing.UIManager;
017
018import jmri.InstanceManager;
019import jmri.NamedBean;
020import jmri.Sensor;
021import jmri.SensorManager;
022import jmri.Turnout;
023import jmri.swing.NamedBeanComboBox;
024import jmri.util.SystemType;
025import jmri.util.swing.JComboBoxUtil;
026
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030/**
031 * JTable to display a TurnoutTableDataModel.
032 * Code originally within TurnoutTableAction.
033 *
034 * @author Bob Jacobsen Copyright (C) 2003, 2004, 2007
035 * @author Egbert Broerse Copyright (C) 2017
036 * @author Steve Young Copyright (C) 2021
037 */
038public class TurnoutTableJTable extends JTable {
039
040    final TurnoutTableDataModel model;
041
042    final Hashtable<Turnout, TableCellRenderer> rendererMapSensor1 = new Hashtable<>();
043    final Hashtable<Turnout, TableCellEditor> editorMapSensor1 = new Hashtable<>();
044
045    final Hashtable<Turnout, TableCellRenderer> rendererMapSensor2 = new Hashtable<>();
046    final Hashtable<Turnout, TableCellEditor> editorMapSensor2 = new Hashtable<>();
047
048    public TurnoutTableJTable(TurnoutTableDataModel mdl){
049        super(mdl);
050        model = mdl;
051    }
052
053    @Override
054    public String getToolTipText(@Nonnull MouseEvent e) {
055        java.awt.Point p = e.getPoint();
056        int rowIndex = rowAtPoint(p);
057        int colIndex = columnAtPoint(p);
058        int realRowIndex = convertRowIndexToModel(rowIndex);
059        int realColumnIndex = convertColumnIndexToModel(colIndex);
060        return model.getCellToolTip(this, realRowIndex, realColumnIndex);
061    }
062
063    /**
064     * Disable Windows Key or Mac Meta Keys being pressed acting
065     * as a trigger for editing the focused cell.
066     * Causes unexpected behaviour, i.e. button presses.
067     * {@inheritDoc}
068     */
069    @Override
070    public boolean editCellAt(int row, int column, EventObject e) {
071        if (e instanceof KeyEvent) {
072            if ( ((KeyEvent) e).getKeyCode() == KeyEvent.VK_WINDOWS
073                || ( (KeyEvent) e).getKeyCode() == KeyEvent.VK_META ) {
074                return false;
075            }
076        }
077        return super.editCellAt(row, column, e);
078    }
079
080    @Override
081    public TableCellRenderer getCellRenderer(int row, int column) {
082        // Convert the displayed index to the model index, rather than the displayed index
083        int modelColumn = this.convertColumnIndexToModel(column);
084        if (modelColumn == TurnoutTableDataModel.SENSOR1COL || modelColumn == TurnoutTableDataModel.SENSOR2COL) {
085            return getRenderer(row, modelColumn);
086        } else {
087            return super.getCellRenderer(row, column);
088        }
089    }
090
091    @Override
092    public TableCellEditor getCellEditor(int row, int column) {
093        //Convert the displayed index to the model index, rather than the displayed index
094        int modelColumn = this.convertColumnIndexToModel(column);
095        if (modelColumn == TurnoutTableDataModel.SENSOR1COL || modelColumn == TurnoutTableDataModel.SENSOR2COL) {
096            return getEditor(row, modelColumn);
097        } else {
098            return super.getCellEditor(row, column);
099        }
100    }
101
102    TableCellRenderer getRenderer(int row, int column) {
103        TableCellRenderer retval;
104        Turnout t = (Turnout) getModel().getValueAt(row, TurnoutTableDataModel.SYSNAMECOL);
105        java.util.Objects.requireNonNull(t, "SYSNAMECOL column content must be nonnull");
106        switch (column) {
107            case TurnoutTableDataModel.SENSOR1COL:
108                retval = rendererMapSensor1.get(t);
109                break;
110            case TurnoutTableDataModel.SENSOR2COL:
111                retval = rendererMapSensor2.get(t);
112                break;
113            default:
114                return null;
115        }
116
117        if (retval == null) {
118            if (column == TurnoutTableDataModel.SENSOR1COL) {
119                loadRenderEditMaps(rendererMapSensor1, editorMapSensor1, t, t.getFirstSensor());
120            } else {
121                loadRenderEditMaps(rendererMapSensor2, editorMapSensor2, t, t.getSecondSensor());
122            }
123            retval = rendererMapSensor1.get(t);
124        }
125        log.debug("fetched for Turnout \"{}\" renderer {}", t, retval);
126        return retval;
127    }
128
129    TableCellEditor getEditor(int row, int column) {
130        TableCellEditor retval;
131        Turnout t = (Turnout) getModel().getValueAt(row, TurnoutTableDataModel.SYSNAMECOL);
132        java.util.Objects.requireNonNull(t, "SYSNAMECOL column content must be nonnull");
133        switch (column) {
134            case TurnoutTableDataModel.SENSOR1COL:
135                retval = editorMapSensor1.get(t);
136                break;
137            case TurnoutTableDataModel.SENSOR2COL:
138                retval = editorMapSensor2.get(t);
139                break;
140            default:
141                return null;
142        }
143        if (retval == null) {
144            if (column == TurnoutTableDataModel.SENSOR1COL) {
145                loadRenderEditMaps(rendererMapSensor1, editorMapSensor1, t, t.getFirstSensor());
146                retval = editorMapSensor1.get(t);
147            } else { //Must be two
148                loadRenderEditMaps(rendererMapSensor2, editorMapSensor2, t, t.getSecondSensor());
149                retval = editorMapSensor2.get(t);
150            }
151        }
152        log.debug("fetched for Turnout \"{}\" editor {}", t, retval);
153        return retval;
154    }
155
156    protected void loadRenderEditMaps(Hashtable<Turnout, TableCellRenderer> r, Hashtable<Turnout, TableCellEditor> e,
157            Turnout t, Sensor s) {
158        NamedBeanComboBox<Sensor> c = new NamedBeanComboBox<>(InstanceManager.getDefault(SensorManager.class), s, NamedBean.DisplayOptions.DISPLAYNAME);
159        c.setAllowNull(true);
160        JComboBoxUtil.setupComboBoxMaxRows(c);
161
162        BeanBoxRenderer renderer = new BeanBoxRenderer();
163        renderer.setSelectedItem(s);
164        r.put(t, renderer);
165
166        TableCellEditor editor = new BeanComboBoxEditor(c);
167        e.put(t, editor);
168        log.debug("initialize for Turnout \"{}\" Sensor \"{}\"", t, s);
169    }
170
171    static class BeanBoxRenderer extends NamedBeanComboBox<Sensor> implements TableCellRenderer {
172
173        private final Border existingBorder;
174        private final Border errorBorder = BorderFactory.createLineBorder(java.awt.Color.RED);
175
176        public BeanBoxRenderer() {
177            super(InstanceManager.getDefault(SensorManager.class));
178            setAllowNull(true);
179            existingBorder = this.getBorder();
180        }
181
182        @Override
183        public Component getTableCellRendererComponent(JTable table, Object value,
184                boolean isSelected, boolean hasFocus, int row, int column) {
185            if (!(SystemType.isMacOSX() && UIManager.getLookAndFeel().isNativeLookAndFeel())) {
186                if (isSelected) {
187                    setForeground(table.getSelectionForeground());
188                    super.setBackground(table.getSelectionBackground());
189                } else {
190                    setForeground(table.getForeground());
191                    setBackground(table.getBackground());
192                }
193            }
194            
195            int tableCol = table.convertColumnIndexToModel(column);
196            int tableRow = table.convertRowIndexToModel(row);
197            
198            Turnout t = (Turnout)table.getModel().getValueAt(tableRow, TurnoutTableDataModel.SYSNAMECOL);
199            if ( t == null ) {
200                return this;
201            }
202            if (value instanceof Sensor) {
203                setSelectedItem(value);
204                if (( tableCol == TurnoutTableDataModel.SENSOR1COL ) && 
205                    (t.getFeedbackMode() != Turnout.ONESENSOR && t.getFeedbackMode() != Turnout.TWOSENSOR )) {
206                    setBorder(errorBorder);
207                } else if ( tableCol == TurnoutTableDataModel.SENSOR2COL && t.getFeedbackMode() != Turnout.TWOSENSOR ) {
208                    setBorder(errorBorder);
209                } else {
210                    setBorder(existingBorder);
211                }
212            } else {
213                setSelectedItem(null);
214                if (( tableCol == TurnoutTableDataModel.SENSOR1COL ) && 
215                        (t.getFeedbackMode() == Turnout.ONESENSOR || t.getFeedbackMode() == Turnout.TWOSENSOR )) {
216                    setBorder(errorBorder);
217                } else if ( tableCol == TurnoutTableDataModel.SENSOR2COL && t.getFeedbackMode() == Turnout.TWOSENSOR ) {
218                    setBorder(errorBorder);
219                } else {
220                    setBorder(existingBorder);
221                }
222            }
223            return this;
224        }
225    }
226
227    static class BeanComboBoxEditor extends DefaultCellEditor {
228
229        public BeanComboBoxEditor(NamedBeanComboBox<Sensor> beanBox) {
230            super(beanBox);
231        }
232    }
233
234
235private final static Logger log = LoggerFactory.getLogger(TurnoutTableJTable.class);
236
237}