001package jmri.jmrit.cabsignals;
002
003import java.beans.PropertyChangeEvent;
004import javax.swing.JTable;
005import javax.swing.JTextField;
006import jmri.Block;
007import jmri.BlockManager;
008import jmri.CabSignalManager;
009import jmri.InstanceManager;
010import jmri.LocoAddress;
011import jmri.Path;
012import jmri.SignalMast;
013
014import org.slf4j.Logger;
015import org.slf4j.LoggerFactory;
016
017
018/**
019 * Table data model for display of Cab Signaling information.
020 *
021 * @author Steve Young (c) 2018
022 * @author Paul Bender (c) 2018
023 * @see CabSignalPane
024 * @since 4.13.4
025 */
026public class CabSignalTableModel extends javax.swing.table.AbstractTableModel {
027
028    private CabSignalManager cabSignalManager;
029
030    // column order needs to match list in columnToolTips
031
032    static public final int LOCO_ID_COLUMN = 0;
033    static public final int SEND_CABSIG_COLUMN = 1;
034    static public final int CURRENT_BLOCK = 2;
035    static public final int BLOCK_DIR = 3;
036    static public final int REVERSE_BLOCK_DIR_BUTTON_COLUMN = 4;
037    static public final int NEXT_BLOCK = 5;
038    static public final int NEXT_SIGNAL = 6;
039    static public final int NEXT_ASPECT = 7;
040    static public final int NEXT_ASPECT_ICON = 8;
041
042    static public final int MAX_COLUMN = 9;
043
044    static final int[] STARTUPCOLUMNS = {0,1,2,3,4,5,6,7,8};
045
046    CabSignalTableModel(int row, int column) {
047        cabSignalManager = InstanceManager.getNullableDefault(CabSignalManager.class);
048        if(cabSignalManager == null){
049           log.info("creating new DefaultCabSignalManager");
050           InstanceManager.store(new jmri.managers.DefaultCabSignalManager(), CabSignalManager.class);
051           cabSignalManager = InstanceManager.getNullableDefault(CabSignalManager.class);
052        }
053    }
054
055    // order needs to match column list top of dtabledatamodel
056    static final String[] COLUMNTOOLTIPS = {
057        null, // loco id
058        Bundle.getMessage("CabsigCheckboxTip"),
059        Bundle.getMessage("BlockUserName"),
060        Bundle.getMessage("BlockDirectionTip"),
061        null, // block lookup button
062        Bundle.getMessage("NextBlockTip"),
063        Bundle.getMessage("NextSignalTip"),
064        Bundle.getMessage("NextAspectTip"),
065        Bundle.getMessage("NextIconTip"), // aspect icon
066
067    }; // Length = number of items in array should (at least) match number of columns
068
069    /**
070     * {@inheritDoc}
071     */
072    @Override
073    public int getRowCount() {
074        return cabSignalManager.getCabSignalList().size();
075    }
076
077    /**
078     * {@inheritDoc}
079     */
080    @Override
081    public int getColumnCount() {
082        return MAX_COLUMN;
083    }
084
085    /**
086     * Returns String of column name from column int
087     * used in table header.
088     *
089     * @param col int col number
090     */
091    @Override
092    public String getColumnName(int col) { // not in any order
093        switch (col) {
094            case LOCO_ID_COLUMN:
095                return Bundle.getMessage("LocoID");
096            case CURRENT_BLOCK:
097                return Bundle.getMessage("Block");
098            case BLOCK_DIR:
099                return Bundle.getMessage("BlockDirection");
100            case REVERSE_BLOCK_DIR_BUTTON_COLUMN:
101                return Bundle.getMessage("BlockButton");
102            case NEXT_BLOCK:
103                return Bundle.getMessage("NextBlock");
104            case NEXT_SIGNAL:
105                return Bundle.getMessage("NextSignal");
106            case NEXT_ASPECT:
107                return Bundle.getMessage("NextAspect");
108            case SEND_CABSIG_COLUMN:
109                return Bundle.getMessage("SigDataOn");
110            case NEXT_ASPECT_ICON:
111                return Bundle.getMessage("AspectIconMenu");
112            default:
113                return "unknown"; // NOI18N
114        }
115    }
116
117    /**
118     * Returns int of startup column widths.
119     *
120     * @param col int col number
121     * @return initial preferred width
122     */
123    public static int getPreferredWidth(int col) {
124        switch (col) {
125            case SEND_CABSIG_COLUMN:
126            case NEXT_ASPECT_ICON:
127                return new JTextField(3).getPreferredSize().width;
128            case LOCO_ID_COLUMN:
129                return new JTextField(4).getPreferredSize().width;
130            case BLOCK_DIR:
131            case NEXT_SIGNAL:
132                return new JTextField(6).getPreferredSize().width;
133            case CURRENT_BLOCK:
134            case NEXT_BLOCK:
135                return new JTextField(8).getPreferredSize().width;
136            case REVERSE_BLOCK_DIR_BUTTON_COLUMN:
137            case NEXT_ASPECT:
138                return new JTextField(10).getPreferredSize().width;
139            default:
140                log.warn("no width found col {}",col);
141                return new JTextField(" <unknown> ").getPreferredSize().width; // NOI18N
142        }
143    }
144
145    /**
146     * {@inheritDoc}
147     */
148    @Override
149    public Class<?> getColumnClass(int col) {
150        switch (col) {
151            case LOCO_ID_COLUMN:
152                return LocoAddress.class;
153            case REVERSE_BLOCK_DIR_BUTTON_COLUMN:
154                return javax.swing.JButton.class;
155            case CURRENT_BLOCK:
156            case BLOCK_DIR:
157            case NEXT_BLOCK:
158            case NEXT_SIGNAL:
159            case NEXT_ASPECT:
160            case NEXT_ASPECT_ICON:
161                return String.class;
162            case SEND_CABSIG_COLUMN:
163                return Boolean.class;
164            default:
165                log.error("no column class located");
166                return null;
167        }
168    }
169
170    /**
171     * {@inheritDoc}
172     */
173    @Override
174    public boolean isCellEditable(int row, int col) {
175        switch (col) {
176            case SEND_CABSIG_COLUMN:
177            case REVERSE_BLOCK_DIR_BUTTON_COLUMN:
178                return true;
179            default:
180                return false;
181        }
182    }
183
184    /**
185     * Configure a table to have our standard rows and columns.
186     * <p>
187     * This is optional, in that other table formats can use this table model.
188     * But we put it here to help keep it consistent.
189     * @param cmdStatTable Table to be configured
190     */
191    public void configureTable(JTable cmdStatTable) {
192        // allow reordering of the columns
193        cmdStatTable.getTableHeader().setReorderingAllowed(true);
194
195        // shut off autoResizeMode to get horizontal scroll to work (JavaSwing p 541)
196        cmdStatTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
197
198        // resize columns as requested
199        for (int i = 0; i < cmdStatTable.getColumnCount(); i++) {
200            int width = getPreferredWidth(i);
201            cmdStatTable.getColumnModel().getColumn(i).setPreferredWidth(width);
202        }
203       // cmdStatTable.sizeColumnsToFit(-1);
204    }
205
206    /**
207     * {@inheritDoc}
208     */
209    @Override
210    public Object getValueAt(int row, int col) {
211        SignalMast mast;
212        Block b;
213        switch (col) {
214            case LOCO_ID_COLUMN:
215                cabSignalManager.getCabSignalArray()[row].addPropertyChangeListener( (PropertyChangeEvent e) -> {
216                   if(e.getSource() instanceof jmri.CabSignal){
217                      fireTableDataChanged();
218                   }
219                });
220                return cabSignalManager.getCabSignalArray()[row].getCabSignalAddress();
221            case CURRENT_BLOCK:
222                b = cabSignalManager.getCabSignalArray()[row].getBlock();
223                if ( b != null){
224                    return b.getDisplayName();
225                } else {
226                    return "";
227                }
228            case BLOCK_DIR:
229                b = cabSignalManager.getCabSignalArray()[row].getBlock();
230                if ( b != null){
231                    return Path.decodeDirection(b.getDirection());
232                } else {
233                    return "";
234                }
235            case REVERSE_BLOCK_DIR_BUTTON_COLUMN:
236                if (cabSignalManager.getCabSignalArray()[row].getBlock()==null){
237                    return Bundle.getMessage("BlockLookup");
238                } else {
239                    return Bundle.getMessage("ChngDirection");
240                }
241            case NEXT_BLOCK:
242                Block nextBl = cabSignalManager.getCabSignalArray()[row].getNextBlock();
243                if ( nextBl != null){
244                    return nextBl.getDisplayName();
245                } else {
246                    return "";
247                }
248            case NEXT_SIGNAL:
249                mast = cabSignalManager.getCabSignalArray()[row].getNextMast();
250                if (mast!=null) {
251                    return mast.getDisplayName();
252                }
253                return "";
254            case NEXT_ASPECT:
255                mast = cabSignalManager.getCabSignalArray()[row].getNextMast();
256                if (mast!=null) {
257                    return mast.getAspect();
258                }
259                return "";
260            case NEXT_ASPECT_ICON:
261                mast = cabSignalManager.getCabSignalArray()[row].getNextMast();
262                if (mast!=null) {
263                    String imageLink = mast.getAppearanceMap().getProperty(mast.getAspect(), "imagelink");
264                    log.debug("imagelink is {}", imageLink);
265                    if ( imageLink != null ) {
266                        String newlink = imageLink.replace("../", "");  // replace is immutable
267                        // should start at the resources directory
268                        return newlink;
269                    }
270                }
271                return "";
272            case SEND_CABSIG_COLUMN:
273                return cabSignalManager.getCabSignalArray()[row].isCabSignalActive();
274            default:
275                log.error("internal state inconsistent with table request for row {} col {}", row, col);
276                return null;
277        }
278    }
279
280    /**
281     * {@inheritDoc}
282     */
283    @Override
284    public void setValueAt(Object value, int row, int col) {
285        switch (col) {
286            case REVERSE_BLOCK_DIR_BUTTON_COLUMN:
287                cabSignalManager.getCabSignalArray()[row].setBlock();
288                chngblockdir(row);
289                break;
290            case NEXT_BLOCK:
291                jmri.util.ThreadingUtil.runOnLayout( ()->{
292                    BlockManager bmgr = jmri.InstanceManager.getDefault(jmri.BlockManager.class);
293                    Block b = bmgr.getBlock((String)value);
294                    cabSignalManager.getCabSignalArray()[row].setBlock(b);
295                });
296                break;
297            case SEND_CABSIG_COLUMN:
298                cabSignalManager.getCabSignalArray()[row].setCabSignalActive((Boolean) value);
299                break;
300            default:
301                break;
302        }
303    }
304
305    /**
306     * Reverse the direction on the block associated with
307     * the row.  Changes to the cab signal happen when the block's
308     * properties change.
309     */
310    private void chngblockdir(int row){
311        log.debug("changing block direction for row {}", row);
312        int olddirection;
313        Block b = cabSignalManager.getCabSignalArray()[row].getBlock();
314        if (b == null){
315            cabSignalManager.getCabSignalArray()[row].setBlock();
316            b = cabSignalManager.getCabSignalArray()[row].getBlock();
317            if (b==null){
318                return;
319            } else {
320                cabSignalManager.getCabSignalArray()[row].setBlock(b);
321                olddirection=b.getDirection();
322            }
323        }
324        else {
325            olddirection=b.getDirection();
326            log.debug("Block {} set to direction {} ", b.getUserName(), (String.valueOf(olddirection)) );
327        }
328
329        log.debug(" Direction to reverse :{}", Path.decodeDirection(olddirection) );
330
331        if (olddirection==0){
332            log.debug("No direction found, setting to North, East");
333            b.setDirection(80);
334        } else {
335            log.debug(" direction found, setting reverse.");
336            b.setDirection(Path.reverseDirection(olddirection));
337        }
338        fireTableDataChanged();
339        log.debug("block {} now has direction {}", b.getUserName(), b.getDirection());
340    }
341
342    protected void setPanelPauseButton(boolean isPaused){
343        for (int i = 0; i < getRowCount(); i++) {
344            cabSignalManager.getCabSignalArray()[i].setMasterCabSigPauseActive(isPaused);
345        }
346    }
347
348    public void dispose() {
349    }
350
351    private final static Logger log = LoggerFactory.getLogger(CabSignalTableModel.class);
352
353}