001package jmri.jmrit.beantable.oblock;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005
006import javax.annotation.Nonnull;
007import javax.swing.*;
008import javax.swing.table.AbstractTableModel;
009
010import jmri.*;
011import jmri.jmrit.beantable.RowComboBoxPanel;
012import jmri.jmrit.logix.OPath;
013import jmri.util.gui.GuiLafPreferencesManager;
014import jmri.util.swing.JmriJOptionPane;
015
016/**
017 * GUI to define Path-Turnout combos for OBlocks.
018 * <p>
019 * Can be used with two interfaces:
020 * <ul>
021 *     <li>original "desktop" InternalFrames (parent class TableFrames, an extended JmriJFrame)
022 *     <li>JMRI standard Tabbed tables (parent class JPanel)
023 * </ul>
024 * The _tabbed field decides, it is set in prefs (restart required).
025 * <hr>
026 * This file is part of JMRI.
027 * <p>
028 * JMRI is free software; you can redistribute it and/or modify it under the
029 * terms of version 2 of the GNU General Public License as published by the Free
030 * Software Foundation. See the "COPYING" file for a copy of this license.
031 * <p>
032 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
033 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
034 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
035 *
036 * @author Pete Cressman (C) 2010
037 * @author Egbert Broerse (C) 2020
038 */
039public class PathTurnoutTableModel extends AbstractTableModel implements PropertyChangeListener {
040
041    public static final int TURNOUT_NAME_COL = 0;
042    public static final int STATE_COL = 1;
043    public static final int DELETE_COL = 2;
044    public static final int NUMCOLS = 3;
045    // also
046    private static final String SET_CLOSED = jmri.InstanceManager.turnoutManagerInstance().getClosedText();
047    private static final String SET_THROWN = jmri.InstanceManager.turnoutManagerInstance().getThrownText();
048
049    private OPath _path;
050    private final String[] tempRow = new String[NUMCOLS];
051    private TableFrames.PathTurnoutFrame _parent;
052    private final boolean _tabbed; // updated from prefs (restart required)
053
054    public PathTurnoutTableModel() {
055        super();
056        _tabbed = InstanceManager.getDefault(GuiLafPreferencesManager.class).isOblockEditTabbed();
057    }
058
059    public PathTurnoutTableModel(OPath path, @Nonnull TableFrames.PathTurnoutFrame parent) { // for _desktop
060        super();
061        _path = path;
062        _path.getBlock().addPropertyChangeListener(this);
063        _tabbed = false;
064        _parent = parent; // is used to change the title, or dispose when item is deleted
065    }
066
067    public PathTurnoutTableModel(OPath path) { // for _tabbed
068        super();
069        _path = path;
070        _path.getBlock().addPropertyChangeListener(this);
071        _tabbed = true;
072        //_tabbedParent = parent; // is -not- used to change the title, or dispose when item is deleted
073    }
074
075    public void removeListener() {
076        Block block = _path.getBlock();
077        if (block == null) {
078            return;
079        }
080        try {
081            _path.getBlock().removePropertyChangeListener(this);
082        } catch (NullPointerException npe) { // OK when block is removed
083        }
084    }
085
086    void initTempRow() {
087        for (int i = 0; i < NUMCOLS; i++) {
088            tempRow[i] = null;
089        }
090        tempRow[DELETE_COL] = Bundle.getMessage("ButtonClear");
091    }
092
093    @Override
094    public int getColumnCount() {
095        return NUMCOLS;
096    }
097
098    @Override
099    public int getRowCount() {
100        return _path.getSettings().size() + (_tabbed ? 0 : 1); // + 1 row in _desktop to create entry row
101    }
102
103    @Override
104    public String getColumnName(int col) {
105        switch (col) {
106            case TURNOUT_NAME_COL:
107                return Bundle.getMessage("LabelItemName");
108            case STATE_COL:
109                return Bundle.getMessage("TurnoutState"); // state
110            default:
111                // fall through
112                break;
113        }
114        return "";
115    }
116
117    @Override
118    public Object getValueAt(int rowIndex, int columnIndex) {
119        if (_path.getSettings().size() == rowIndex) { // this must be tempRow
120            return tempRow[columnIndex];
121        }
122        // some error checking
123        if (rowIndex >= _path.getSettings().size()) {
124            log.debug("row greater than bean list size");
125            return "Error bean list";
126        }
127        BeanSetting bs = _path.getSettings().get(rowIndex);
128        // some error checking
129        if (bs == null) {
130            log.debug("bean is null");
131            return "Error no bean";
132        }
133        switch (columnIndex) {
134            case TURNOUT_NAME_COL:
135                return bs.getBeanName();
136            case STATE_COL:
137                switch (bs.getSetting()) {
138                    case Turnout.CLOSED:
139                        return SET_CLOSED;
140                    case Turnout.THROWN:
141                        return SET_THROWN;
142                    default:
143                        return "";
144                }
145            case DELETE_COL:
146                return Bundle.getMessage("ButtonDelete");
147            default:
148                // fall through
149                break;
150        }
151        return "";
152    }
153
154    @Override
155    public void setValueAt(Object value, int row, int col) {
156        if (_path.getSettings().size() == row) { // last entry row, only possible in _desktop interface
157            switch (col) {
158                case TURNOUT_NAME_COL:
159                    tempRow[TURNOUT_NAME_COL] = (String) value;
160                    if (tempRow[STATE_COL] == null) {
161                        return;
162                    }
163                    break;
164                case STATE_COL:
165                    tempRow[STATE_COL] = (String) value;
166                    if (tempRow[TURNOUT_NAME_COL] == null) {
167                        return;
168                    }
169                    break;
170                case DELETE_COL: // called "Clear"
171                    initTempRow();
172                    fireTableRowsUpdated(row, row);
173                    return;
174                default:
175                    // fall through
176                    break;
177            }
178            Turnout t = InstanceManager.turnoutManagerInstance().getTurnout(tempRow[TURNOUT_NAME_COL]);
179            if (t != null) {
180                int s;
181                if (tempRow[STATE_COL].equals(SET_CLOSED)) {
182                    s = Turnout.CLOSED;
183                } else if (tempRow[STATE_COL].equals(SET_THROWN)) {
184                    s = Turnout.THROWN;
185                } else {
186                    JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("TurnoutMustBeSet", SET_CLOSED, SET_THROWN),
187                            Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE);
188                    return;
189                }
190                BeanSetting bs = new BeanSetting(t, tempRow[TURNOUT_NAME_COL], s);
191                _path.addSetting(bs);
192                fireTableRowsUpdated(row, row);
193            } else {
194                JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("NoSuchTurnout", tempRow[TURNOUT_NAME_COL]),
195                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE);
196                return;
197            }
198            if (!_tabbed) {
199                initTempRow();
200            }
201            return;
202        }
203
204        BeanSetting bs = _path.getSettings().get(row);
205        Turnout t;
206        switch (col) {
207            case TURNOUT_NAME_COL:
208                t = InstanceManager.turnoutManagerInstance().getTurnout((String) value);
209                if (t != null) {
210                    if (!t.equals(bs.getBean())) {
211                        _path.removeSetting(bs);
212                        _path.addSetting(new BeanSetting(t, (String) value, bs.getSetting()));
213                    }
214                } else {
215                    JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("NoSuchTurnout", (String) value),
216                            Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE);
217                    return;
218                }
219                fireTableDataChanged();
220                break;
221            case STATE_COL:
222                String setting = (String) value;
223                if (setting.equals(SET_CLOSED)) {
224                    //bs.setSetting(Turnout.CLOSED);  - This was the form before BeanSetting was returned to Immutable
225                    _path.getSettings().set(row, new BeanSetting(bs.getBean(), bs.getBeanName(), Turnout.CLOSED));
226                } else if (setting.equals(SET_THROWN)) {
227                    //bs.setSetting(Turnout.THROWN); 
228                    _path.getSettings().set(row, new BeanSetting(bs.getBean(), bs.getBeanName(), Turnout.THROWN));
229                } else {
230                    JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("TurnoutMustBeSet", SET_CLOSED, SET_THROWN),
231                            Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE);
232                    return;
233                }
234                fireTableRowsUpdated(row, row);
235                break;
236            case DELETE_COL:
237                if (JmriJOptionPane.showConfirmDialog(null, Bundle.getMessage("DeleteTurnoutConfirm"),
238                        Bundle.getMessage("WarningTitle"),
239                        JmriJOptionPane.YES_NO_OPTION, JmriJOptionPane.QUESTION_MESSAGE)
240                        == JmriJOptionPane.YES_OPTION) {
241                    _path.removeSetting(bs);
242                    fireTableDataChanged();
243                }
244                break;
245            default:
246                log.warn("Unhandled col: {}", col);
247                break;
248        }
249    }
250
251    @Override
252    public boolean isCellEditable(int row, int col) {
253        return true;
254    }
255
256    @Override
257    public Class<?> getColumnClass(int col) {
258        switch (col) {
259            case DELETE_COL:
260                return JButton.class;
261            case STATE_COL:
262                return StateComboBoxPanel.class;
263            case TURNOUT_NAME_COL:
264            default:
265                return String.class;
266        }
267    }
268
269    /**
270     * Provide a table cell renderer looking like a JComboBox as an
271     * editor/renderer for the turnout tables.
272     * <p>
273     * This is a lightweight version of the
274     * {@link jmri.jmrit.beantable.RowComboBoxPanel} RowComboBox cell editor
275     * class, some of the hashtables not needed here since we only need
276     * identical options for all rows in a column.
277     *
278     * see jmri.jmrit.signalling.SignallingPanel.SignalMastModel.AspectComboBoxPanel for a full application with
279     * row specific comboBox choices.
280     */
281    public class StateComboBoxPanel extends RowComboBoxPanel {
282
283        @Override
284        protected final void eventEditorMousePressed() {
285            this.editor.add(getEditorBox(table.convertRowIndexToModel(this.currentRow))); // add editorBox to JPanel
286            this.editor.revalidate();
287            SwingUtilities.invokeLater(this.comboBoxFocusRequester);
288            log.debug("eventEditorMousePressed in row: {})", this.currentRow);  // NOI18N
289        }
290
291        /**
292         * Call the method in the surrounding method for the
293         * SignalHeadTable.
294         *
295         * @param row the user clicked on in the table
296         * @return an appropriate combobox for this signal head
297         */
298        @Override
299        protected JComboBox<String> getEditorBox(int row) {
300            return getStateEditorBox(row);
301        }
302
303    }
304    // end of methods to display STATE_COLUMN ComboBox
305
306    /**
307     * Provide a static JComboBox element to display inside the JPanel
308     * CellEditor. When not yet present, create, store and return a new one.
309     *
310     * @param row Index number (in TableDataModel)
311     * @return A combobox containing the valid aspect names for this mast
312     */
313    JComboBox<String> getStateEditorBox(int row) {
314        // create dummy comboBox, override in extended classes for each bean
315        JComboBox<String> editCombo = new JComboBox<>();
316        editCombo.addItem(SET_THROWN);
317        editCombo.addItem(SET_CLOSED);
318        editCombo.putClientProperty("JComponent.sizeVariant", "small");
319        editCombo.putClientProperty("JComboBox.buttonType", "square");
320        return editCombo;
321    }
322
323    /**
324     * Customize the Turnout State column to show an appropriate ComboBox of
325     * available options.
326     *
327     * @param table a JTable of beans
328     */
329    protected void configTurnoutStateColumn(JTable table) {
330        // have the state column hold a JPanel with a JComboBox for States
331        table.setDefaultEditor(StateComboBoxPanel.class, new StateComboBoxPanel());
332        table.setDefaultRenderer(StateComboBoxPanel.class, new StateComboBoxPanel()); // use same class as renderer
333        // Set more things?
334    }
335
336    public int getPreferredWidth(int col) {
337        switch (col) {
338            case TURNOUT_NAME_COL:
339                return new JTextField(20).getPreferredSize().width;
340            case STATE_COL:
341                return new JTextField(10).getPreferredSize().width;
342            case DELETE_COL:
343                return new JButton("DELETE").getPreferredSize().width;
344            default:
345                // fall through
346                break;
347        }
348        return 5;
349    }
350
351    @Override
352    public void propertyChange(PropertyChangeEvent e) {
353        if (_path.getBlock().equals(e.getSource())) {
354            String property = e.getPropertyName();
355            if (property.equals("pathCount")) {
356                fireTableDataChanged();
357                if (_path.equals(e.getOldValue())) { // path was deleted
358                    removeListener();
359                    if (!_tabbed) {
360                        _parent.dispose();
361                    }
362                }
363            } else if (property.equals("pathName")) {
364                String title = Bundle.getMessage("TitlePathTurnoutTable", _path.getBlock().getDisplayName(), e.getOldValue());
365                if (!_tabbed && _parent.getTitle().equals(title)) {
366                    title = Bundle.getMessage("TitlePathTurnoutTable", _path.getBlock().getDisplayName(), e.getNewValue());
367                }
368                if (!_tabbed) {
369                    _parent.setTitle(title);
370                }
371            }
372        }
373    }
374
375    void dispose() {
376        InstanceManager.turnoutManagerInstance().removePropertyChangeListener(this);
377    }
378
379    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(PathTurnoutTableModel.class);
380
381}