001package jmri.jmrit.beantable.oblock;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.text.ParseException;
006import java.util.ArrayList;
007import java.util.List;
008import java.util.SortedSet;
009import java.util.TreeSet;
010import javax.annotation.Nonnull;
011import javax.swing.*;
012
013import jmri.*;
014import jmri.implementation.SignalSpeedMap;
015import jmri.jmrit.beantable.RowComboBoxPanel;
016import jmri.jmrit.beantable.block.BlockCurvatureJComboBox;
017import jmri.jmrit.logix.*;
018import jmri.util.IntlUtilities;
019import jmri.util.NamedBeanComparator;
020import jmri.util.ThreadingUtil;
021import jmri.util.gui.GuiLafPreferencesManager;
022import jmri.util.swing.JmriJOptionPane;
023
024/**
025 * GUI to define OBlocks.
026 * <p>
027 * Duplicates the JTable model for BlockTableAction and adds a column for the
028 * occupancy sensor. Configured for use within an internal frame.
029 * <p>
030 * Can be used with two interfaces:
031 * <ul>
032 *     <li>original "desktop" InternalFrames (parent class TableFrames, an extended JmriJFrame)
033 *     <li>JMRI "standard" Tabbed tables (parent class JPanel)
034 * </ul>
035 * The _tabbed field decides, it is set in prefs (restart required).
036 *
037 * @author Pete Cressman (C) 2010
038 * @author Egbert Broerse (C) 2020
039 */
040public class OBlockTableModel extends jmri.jmrit.beantable.BeanTableDataModel<OBlock> {
041
042    @SuppressWarnings("hiding")     // Field has same name as a field in the super class
043    static public final int SYSNAMECOL = 0;
044    @SuppressWarnings("hiding")     // Field has same name as a field in the super class
045    static public final int USERNAMECOL = 1;
046    @SuppressWarnings("hiding")     // Field has same name as a field in the super class
047    static public final int COMMENTCOL = 2;
048    static public final int STATECOL = 3;
049    static public final int SENSORCOL = 4;
050    static public final int EDIT_COL = 5;   // Paths button
051    static public final int DELETE_COL = 6;
052    static public final int LENGTHCOL = 7;
053    static public final int UNITSCOL = 8;
054    static public final int REPORTERCOL = 9;
055    static public final int REPORT_CURRENTCOL = 10;
056    static public final int PERMISSIONCOL = 11;
057    static public final int SPEEDCOL = 12;
058    static public final int WARRANTCOL = 13;
059    static public final int ERR_SENSORCOL = 14;
060    static public final int CURVECOL = 15;
061    static public final int VALUE = 16;
062    static public final int NUMCOLS = 17;
063
064    static String ZEROS = "000000000";      // 9 bits contain the OBlock state info
065
066    java.text.DecimalFormat twoDigit = new java.text.DecimalFormat("0.00");
067
068    OBlockManager _manager;
069    private final String[] tempRow = new String[NUMCOLS];
070    private float _tempLen = 0.0f;      // mm for length col of tempRow
071    TableFrames _parent;
072    private final boolean _tabbed; // updated from prefs (restart required)
073    private boolean _isMetric = false;
074    public OBlockTableModel(@Nonnull TableFrames parent) {
075        super();
076        _parent = parent;
077        _tabbed = InstanceManager.getDefault(GuiLafPreferencesManager.class).isOblockEditTabbed();
078        if (_tabbed) {
079            _manager = InstanceManager.getDefault(OBlockManager.class); // TODO also for _desktop?
080            _manager.addPropertyChangeListener(this);
081        }
082        updateNameList();
083        if (!_tabbed) {
084            initTempRow();
085        }
086    }
087
088    /**
089     * Respond to mouse events to show/hide columns.
090     * Has public access to allow setting from OBlockTableAction OBlock Panel.
091     *
092     * @param table the table based on this model
093     */
094    public void addHeaderListener(JTable table) {
095        addMouseListenerToHeader(table);
096    }
097
098    void initTempRow() {
099        for (int i = 0; i < NUMCOLS; i++) {
100            tempRow[i] = null;
101        }
102        tempRow[LENGTHCOL] = twoDigit.format(0.0);
103        tempRow[UNITSCOL] = Bundle.getMessage("in");
104        tempRow[CURVECOL] = BlockCurvatureJComboBox.getStringFromCurvature(Block.NONE);
105        tempRow[REPORT_CURRENTCOL] = Bundle.getMessage("Current");
106        tempRow[PERMISSIONCOL] = Bundle.getMessage("Permissive");
107        tempRow[SPEEDCOL] = "";
108        tempRow[DELETE_COL] = Bundle.getMessage("ButtonClear");
109    }
110
111    @Override
112    public Manager<OBlock> getManager() {
113        _manager = InstanceManager.getDefault(OBlockManager.class);
114        return _manager;
115    }
116
117    @Override
118    public OBlock getBySystemName(@Nonnull String name) {
119        return _manager.getBySystemName(name);
120    }
121
122    @Override
123    public OBlock getByUserName(@Nonnull String name) {
124        return _manager.getByUserName(name);
125    }
126
127    @Override
128    protected String getBeanType() {
129        return "OBlock";
130    }
131
132    @Override
133    public void clickOn(OBlock t) {
134    }
135
136    protected boolean isMetric() {
137        return _isMetric;
138    }
139
140    protected void changeUnits() {
141        _isMetric = !_isMetric;
142        SortedSet<OBlock> oblockList = _manager.getNamedBeanSet();
143        for (OBlock block : oblockList) {
144           block.setMetricUnits(_isMetric);
145        }
146        fireTableDataChanged();
147    }
148
149    /**
150     * {@inheritDoc}
151     */
152    @Override
153    protected String getMasterClassName() {
154        return getClassName();
155    }
156
157    protected List<OBlock> getBeanList() {
158        TreeSet<OBlock> ts = new TreeSet<>(new NamedBeanComparator<>());
159
160        for (String s : sysNameList) {
161            ts.add(getBySystemName(s));
162        }
163        ArrayList<OBlock> list = new ArrayList<>(sysNameList.size());
164
165        list.addAll(ts);
166        return list;
167    }
168
169    @Override
170    public String getValue(String name) {
171        OBlock bl = _manager.getBySystemName(name);
172        if (bl !=null) {
173            return getValue(bl.getState());
174        }
175        return "";
176    }
177
178    static protected String getValue(int state) {
179        StringBuilder sb = new StringBuilder();
180        if ((state & OBlock.UNDETECTED) != 0) {
181            sb.append(Bundle.getMessage("Dark"));
182        }
183        if ((state & OBlock.OCCUPIED) != 0) {
184            if (sb.length() > 0) {
185                sb.append('-');
186            }
187            sb.append(Bundle.getMessage("Occupied"));
188        }
189        if ((state & OBlock.UNOCCUPIED) != 0) {
190            if (sb.length() > 0) {
191                sb.append('-');
192            }
193            sb.append(Bundle.getMessage("Unoccupied"));
194        }
195        if ((state & OBlock.INCONSISTENT) != 0) {
196            if (sb.length() > 0) {
197                sb.append('-');
198            }
199            sb.append(Bundle.getMessage("BeanStateInconsistent"));
200        }
201        if ((state & OBlock.UNKNOWN) != 0) {
202            if (sb.length() > 0) {
203                sb.append('-');
204            }
205            sb.append(Bundle.getMessage("BeanStateUnknown"));
206        }
207        if ((state & OBlock.ALLOCATED) != 0) {
208            if (sb.length() > 0) {
209                sb.append('-');
210            }
211            sb.append(Bundle.getMessage("Allocated"));
212        }
213        if ((state & OBlock.RUNNING) != 0) {
214            if (sb.length() > 0) {
215                sb.append('-');
216            }
217            sb.append(Bundle.getMessage("Running"));
218        }
219        if ((state & OBlock.OUT_OF_SERVICE) != 0) {
220            if (sb.length() > 0) {
221                sb.append('-');
222            }
223            sb.append(Bundle.getMessage("OutOfService"));
224        }
225        if ((state & OBlock.TRACK_ERROR) != 0) {
226            if (sb.length() > 0) {
227                sb.append('-');
228            }
229            sb.append(Bundle.getMessage("TrackError"));
230        }
231        if (sb.length() == 0) {
232            sb.append(Bundle.getMessage("UnDefined"));
233        }
234        return sb.toString();
235    }
236
237    @Override
238    public int getColumnCount() {
239        return NUMCOLS;
240    }
241
242    @Override
243    public int getRowCount() {
244        return super.getRowCount() + (_tabbed ? 0 : 1); // + 1 row in _desktop to create entry row
245    }
246
247    @Override
248    public Object getValueAt(int row, int col) {
249        if (row > sysNameList.size()) {
250            return "";
251        }
252        OBlock b = null;
253        if ((_tabbed && row <= sysNameList.size()) || (!_tabbed && row < sysNameList.size())) {
254            String name = sysNameList.get(row);
255            b = _manager.getBySystemName(name);
256        }
257        switch (col) {
258            case SYSNAMECOL:
259                if (b != null) {
260                    return b.getSystemName();
261                }
262                return tempRow[col]; // this must be tempRow
263            case USERNAMECOL:
264                if (b != null) {
265                    return b.getUserName();
266                }
267                return tempRow[col];
268            case COMMENTCOL:
269                if (b != null) {
270                    return b.getComment();
271                }
272                return tempRow[col];
273            case STATECOL:
274                if (b != null) {
275                    int state = b.getState();
276                    int num = Integer.numberOfLeadingZeros(state) - 23;
277                    if (num >= 0) {
278                        return ZEROS.substring(0, num) + Integer.toBinaryString(state);
279                    }
280                }
281                return ZEROS;
282            case SENSORCOL:
283                if (b != null) {
284                    Sensor s = b.getSensor();
285                    if (s == null) {
286                        return "";
287                    }
288                    return s.getDisplayName();
289                }
290                return tempRow[col];
291            case LENGTHCOL:
292                if (b != null) {
293                    if (b.isMetric()) {
294                        return (twoDigit.format(b.getLengthCm()));
295                    }
296                    return (twoDigit.format(b.getLengthIn()));
297                }
298                if (tempRow[UNITSCOL].equals(Bundle.getMessage("cm"))) {
299                    return (twoDigit.format(_tempLen/10));
300                }
301                return (twoDigit.format(_tempLen/25.4f));
302            case UNITSCOL:
303                if (b != null) {
304                    if (log.isDebugEnabled()) {
305                        log.debug("getValueAt: row= {}, col= {}, \"{}\" isMetric= {}", row, col, b.getDisplayName(), b.isMetric());
306                    }
307                    return b.isMetric();
308                }
309                if (log.isDebugEnabled()) {
310                    log.debug("getValueAt: row= {}, col= {}, isMetric= {}", row, col, tempRow[UNITSCOL].equals(Bundle.getMessage("cm")));
311                }
312                return tempRow[UNITSCOL].equals(Bundle.getMessage("cm"));
313            case CURVECOL:
314                if (b != null) {
315                    return BlockCurvatureJComboBox.getStringFromCurvature(b.getCurvature());
316                }
317                return tempRow[col];
318            case ERR_SENSORCOL:
319                if (b != null) {
320                    Sensor s = b.getErrorSensor();
321                    if (s == null) {
322                        return "";
323                    }
324                    return s.getDisplayName();
325                }
326                return tempRow[col];
327            case REPORTERCOL:
328                if (b != null) {
329                    Reporter r = b.getReporter();
330                    if (r == null) {
331                        return "";
332                    }
333                    return r.getDisplayName();
334                }
335                return tempRow[col];
336            case REPORT_CURRENTCOL:
337                if (b != null) {
338                    if (b.getReporter() != null) {
339                        return b.isReportingCurrent();
340                    }
341                    return "";
342                }
343                return tempRow[REPORT_CURRENTCOL].equals(Bundle.getMessage("Current"));
344            case PERMISSIONCOL:
345                if (b != null) {
346                    return b.getPermissiveWorking();
347                }
348                return tempRow[PERMISSIONCOL].equals(Bundle.getMessage("Permissive"));
349            case SPEEDCOL:
350                if (b != null) {
351                    return b.getBlockSpeed();
352                }
353                return tempRow[col];
354            case WARRANTCOL:
355                if (b != null) {
356                    Warrant w = b.getWarrant();
357                    if (w != null) {
358                        return w.getDisplayName();
359                    }
360                }
361                return tempRow[col];
362            case VALUE:
363                if (b != null) {
364                    Object obj = b.getValue();
365                    if (obj != null) {
366                        return obj;
367                    } else if ((b.getState() & OBlock.OCCUPIED) != 0) {
368                        return Bundle.getMessage("BlockUnknown");
369                    } else {
370                        return null;
371                    }
372                }
373                return tempRow[col];
374            case EDIT_COL:
375                if (b != null) {
376                    if (_tabbed) {
377                        return Bundle.getMessage("ButtonEdit");
378                    } else {
379                        return Bundle.getMessage("ButtonEditPath");
380                    }
381                }
382                return "";
383            case DELETE_COL:
384                if (b != null) {
385                    return Bundle.getMessage("ButtonDelete");
386                }
387                return Bundle.getMessage("ButtonClear");
388            default:
389                // fall through
390                break;
391        }
392        return super.getValueAt(row, col);
393    }
394
395    @Override
396    public void setValueAt(Object value, int row, int col) {
397        log.debug("setValueAt: row= {}, col= {}, value= {}", row, col, value);
398        if (!_tabbed && (super.getRowCount() == row)) { // editing tempRow
399            switch (col) {
400                case SYSNAMECOL:
401                    if (!_manager.isValidSystemNameFormat((String) value)) {
402                        JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("BadNameOBlock"),
403                                Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE);
404                        return;
405                    }
406                    OBlock block = _manager.createNewOBlock((String) value, tempRow[USERNAMECOL]);
407                    if (block == null) { // an OBlock with the same systemName or userName already exists
408                        block = _manager.getOBlock(tempRow[USERNAMECOL]);
409                        String name = value + " / " + tempRow[USERNAMECOL];
410                        if (block != null) {
411                            name = block.getDisplayName();
412                        } else {
413                            block = _manager.getOBlock((String)value);
414                            if (block != null) {
415                                name = block.getDisplayName();
416                            }
417                        }
418                        JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("CreateDuplBlockErr", name),
419                                Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE);
420                        return;
421                    }
422                    if (tempRow[SENSORCOL] != null) {
423                        if (!sensorExists(tempRow[SENSORCOL])) {
424                            JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("NoSuchSensorErr", tempRow[SENSORCOL]),
425                                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE);
426                        }
427                    }
428                    block.setComment(tempRow[COMMENTCOL]);
429                    float len = 0.0f;
430                    try {
431                        len = IntlUtilities.floatValue(tempRow[LENGTHCOL]);
432                    } catch (ParseException e) {
433                        JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("BadNumber", tempRow[LENGTHCOL]),
434                                Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE);
435                    }
436                    if (tempRow[UNITSCOL].equals(Bundle.getMessage("cm"))) {
437                        block.setLength(len * 10.0f);
438                        block.setMetricUnits(true);
439                    } else {
440                        block.setLength(len * 25.4f);
441                        block.setMetricUnits(false);
442                    }
443                    block.setCurvature(BlockCurvatureJComboBox.getCurvatureFromString(tempRow[CURVECOL]));
444                    block.setPermissiveWorking(tempRow[PERMISSIONCOL].equals(Bundle.getMessage("Permissive")));
445                    block.setBlockSpeedName(tempRow[SPEEDCOL]);
446
447                    if (tempRow[ERR_SENSORCOL] != null) {
448                        if (tempRow[ERR_SENSORCOL].trim().length() > 0) {
449                            if (!sensorExists(tempRow[ERR_SENSORCOL])) {
450                                JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("NoSuchSensorErr", tempRow[ERR_SENSORCOL]),
451                                        Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE);
452                            }
453                        }
454                    }
455                    if (tempRow[REPORTERCOL] != null) {
456                        Reporter rep = null;
457                        try {
458                            rep = InstanceManager.getDefault(jmri.ReporterManager.class).getReporter(tempRow[REPORTERCOL]);
459                            if (rep != null) {
460                                block.setReporter(rep);
461                                block.setReportingCurrent(tempRow[REPORT_CURRENTCOL].equals(Bundle.getMessage("Current")));
462                            }
463                        } catch (Exception ex) {
464                            log.error("No Reporter named \"{}\" found. threw exception", tempRow[REPORTERCOL], ex);
465                        }
466                        if (rep == null) {
467                            JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("NoSuchReporterErr", tempRow[REPORTERCOL]),
468                                    Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE);
469                        }
470                        block.setReporter(rep);
471                    }
472                    initTempRow();
473                    fireTableDataChanged();
474                    return;
475                case DELETE_COL:            // "Clear"
476                    initTempRow();
477                    fireTableRowsUpdated(row, row);
478                    return;
479                case LENGTHCOL:
480                    try {
481                        _tempLen = IntlUtilities.floatValue(value.toString());
482                        if (tempRow[UNITSCOL].equals(Bundle.getMessage("cm"))) {
483                            _tempLen *= 10f;
484                        } else {
485                            _tempLen *= 25.4f;
486                        }
487                    } catch (ParseException e) {
488                        JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("BadNumber", tempRow[LENGTHCOL]),
489                                Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE);
490                    }
491                    return;
492                case UNITSCOL:
493                    if (((Boolean) value)) {
494                        tempRow[UNITSCOL] = Bundle.getMessage("cm");
495                    } else {
496                        tempRow[UNITSCOL] = Bundle.getMessage("in");
497                    }
498                    fireTableRowsUpdated(row, row); // recalculates length value as displayed
499                    return;
500                case REPORT_CURRENTCOL:
501                    if ((Boolean) value) {//toggle
502                        tempRow[REPORT_CURRENTCOL] = Bundle.getMessage("Current");
503                    } else {
504                        tempRow[REPORT_CURRENTCOL] = Bundle.getMessage("Last");
505                    }
506                    return;
507                case PERMISSIONCOL:
508                    if ((Boolean) value) {//toggle
509                        tempRow[PERMISSIONCOL] = Bundle.getMessage("Permissive");
510                    } else {
511                        tempRow[PERMISSIONCOL] = Bundle.getMessage("Absolute");
512                    }
513                    return;
514                default:
515                    // fall though
516                    break;
517            }
518            tempRow[col] = (String) value;
519            return;
520        }
521
522        // Edit an existing row
523        String name = sysNameList.get(row);
524        OBlock block = _manager.getBySystemName(name);
525        if (block == null) {
526            log.error("OBlock named {} not found for OBlockTableModel", name);
527            return;
528        }
529        switch (col) {
530            case USERNAMECOL:
531                OBlock b = _manager.getOBlock((String) value);
532                if (b != null) {
533                    JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("CreateDuplBlockErr", block.getDisplayName()),
534                            Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE);
535                    return;
536                }
537                block.setUserName((String) value);
538                fireTableRowsUpdated(row, row);
539                return;
540            case COMMENTCOL:
541                block.setComment((String) value);
542                fireTableRowsUpdated(row, row);
543                return;
544            case SENSORCOL:
545                if (!block.setSensor((String) value)) {
546                    JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("NoSuchSensorErr", value),
547                            Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE);
548                }
549                fireTableRowsUpdated(row, row);
550                return;
551            case LENGTHCOL:
552                try {
553                    float len = IntlUtilities.floatValue(value.toString());
554                    if (block.isMetric()) {
555                        block.setLength(len * 10.0f);
556                    } else {
557                        block.setLength(len * 25.4f);
558                    }
559                    fireTableRowsUpdated(row, row);
560                } catch (ParseException e) {
561                    JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("BadNumber", value),
562                            Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE);
563                }
564                return;
565            case UNITSCOL:
566                block.setMetricUnits((Boolean) value);
567                fireTableRowsUpdated(row, row);
568                return;
569            case CURVECOL:
570                String cName = (String) value;
571                if (cName == null) {
572                    return;
573                }
574                block.setCurvature(BlockCurvatureJComboBox.getCurvatureFromString(cName));
575                fireTableRowsUpdated(row, row);
576                return;
577            case ERR_SENSORCOL:
578                boolean ok = false;
579                try {
580                    if (((String) value).trim().length() == 0) {
581                        block.setErrorSensor(null);
582                        ok = true;
583                    } else {
584                        ok = block.setErrorSensor((String) value);
585                        fireTableRowsUpdated(row, row);
586                    }
587                } catch (Exception ex) {
588                    log.error("getSensor({}) threw exception", value, ex);
589                }
590                if (!ok) {
591                    JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("NoSuchSensorErr", value),
592                            Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE);
593                }
594                fireTableRowsUpdated(row, row);
595                return;
596            case REPORTERCOL:
597                Reporter rep = null;
598                try {
599                    rep = InstanceManager.getDefault(jmri.ReporterManager.class).getReporter((String) value);
600                    if (rep != null) {
601                        block.setReporter(rep);
602                        fireTableRowsUpdated(row, row);
603                    }
604                } catch (Exception ex) {
605                    log.error("No Reporter named \"{}\" found. threw exception", value, ex);
606                }
607                if (rep == null) {
608                    JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("NoSuchReporterErr", tempRow[REPORTERCOL]),
609                            Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE);
610                }
611                block.setReporter(rep);
612                fireTableRowsUpdated(row, row);
613                return;
614            case REPORT_CURRENTCOL:
615                if (block.getReporter() != null) {
616                    block.setReportingCurrent((Boolean) value);
617                    fireTableRowsUpdated(row, row);
618                }
619                return;
620            case PERMISSIONCOL:
621                block.setPermissiveWorking((Boolean) value); // compare to REPORT_CURRENTCOL
622                fireTableRowsUpdated(row, row);
623                return;
624            case SPEEDCOL:
625                block.setBlockSpeedName((String) value);
626                fireTableRowsUpdated(row, row);
627                return;
628            case EDIT_COL:
629                _parent.openBlockPathPane(block.getSystemName(), null); // interface is checked in TableFrames
630                return;
631            case DELETE_COL:
632                deleteBean(block);
633                return;
634            default:
635                // fall through
636                break;
637        }
638        super.setValueAt(value, row, col);
639    }
640
641    private static boolean sensorExists(String name) {
642        Sensor sensor = InstanceManager.sensorManagerInstance().getByUserName(name);
643        if (sensor == null) {
644            sensor = InstanceManager.sensorManagerInstance().getBySystemName(name);
645        }
646        return (sensor != null);
647    }
648
649    @Override
650    public String getColumnName(int col) {
651        switch (col) {
652            case COMMENTCOL:
653                return Bundle.getMessage("Comment");
654            case STATECOL:
655                return Bundle.getMessage("ColumnState");
656            case SENSORCOL:
657                return Bundle.getMessage("BeanNameSensor");
658            case CURVECOL:
659                return Bundle.getMessage("BlockCurveColName");
660            case LENGTHCOL:
661                return Bundle.getMessage("BlockLengthColName");
662            case UNITSCOL:
663                return Bundle.getMessage("UnitsCol");
664            case EDIT_COL:
665                return Bundle.getMessage("MenuPaths");
666            case DELETE_COL:
667                return Bundle.getMessage("ColumnDelete");
668            case ERR_SENSORCOL:
669                return Bundle.getMessage("ErrorSensorCol");
670            case REPORTERCOL:
671                return Bundle.getMessage("ReporterCol");
672            case REPORT_CURRENTCOL:
673                return Bundle.getMessage("RepCurrentCol");
674            case PERMISSIONCOL:
675                return Bundle.getMessage("PermissionCol");
676            case WARRANTCOL:
677                return Bundle.getMessage("WarrantCol");
678            case VALUE:
679                return Bundle.getMessage("ValueCol");
680            case SPEEDCOL:
681                return Bundle.getMessage("SpeedCol");
682            default:
683                // fall through
684                break;
685        }
686        return super.getColumnName(col);
687    }
688
689    // Delete in row pressed, remove OBlock from manager etc. Works in both interfaces
690    void deleteBean(OBlock bean) {
691        StringBuilder sb = new StringBuilder(Bundle.getMessage("DeletePrompt", bean.getSystemName()));
692        for (PropertyChangeListener listener : bean.getPropertyChangeListeners()) {
693            if (!(listener instanceof OBlockTableModel) &&
694                    !(listener instanceof BlockPathTableModel) &&
695                    !(listener instanceof PathTurnoutTableModel) &&
696                    !(listener instanceof jmri.jmrit.picker.PickListModel) &&
697                    !(listener instanceof OBlockManager)) {
698                sb.append("\n");
699                sb.append(Bundle.getMessage("InUseBy", bean.getDisplayName(), listener.getClass().getName()));
700            }
701        }
702        int val = _parent.verifyWarning(sb.toString());
703        if (val == 2) {
704            return;  // return without deleting
705        }
706        bean.dispose();
707    }
708
709    @Override
710    public Class<?> getColumnClass(int col) {
711        switch (col) {
712            case CURVECOL:
713                return CurveComboBoxPanel.class;
714            case SPEEDCOL:
715                return SpeedComboBoxPanel.class; // apply real combo renderer
716            case DELETE_COL:
717            case EDIT_COL:
718                return JButton.class;
719            case UNITSCOL:
720                return JToggleButton.class;
721            case REPORT_CURRENTCOL:
722                return JRadioButton.class;
723            case PERMISSIONCOL:
724                return JCheckBox.class; // return Boolean.class;
725            default:
726                return String.class;
727        }
728    }
729
730    @Override
731    public int getPreferredWidth(int col) {
732        switch (col) {
733            case SYSNAMECOL:
734            case USERNAMECOL:
735                return new JTextField(15).getPreferredSize().width;
736            case STATECOL:
737                return new JTextField(ZEROS).getPreferredSize().width;
738            case COMMENTCOL:
739            case SENSORCOL:
740            case ERR_SENSORCOL:
741            case REPORTERCOL:
742            case WARRANTCOL:
743                return new JTextField(12).getPreferredSize().width;
744            case VALUE:
745            case CURVECOL:
746            case REPORT_CURRENTCOL:
747            case PERMISSIONCOL:
748            case SPEEDCOL:
749                return new JTextField(10).getPreferredSize().width;
750            case LENGTHCOL:
751                return new JTextField(6).getPreferredSize().width;
752            case UNITSCOL:
753                return new JTextField(5).getPreferredSize().width;
754            case EDIT_COL:
755                return new JButton(Bundle.getMessage("ButtonEditPath")).getPreferredSize().width+4;
756            case DELETE_COL:
757                return new JButton(Bundle.getMessage("ButtonDelete")).getPreferredSize().width+4;
758            default:
759                // fall through
760                break;
761        }
762        return 5;
763    }
764
765    @Override
766    public boolean isCellEditable(int row, int col) {
767        switch (col) {
768            case SYSNAMECOL:
769                if (super.getRowCount() == row) {
770                    return true; // the new entry/bottom row is editable in all cells
771                } else {
772                    return false;
773                }
774            case STATECOL:
775            case WARRANTCOL:
776            case VALUE:
777                return false;
778            default:
779                // fall through
780                break;
781        }
782        return true;
783    }
784
785    //*********************** combo box cell editors *********************************/
786
787    /**
788     * Provide a table cell renderer looking like a JComboBox as an
789     * editor/renderer for the OBlock table SPEED column.
790     * <p>
791     * This is a lightweight version of the
792     * {@link jmri.jmrit.beantable.RowComboBoxPanel} RowComboBox cell editor
793     * class, some of the hashtables not needed here since we only need
794     * identical options for all rows in a column.
795     *
796     * see jmri.jmrit.signalling.SignallingPanel.SignalMastModel.AspectComboBoxPanel for a full application with
797     * row specific comboBox choices.
798     */
799    public static class SpeedComboBoxPanel extends RowComboBoxPanel {
800
801        @Override
802        protected final void eventEditorMousePressed() {
803            this.editor.add(getEditorBox(table.convertRowIndexToModel(this.currentRow))); // add editorBox to JPanel
804            this.editor.revalidate();
805            SwingUtilities.invokeLater(this.comboBoxFocusRequester);
806            log.debug("eventEditorMousePressed in row: {})", this.currentRow);  // NOI18N
807        }
808
809        /**
810         * Call the method in the surrounding method for the
811         * OBlockTable.
812         *
813         * @param row the user clicked on in the table
814         * @return an appropriate combobox for this signal head
815         */
816        @Override
817        protected JComboBox<String> getEditorBox(int row) {
818            return getSpeedEditorBox(row);
819        }
820
821    }
822    // end of methods to display SPEED_COLUMN ComboBox
823
824    /**
825     * Provide a static JComboBox element to display inside the JPanel
826     * CellEditor. When not yet present, create, store and return a new one.
827     *
828     * @param row Index number (in TableDataModel)
829     * @return A JCombobox containing the valid curvature names.
830     */
831    static JComboBox<String> getCurveEditorBox(int row) {
832        // create dummy comboBox, override in extended classes for each bean
833        BlockCurvatureJComboBox j = new BlockCurvatureJComboBox();
834        j.setJTableCellClientProperties();
835        return j;
836    }
837
838    /**
839     * Customize the Turnout column to show an appropriate ComboBox of
840     * available options.
841     *
842     * @param table a JTable of beans
843     */
844    public void configCurveColumn(JTable table) {
845        // have the state column hold a JPanel with a JComboBox for Curvature
846        table.setDefaultEditor(OBlockTableModel.CurveComboBoxPanel.class, new OBlockTableModel.CurveComboBoxPanel());
847        table.setDefaultRenderer(OBlockTableModel.CurveComboBoxPanel.class, new OBlockTableModel.CurveComboBoxPanel()); // use same class as renderer
848        // Set more things?
849    }
850
851    /**
852     * Provide a table cell renderer looking like a JComboBox as an
853     * editor/renderer for the OBlock table CURVE column.
854     * <p>
855     * This is a lightweight version of the
856     * {@link jmri.jmrit.beantable.RowComboBoxPanel} RowComboBox cell editor
857     * class, some of the hashtables not needed here since we only need
858     * identical options for all rows in a column.
859     *
860     * see jmri.jmrit.signalling.SignallingPanel.SignalMastModel.AspectComboBoxPanel for a full application with
861     * row specific comboBox choices.
862     */
863    public static class CurveComboBoxPanel extends RowComboBoxPanel {
864
865        @Override
866        protected final void eventEditorMousePressed() {
867            this.editor.add(getEditorBox(table.convertRowIndexToModel(this.currentRow))); // add editorBox to JPanel
868            this.editor.revalidate();
869            SwingUtilities.invokeLater(this.comboBoxFocusRequester);
870            log.debug("eventEditorMousePressed in row: {})", this.currentRow);  // NOI18N
871        }
872
873        /**
874         * Call the method in the surrounding method for the
875         * OBlockTable.
876         *
877         * @param row the user clicked on in the table
878         * @return an appropriate combobox for this signal head
879         */
880        @Override
881        protected JComboBox<String> getEditorBox(int row) {
882            return getCurveEditorBox(row);
883        }
884
885    }
886    // end of methods to display CURVE_COLUMN ComboBox
887
888    /**
889     * Provide a static JComboBox element to display inside the JPanel
890     * CellEditor. When not yet present, create, store and return a new one.
891     *
892     * @param row Index number (in TableDataModel)
893     * @return A combobox containing the valid aspect names for this mast
894     */
895    static JComboBox<String> getSpeedEditorBox(int row) {
896        // create dummy comboBox, override in extended classes for each bean
897        JComboBox<String> editCombo = new JComboBox<>(jmri.InstanceManager.getDefault(SignalSpeedMap.class).getValidSpeedNames());
898        // item to reset speed notch to default, i.e. continue at current speed requirement.
899        javax.swing.DefaultComboBoxModel<String> model = (DefaultComboBoxModel<String>)editCombo.getModel();
900        model.addElement("");
901        editCombo.putClientProperty("JComponent.sizeVariant", "small");
902        editCombo.putClientProperty("JComboBox.buttonType", "square");
903        return editCombo;
904    }
905
906    /**
907     * Customize the Turnout column to show an appropriate ComboBox of
908     * available options.
909     *
910     * @param table a JTable of beans
911     */
912    public void configSpeedColumn(JTable table) {
913        // have the state column hold a JPanel with a JComboBox for Speeds
914        table.setDefaultEditor(OBlockTableModel.SpeedComboBoxPanel.class, new OBlockTableModel.SpeedComboBoxPanel());
915        table.setDefaultRenderer(OBlockTableModel.SpeedComboBoxPanel.class, new OBlockTableModel.SpeedComboBoxPanel()); // use same class as renderer
916        // Set more things?
917    }
918
919    @Override
920    public void propertyChange(PropertyChangeEvent e) {
921        super.propertyChange(e);
922        String property = e.getPropertyName();
923        if (log.isDebugEnabled()) log.debug("PropertyChange = {}", property);
924        if (property.equals("length") || property.equals("UserName") || property.equals("state")) {
925            ThreadingUtil.runOnGUIEventually(()-> {
926                _parent.updateOBlockTablesMenu();
927                fireTableDataChanged();
928            });
929        }
930        _parent.getPortalXRefTableModel().propertyChange(e);
931        _parent.getSignalTableModel().propertyChange(e);
932        _parent.getPortalTableModel().propertyChange(e);
933
934    }
935
936    protected String getClassName() {
937        return jmri.jmrit.beantable.OBlockTableAction.class.getName();
938    }
939
940    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(OBlockTableModel.class);
941
942}