001package jmri.jmrit.beantable.signalmast;
002
003import java.awt.BorderLayout;
004import java.awt.Color;
005import java.awt.event.ActionEvent;
006import java.beans.PropertyChangeListener;
007import java.util.ArrayList;
008import java.util.HashSet;
009import java.util.Set;
010
011import javax.swing.BorderFactory;
012import javax.swing.BoxLayout;
013import javax.swing.JButton;
014import javax.swing.JLabel;
015import javax.swing.JPanel;
016import javax.swing.JScrollPane;
017import javax.swing.JTable;
018import javax.swing.JTextField;
019import javax.swing.SortOrder;
020import javax.swing.border.TitledBorder;
021import javax.swing.table.AbstractTableModel;
022import javax.swing.table.TableCellEditor;
023import javax.swing.table.TableRowSorter;
024
025import jmri.InstanceManager;
026import jmri.JmriException;
027import jmri.SignalMast;
028import jmri.SignalMastManager;
029import jmri.implementation.SignalMastRepeater;
030import jmri.managers.DefaultSignalMastManager;
031import jmri.swing.NamedBeanComboBox;
032import jmri.swing.RowSorterUtil;
033import jmri.util.JmriJFrame;
034import jmri.util.swing.JComboBoxUtil;
035import jmri.util.swing.JmriPanel;
036import jmri.util.swing.JmriJOptionPane;
037import jmri.util.table.ButtonEditor;
038import jmri.util.table.ButtonRenderer;
039
040/**
041 * Frame for Signal Mast Add / Edit Panel
042 *
043 * @author Kevin Dickerson Copyright (C) 2011
044 */
045public class SignalMastRepeaterPanel extends JmriPanel implements PropertyChangeListener {
046
047    final DefaultSignalMastManager dsmm;
048
049    SignalMastRepeaterModel _RepeaterModel;
050    JScrollPane _SignalAppearanceScrollPane;
051    NamedBeanComboBox<SignalMast> _MasterBox;
052    NamedBeanComboBox<SignalMast> _SlaveBox;
053    JButton _addRepeater;
054
055    public SignalMastRepeaterPanel() {
056        super();
057        dsmm = (DefaultSignalMastManager) InstanceManager.getDefault(SignalMastManager.class);
058        init();
059    }
060
061    final void init() {
062        dsmm.addPropertyChangeListener(this);
063
064        setLayout(new BorderLayout());
065
066        JPanel header = new JPanel();
067        header.setLayout(new BoxLayout(header, BoxLayout.Y_AXIS));
068
069        JPanel sourcePanel = new JPanel();
070
071        header.add(sourcePanel);
072        add(header, BorderLayout.NORTH);
073
074        _RepeaterModel = new SignalMastRepeaterModel();
075        JTable _RepeaterTable = new JTable(_RepeaterModel);
076
077        TableRowSorter<SignalMastRepeaterModel> sorter = new TableRowSorter<>(_RepeaterModel); // leave default sorting
078        RowSorterUtil.setSortOrder(sorter, SignalMastRepeaterModel.DIR_COLUMN, SortOrder.ASCENDING);
079        _RepeaterTable.setRowSorter(sorter);
080
081        _RepeaterTable.setRowSelectionAllowed(false);
082        _RepeaterTable.setPreferredScrollableViewportSize(new java.awt.Dimension(526, 120));
083        _RepeaterModel.configureTable(_RepeaterTable);
084        _SignalAppearanceScrollPane = new JScrollPane(_RepeaterTable);
085        _RepeaterModel.fireTableDataChanged();
086        add(_SignalAppearanceScrollPane, BorderLayout.CENTER);
087
088        JPanel footer = new JPanel();
089        updateDetails();
090
091        _MasterBox = new NamedBeanComboBox<>(dsmm);
092        _MasterBox.addActionListener((ActionEvent e) -> {
093            setSlaveBoxLists();
094        });
095        JComboBoxUtil.setupComboBoxMaxRows(_MasterBox);
096
097        _SlaveBox = new NamedBeanComboBox<>(dsmm);
098        JComboBoxUtil.setupComboBoxMaxRows(_SlaveBox);
099        _SlaveBox.setEnabled(false);
100        footer.add(new JLabel(Bundle.getMessage("Master") + " : "));
101        footer.add(_MasterBox);
102        footer.add(new JLabel(Bundle.getMessage("Slave") + " : "));
103        footer.add(_SlaveBox);
104        _addRepeater = new JButton(Bundle.getMessage("ButtonAddText"));
105        _addRepeater.setEnabled(false);
106        _addRepeater.addActionListener((ActionEvent e) -> {
107            SignalMastRepeater rp = new SignalMastRepeater(_MasterBox.getSelectedItem(), _SlaveBox.getSelectedItem());
108            try {
109                dsmm.addRepeater(rp);
110            } catch (JmriException ex) {
111                String error = java.text.MessageFormat.format(Bundle.getMessage("MessageAddFailed"),
112                    new Object[]{_MasterBox.getSelectedItemDisplayName(), _SlaveBox.getSelectedItemDisplayName()});
113                log.error("Failed to add Repeater. {} {}", error, ex.getMessage());
114                JmriJOptionPane.showMessageDialog(this, error,
115                    Bundle.getMessage("TitleAddFailed"), JmriJOptionPane.ERROR_MESSAGE);
116            }
117        });
118        footer.add(_addRepeater);
119
120        TitledBorder border = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black));
121        border.setTitle(Bundle.getMessage("AddRepeater"));
122        footer.setBorder(border);
123
124        add(footer, BorderLayout.SOUTH);
125    }
126
127    void setSlaveBoxLists() {
128        SignalMast masterMast = _MasterBox.getSelectedItem();
129        if (masterMast == null) {
130            _SlaveBox.setEnabled(false);
131            _addRepeater.setEnabled(false);
132            return;
133        }
134        java.util.Iterator<SignalMast> iter
135                = dsmm.getNamedBeanSet().iterator();
136
137        // don't return an element if there are not sensors to include
138        if (!iter.hasNext()) {
139            return;
140        }
141        Set<SignalMast> excludedSignalMasts = new HashSet<>();
142        while (iter.hasNext()) {
143            SignalMast s = iter.next();
144            if (s.getAppearanceMap() != masterMast.getAppearanceMap()) {
145                excludedSignalMasts.add(s);
146            } else if (s == masterMast) {
147                excludedSignalMasts.add(s);
148            }
149        }
150        _SlaveBox.setExcludedItems(excludedSignalMasts);
151        if (excludedSignalMasts.size() == dsmm.getNamedBeanSet().size()) {
152            _SlaveBox.setEnabled(false);
153            _addRepeater.setEnabled(false);
154        } else {
155            _SlaveBox.setEnabled(true);
156            _addRepeater.setEnabled(true);
157        }
158    }
159
160    JmriJFrame signalMastLogicFrame = null;
161    JLabel sourceLabel = new JLabel();
162
163    @Override
164    public void propertyChange(java.beans.PropertyChangeEvent e) {
165    }
166
167    private ArrayList<SignalMastRepeater> _signalMastRepeaterList;
168
169    private void updateDetails() {
170        _signalMastRepeaterList = new ArrayList<>(dsmm.getRepeaterList());
171        _RepeaterModel.fireTableDataChanged();//updateSignalMastLogic(old, sml);
172    }
173
174    public class SignalMastRepeaterModel extends AbstractTableModel implements PropertyChangeListener {
175
176        SignalMastRepeaterModel() {
177            super();
178            init();
179        }
180
181        final void init(){
182            dsmm.addPropertyChangeListener(this);
183        }
184
185        @Override
186        public Class<?> getColumnClass(int c) {
187            switch (c) {
188                case DIR_COLUMN:
189                case DEL_COLUMN:
190                    return JButton.class;
191                case ENABLE_COLUMN:
192                    return Boolean.class;
193                default:
194                    return String.class;
195            }
196        }
197
198        public void configureTable(JTable table) {
199            // allow reordering of the columns
200            table.getTableHeader().setReorderingAllowed(true);
201
202            // have to shut off autoResizeMode to get horizontal scroll to work (JavaSwing p 541)
203            table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
204
205            // resize columns as requested
206            for (int i = 0; i < table.getColumnCount(); i++) {
207                int width = getPreferredWidth(i);
208                table.getColumnModel().getColumn(i).setPreferredWidth(width);
209            }
210            table.sizeColumnsToFit(-1);
211
212            configEditColumn(table);
213
214        }
215
216        @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES",
217                                justification="better to keep cases in column order rather than to combine")
218        public int getPreferredWidth(int col) {
219            switch (col) {
220                case ENABLE_COLUMN:
221                case DIR_COLUMN:
222                    return new JTextField(5).getPreferredSize().width;
223                case SLAVE_COLUMN:
224                    return new JTextField(15).getPreferredSize().width;
225                case MASTER_COLUMN:
226                    return new JTextField(15).getPreferredSize().width;
227                case DEL_COLUMN: // not actually used due to the configureTable, setColumnToHoldButton, configureButton
228                    return new JTextField(22).getPreferredSize().width;
229                default:
230                    log.warn("Unexpected column in getPreferredWidth: {}", col);
231                    return new JTextField(8).getPreferredSize().width;
232            }
233        }
234
235        @Override
236        public String getColumnName(int col) {
237            switch (col) {
238                case MASTER_COLUMN:
239                    return Bundle.getMessage("ColumnMaster");
240                case DIR_COLUMN:
241                    return Bundle.getMessage("ColumnDir");
242                case SLAVE_COLUMN:
243                    return Bundle.getMessage("ColumnSlave");
244                case ENABLE_COLUMN:
245                    return Bundle.getMessage("ColumnHeadEnabled");
246                case DEL_COLUMN:
247                default:
248                    return "";
249            }
250        }
251
252        public void dispose() {
253
254        }
255
256        @Override
257        public void propertyChange(java.beans.PropertyChangeEvent e) {
258            if (e.getPropertyName().equals("repeaterlength")) {
259                updateDetails();
260            }
261        }
262
263        protected void configEditColumn(JTable table) {
264            // have the delete column hold a button
265            /*AbstractTableAction.Bundle.getMessage("EditDelete")*/
266
267            JButton b = new JButton("< >");
268            b.putClientProperty("JComponent.sizeVariant", "small");
269            b.putClientProperty("JButton.buttonType", "square");
270
271            setColumnToHoldButton(table, DIR_COLUMN,
272                    b);
273            setColumnToHoldButton(table, DEL_COLUMN,
274                    new JButton(Bundle.getMessage("ButtonDelete")));
275        }
276
277        protected void setColumnToHoldButton(JTable table, int column, JButton sample) {
278            //TableColumnModel tcm = table.getColumnModel();
279            // install a button renderer & editor
280            ButtonRenderer buttonRenderer = new ButtonRenderer();
281            table.setDefaultRenderer(JButton.class, buttonRenderer);
282            TableCellEditor buttonEditor = new ButtonEditor(new JButton());
283            table.setDefaultEditor(JButton.class, buttonEditor);
284            // ensure the table rows, columns have enough room for buttons
285            table.setRowHeight(sample.getPreferredSize().height);
286            table.getColumnModel().getColumn(column)
287                    .setPreferredWidth((sample.getPreferredSize().width) + 4);
288        }
289
290        @Override
291        public int getColumnCount() {
292            return 5;
293        }
294
295        @Override
296        public boolean isCellEditable(int r, int c) {
297            switch (c) {
298                case DEL_COLUMN:
299                case ENABLE_COLUMN:
300                case DIR_COLUMN:
301                    return true;
302                default:
303                    return false;
304            }
305        }
306
307        protected void deleteRepeater(int r) {
308            dsmm.removeRepeater(_signalMastRepeaterList.get(r));
309        }
310
311        public static final int MASTER_COLUMN = 0;
312        public static final int DIR_COLUMN = 1;
313        public static final int SLAVE_COLUMN = 2;
314        public static final int ENABLE_COLUMN = 3;
315        public static final int DEL_COLUMN = 4;
316
317        public void setSetToState(String x) {
318        }
319
320        @Override
321        public int getRowCount() {
322            return ( _signalMastRepeaterList == null ? 0 : _signalMastRepeaterList.size() );
323        }
324
325        @Override
326        public Object getValueAt(int r, int c) {
327            if (r >= _signalMastRepeaterList.size()) {
328                log.debug("row is greater than turnout list size");
329                return null;
330            }
331            switch (c) {
332                case MASTER_COLUMN:
333                    return _signalMastRepeaterList.get(r).getMasterMastName();
334                case DIR_COLUMN:  // slot number
335                    switch (_signalMastRepeaterList.get(r).getDirection()) {
336                        case SignalMastRepeater.BOTHWAY:
337                            return "< >";
338                        case SignalMastRepeater.MASTERTOSLAVE:
339                            return " > ";
340                        case SignalMastRepeater.SLAVETOMASTER:
341                            return " < ";
342                        default:
343                            return "< >";
344                    }
345                case SLAVE_COLUMN:
346                    return _signalMastRepeaterList.get(r).getSlaveMastName();
347                case ENABLE_COLUMN:
348                    return _signalMastRepeaterList.get(r).getEnabled();
349                case DEL_COLUMN:
350                    return Bundle.getMessage("ButtonDelete");
351                default:
352                    return null;
353            }
354        }
355
356        @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "DB_DUPLICATE_SWITCH_CLAUSES",
357                                justification="better to keep cases in column order rather than to combine")
358        @Override
359        public void setValueAt(Object type, int r, int c) {
360            switch (c) {
361                case DIR_COLUMN:
362                    switch (_signalMastRepeaterList.get(r).getDirection()) {
363                        case SignalMastRepeater.BOTHWAY:
364                            _signalMastRepeaterList.get(r).setDirection(SignalMastRepeater.MASTERTOSLAVE);
365                            break;
366                        case SignalMastRepeater.MASTERTOSLAVE:
367                            _signalMastRepeaterList.get(r).setDirection(SignalMastRepeater.SLAVETOMASTER);
368                            break;
369                        case SignalMastRepeater.SLAVETOMASTER:
370                            _signalMastRepeaterList.get(r).setDirection(SignalMastRepeater.BOTHWAY);
371                            break;
372                        default:
373                            _signalMastRepeaterList.get(r).setDirection(SignalMastRepeater.BOTHWAY);
374                            break;
375                    }
376                    _RepeaterModel.fireTableDataChanged();
377                    break;
378                case DEL_COLUMN:
379                    deleteRepeater(r);
380                    break;
381                case ENABLE_COLUMN:
382                    boolean b = ((Boolean) type);
383                    _signalMastRepeaterList.get(r).setEnabled(b);
384                    break;
385                default:
386                    break;
387            }
388        }
389    }
390
391    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SignalMastRepeaterPanel.class);
392
393}