001package jmri.jmrit.beantable.oblock;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.text.ParseException;
006import java.util.ArrayList;
007import javax.annotation.Nonnull;
008import javax.swing.*;
009import javax.swing.table.AbstractTableModel;
010import jmri.InstanceManager;
011import jmri.jmrit.logix.OBlock;
012import jmri.jmrit.logix.OPath;
013import jmri.jmrit.logix.Portal;
014import jmri.jmrit.logix.PortalManager;
015import jmri.util.IntlUtilities;
016import jmri.util.gui.GuiLafPreferencesManager;
017import jmri.util.swing.JmriJOptionPane;
018
019/**
020 * GUI to define the OPaths within an OBlock. An OPath is the setting of turnouts
021 * from one Portal to another Portal within an OBlock.  It may also be assigned
022 * a length.
023 * <hr>
024 * This file is part of JMRI.
025 * <p>
026 * JMRI is free software; you can redistribute it and/or modify it under the
027 * terms of version 2 of the GNU General Public License as published by the Free
028 * Software Foundation. See the "COPYING" file for a copy of this license.
029 * <p>
030 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
031 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
032 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
033 *
034 * @author Pete Cressman (C) 2010
035 */
036
037public class BlockPathTableModel extends AbstractTableModel implements PropertyChangeListener {
038
039    public static final int FROM_PORTAL_COLUMN = 0;
040    public static final int NAME_COLUMN = 1;
041    public static final int TO_PORTAL_COLUMN = 2;
042    static public final int LENGTHCOL = 3;
043    static public final int UNITSCOL = 4;
044    public static final int EDIT_COL = 5;
045    public static final int DELETE_COL = 6;
046    public static final int NUMCOLS = 7;
047
048    private final String[] tempRow = new String[NUMCOLS];
049
050    private OBlock _block;
051    private TableFrames _parent;
052    private final boolean _tabbed;     // read from prefs (restart required)
053    private ArrayList<Boolean> _units; // list to toggle units of length col for each path
054    
055    java.text.DecimalFormat twoDigit = new java.text.DecimalFormat("0.00");
056
057    public BlockPathTableModel() {
058        super();
059        _tabbed = InstanceManager.getDefault(GuiLafPreferencesManager.class).isOblockEditTabbed();
060    }
061
062    public BlockPathTableModel(@Nonnull OBlock block, @Nonnull TableFrames parent) {
063        super();
064        _block = block;
065        _parent = parent;
066        _tabbed = InstanceManager.getDefault(GuiLafPreferencesManager.class).isOblockEditTabbed();
067        initTempRow();
068        _block.addPropertyChangeListener(this);
069    }
070
071    public void removeListener() {
072        if (_block == null) {
073            return;
074        }
075        try {
076            _block.removePropertyChangeListener(this);
077        } catch (NullPointerException npe) {
078            // OK when block is removed
079        }
080    }
081
082    protected OBlock getBlock() {
083        return _block;
084    }
085
086    void initTempRow() {
087        if (!_tabbed) {
088            for (int i = 0; i < NUMCOLS; i++) {
089                tempRow[i] = null;
090            }
091            tempRow[LENGTHCOL] = twoDigit.format(0.0);
092            if (_block.isMetric()) {
093                tempRow[UNITSCOL] = Bundle.getMessage("cm");
094            } else {
095                tempRow[UNITSCOL] = Bundle.getMessage("in");
096            }
097            tempRow[DELETE_COL] = Bundle.getMessage("ButtonClear");
098        }
099        // refresh the Unit column values list
100        _units = new ArrayList<>();
101        for (int i = 0; i <= _block.getPaths().size(); i++) {
102            _units.add(_block.isMetric());
103        }
104    }
105
106    @Override
107    public int getColumnCount() {
108        return NUMCOLS; // Edit Turnouts column button is replaced by Edit for _tabbed
109    }
110
111    @Override
112    public int getRowCount() {
113        return _block.getPaths().size() + (_tabbed ? 0 : 1);
114        // +1 adds the extra empty row at the bottom of the table display for _desktop
115    }
116
117    @Override
118    public String getColumnName(int col) {
119        switch (col) {
120            case FROM_PORTAL_COLUMN:
121                return Bundle.getMessage("FromPortal");
122            case NAME_COLUMN:
123                return Bundle.getMessage("PathName");
124            case TO_PORTAL_COLUMN:
125                return Bundle.getMessage("ToPortal");
126            case LENGTHCOL:
127                return Bundle.getMessage("BlockLengthColName");
128            case UNITSCOL:
129                return "  ";
130            default:
131                // fall through
132                break;
133        }
134        return "";
135    }
136
137    @Override
138    public Object getValueAt(int rowIndex, int columnIndex) {
139        OPath path = null;
140        if (rowIndex < _block.getPaths().size()) {
141            path = (OPath) _block.getPaths().get(rowIndex);
142        }
143        switch (columnIndex) {
144            case FROM_PORTAL_COLUMN:
145                if (path != null) {
146                    Portal portal = path.getFromPortal();
147                    if (portal == null) {
148                        return "";
149                    }
150                    return portal.getName();
151                } else {
152                    return tempRow[columnIndex];
153                }
154            case NAME_COLUMN:
155                if (path != null) {
156                    return path.getName();
157                } else {
158                    return tempRow[columnIndex];
159                }
160            case TO_PORTAL_COLUMN:
161                if (path != null) {
162                    Portal portal = path.getToPortal();
163                    if (portal == null) {
164                        return "";
165                    }
166                    return portal.getName();
167                } else {
168                    return tempRow[columnIndex];
169                }
170            case LENGTHCOL:
171                if (path != null) {
172                    if (_units.get(rowIndex)) {
173                        return (twoDigit.format(path.getLengthCm()));
174                    } else {
175                        return (twoDigit.format(path.getLengthIn()));
176                    }
177                }
178                break;
179            case UNITSCOL:
180                return _units.get(rowIndex);
181            case EDIT_COL:
182                if (path != null) {
183                    if (_tabbed) {
184                        return Bundle.getMessage("ButtonEdit");
185                    } else {
186                        return Bundle.getMessage("ButtonEditTO");
187                    }
188                } else {
189                    return "";
190                }
191            case DELETE_COL:
192                if (path != null) {
193                    return Bundle.getMessage("ButtonDelete");
194                } else {
195                    return Bundle.getMessage("ButtonClear"); // for _desktop
196                }
197            default:
198                // fall through
199                break;
200         }
201        return "";
202    }
203
204    @Override
205    public void setValueAt(Object value, int row, int col) {
206        String msg = null;
207        if (_block.getPaths().size() == row) { // must be a new BlockPath in tempRow (_desktop interface)
208            switch (col) {
209                case NAME_COLUMN:
210                    String strValue = (String)value;
211                    if (_block.getPathByName(strValue) != null) { // check for duplicate Path name in this OBlock
212                        msg = Bundle.getMessage("DuplPathName", strValue);
213                        tempRow[col] = strValue;
214                    } else {
215                        Portal fromPortal = _block.getPortalByName(tempRow[FROM_PORTAL_COLUMN]);
216                        Portal toPortal = _block.getPortalByName(tempRow[TO_PORTAL_COLUMN]);
217                        if (fromPortal != null || toPortal != null) {
218                            OPath path = new OPath(strValue, _block, fromPortal, toPortal, null);
219                            float len = 0.0f;
220                            try {
221                                len = IntlUtilities.floatValue(tempRow[LENGTHCOL]);
222                            } catch (ParseException e) {
223                                msg = Bundle.getMessage("BadNumber", tempRow[LENGTHCOL]);                    
224                            }
225                            if (tempRow[UNITSCOL].equals((Bundle.getMessage("cm")))) {
226                                path.setLength(len * 10.0f);
227                            } else {
228                                path.setLength(len * 25.4f);
229                            }
230                            
231                            if (!_block.addPath(path)) {
232                                msg = Bundle.getMessage("AddPathFailed", strValue);
233                                tempRow[NAME_COLUMN] = strValue;
234                            } else {
235                                initTempRow();
236                                _parent.updateOBlockTablesMenu();
237                                fireTableDataChanged();
238                            }
239                        } else {
240                            tempRow[NAME_COLUMN] = strValue; // initial entry of Path name in cell
241                        }
242                    }
243                    break;
244                case UNITSCOL:
245                    _units.set(row, (Boolean)value); // true = metric
246                    fireTableRowsUpdated(row, row);
247                    return;
248                case DELETE_COL:
249                    initTempRow();
250                    fireTableRowsUpdated(row, row);
251                    break;
252                default:
253                    // fall through
254                    break;
255            }
256            tempRow[col] = (String)value;
257            if (msg != null) {
258                JmriJOptionPane.showMessageDialog(null, msg,
259                        Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
260            }
261            return;
262        }
263
264        // edit existing BlockPath row
265        OPath path = (OPath) _block.getPaths().get(row);
266        switch (col) {
267            case FROM_PORTAL_COLUMN:
268                String strValue = (String)value;
269                if (strValue != null) {
270                    PortalManager portalMgr = InstanceManager.getDefault(PortalManager.class);
271                    Portal portal = _block.getPortalByName(strValue);
272                    if (portal == null || portalMgr.getPortal(strValue) == null) {
273                        int val = _parent.verifyWarning(Bundle.getMessage("BlockPortalConflict", value, _block.getDisplayName()));
274                        if (val == 2) {
275                            break;
276                        }
277                        portal = portalMgr.providePortal(strValue);
278                        if (portal == null) {
279                            msg = Bundle.getMessage("NoSuchPortalName", strValue);
280                            break;
281                        } else {
282                            if (!portal.setFromBlock(_block, false)) {
283                                val = _parent.verifyWarning(Bundle.getMessage("BlockPathsConflict", value, portal.getFromBlockName()));
284                                if (val == 2) {
285                                    break;
286                                }
287                            }
288                            portal.setFromBlock(_block, true);
289                            _parent.getPortalTableModel().fireTableDataChanged();
290                        }
291                    }
292                    path.setFromPortal(portal);
293                    if (!portal.addPath(path)) {
294                        msg = Bundle.getMessage("AddPathFailed", strValue);
295                    }
296                } else {
297                    path.setFromPortal(null);
298                }
299                fireTableRowsUpdated(row, row);
300                break;
301            case NAME_COLUMN:
302                strValue = (String)value;
303                if (strValue != null) {
304                    if (_block.getPathByName(strValue) != null) {
305                        msg = Bundle.getMessage("DuplPathName", strValue);
306                    }
307                    path.setName(strValue);
308                    fireTableRowsUpdated(row, row);
309                }
310                break;
311            case TO_PORTAL_COLUMN:
312                strValue = (String)value;
313                if (strValue != null) {
314                    PortalManager portalMgr = InstanceManager.getDefault(PortalManager.class);
315                    Portal portal = _block.getPortalByName(strValue);
316                    if (portal == null || portalMgr.getPortal(strValue) == null) {
317                        int val = _parent.verifyWarning(Bundle.getMessage("BlockPortalConflict", value, _block.getDisplayName()));
318                        if (val == 2) {
319                            break;  // no response
320                        }
321                        portal = portalMgr.providePortal(strValue);
322                        if (portal == null) {
323                            msg = Bundle.getMessage("NoSuchPortalName", strValue);
324                            break;
325                        } else {
326                            if (!portal.setToBlock(_block, false)) {
327                                val = _parent.verifyWarning(Bundle.getMessage("BlockPathsConflict", value, portal.getToBlockName()));
328                                if (val == 2) {
329                                    break;
330                                }
331                            }
332                            portal.setToBlock(_block, true);
333                            _parent.getPortalTableModel().fireTableDataChanged();
334                        }
335                    }
336                    path.setToPortal(portal);
337                    if (!portal.addPath(path)) {
338                        msg = Bundle.getMessage("AddPathFailed", strValue);
339                    }
340                } else {
341                    path.setToPortal(null);
342                }
343                fireTableRowsUpdated(row, row);
344                break;
345            case LENGTHCOL:
346                try {
347                    float len = IntlUtilities.floatValue(value.toString());
348                    if (_units.get(row)) {
349                        path.setLength(len * 10.0f);
350                    } else {
351                        path.setLength(len * 25.4f);
352                    }
353                    fireTableRowsUpdated(row, row);                    
354                } catch (ParseException e) {
355                    JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("BadNumber", value),
356                            Bundle.getMessage("ErrorTitle"), JmriJOptionPane.WARNING_MESSAGE);                    
357                }
358                return;
359            case UNITSCOL:
360                _units.set(row, (Boolean)value);
361                fireTableRowsUpdated(row, row);
362                return;
363            case EDIT_COL: // button is called [Edit] (_tabbed) or [Edit Turnouts] (_desktop)
364                if (_tabbed && !_parent.isPathEdit()) { // everything is handled in BlockPathEdit panel
365                    _parent.openPathEditor(_block.getSystemName(), path.getName(), this);
366                } else {
367                    _parent.openPathTurnoutFrame(_parent.makePathTurnoutName(_block.getSystemName(), path.getName()));
368                }
369                break;
370            case DELETE_COL:
371                if (deletePath(path)) {
372                    _units.remove(row);
373                    fireTableDataChanged();
374                }
375                break;
376            default:
377                // fall through
378                break;
379        }
380        if (msg != null) {
381            JmriJOptionPane.showMessageDialog(null, msg,
382                    Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE);
383        }
384    }
385
386    boolean deletePath(OPath path) {
387        int val = _parent.verifyWarning(Bundle.getMessage("DeletePathConfirm", path.getName()));
388        if (val == 2) {
389            return false;
390        }
391        return _block.removeOPath(path);
392    }
393
394    @Override
395    public boolean isCellEditable(int row, int col) {
396        return true;
397    }
398
399    @Override
400    public Class<?> getColumnClass(int col) {
401        switch (col) {
402            case DELETE_COL:
403            case EDIT_COL:
404                return JButton.class;
405            case UNITSCOL:
406                return JToggleButton.class;
407            default:
408                return String.class;
409        }
410    }
411
412    public int getPreferredWidth(int col) {
413        switch (col) {
414            case FROM_PORTAL_COLUMN:
415            case NAME_COLUMN:
416            case TO_PORTAL_COLUMN:
417                return new JTextField(25).getPreferredSize().width;
418            case LENGTHCOL:
419                return new JTextField(10).getPreferredSize().width;
420            case UNITSCOL:
421                return new JTextField(6).getPreferredSize().width;
422            case EDIT_COL:
423                return new JButton("TURNOUT").getPreferredSize().width;
424            case DELETE_COL:
425                return new JButton("DELETE").getPreferredSize().width;
426            default:
427                // fall through
428                break;
429        }
430        return 5;
431    }
432
433    @Override
434    public void propertyChange(PropertyChangeEvent e) {
435        if (_block.equals(e.getSource())) {
436            String property = e.getPropertyName();
437            if (log.isDebugEnabled()) {
438                log.debug("propertyChange \"{}\".  source= {}", property, e.getSource());
439            }
440            if (property.equals("portalCount") || 
441                    property.equals("pathCount") || property.equals("pathName")) {
442                fireTableDataChanged();
443            } else if (property.equals("deleted")) {
444                _parent.disposeBlockPathFrame(_block);
445            }
446        }
447    }
448
449    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BlockPathTableModel.class);
450
451}