001package jmri.jmrit.beantable.signalmast;
002
003import java.util.*;
004
005import javax.annotation.CheckForNull;
006import javax.annotation.Nonnull;
007import javax.swing.*;
008import javax.swing.table.TableColumn;
009
010import jmri.*;
011import jmri.jmrit.beantable.BeanTableDataModel;
012import jmri.jmrit.beantable.SignalMastLogicTableAction;
013import jmri.util.swing.JmriMouseEvent;
014import jmri.util.swing.XTableColumnModel;
015
016/**
017 * Create a SignalMastLogic Table Data Model.
018 * Code originally from SignalMastLogicTableAction
019 * @author Kevin Dickerson Copyright(C) 2011
020 * @author Steve Young (C) 2023
021 */
022public class SignalMastLogicTableDataModel extends BeanTableDataModel<SignalMastLogic>{
023    
024    static public final int SOURCECOL = 0;
025    static public final int SOURCEAPPCOL = 1;
026    static public final int DESTCOL = 2;
027    static public final int DESTAPPCOL = 3;
028    static public final int COMCOL = 4;
029    static public final int DELCOL = 5;
030    static public final int ENABLECOL = 6;
031    static public final int EDITLOGICCOL = 7;
032    static public final int MAXSPEEDCOL = 8;
033    static public final int COLUMNCOUNT = 9;
034
035    
036    private boolean suppressUpdate = false; // does not update table model changelistener during auto create pairs
037
038    public SignalMastLogicTableDataModel(){
039        super();
040        SignalMastLogicTableDataModel.this.updateNameList();
041    }
042
043    //We have to set a manager first off, but this gets replaced.
044    @Override
045    protected SignalMastLogicManager getManager() {
046        return InstanceManager.getDefault(SignalMastLogicManager.class);
047    }
048
049    @Override
050    public String getValue(String s) {
051        return "Set";
052    }
053
054    @Override
055    protected String getMasterClassName() {
056        return SignalMastLogicTableAction.class.getName();
057    }
058
059    @Override
060    public void clickOn(SignalMastLogic t) {
061    }
062
063    private ArrayList<Hashtable<SignalMastLogic, SignalMast>> signalMastLogicList = null;
064    
065    @Nonnull
066    private List<Hashtable<SignalMastLogic, SignalMast>> getSMLList(){
067        if ( signalMastLogicList == null) {
068            signalMastLogicList = new ArrayList<>();
069        }
070        return signalMastLogicList;
071    }
072    
073    @Override
074    protected synchronized void updateNameList() {
075        // first, remove listeners from the individual objects
076
077        for (int i = 0; i < getSMLList().size(); i++) {
078            // if object has been deleted, it's not here; ignore it
079            Hashtable<SignalMastLogic, SignalMast> b = getSMLList().get(i);
080            Enumeration<SignalMastLogic> en = b.keys();
081            while (en.hasMoreElements()) {
082                SignalMastLogic sm = en.nextElement();
083                SignalMast dest = b.get(sm);
084                sm.removePropertyChangeListener(this);
085                sm.getSourceMast().removePropertyChangeListener(this);
086                dest.removePropertyChangeListener(this);
087            }
088        }
089        List<SignalMastLogic> source = getManager().getSignalMastLogicList();
090        signalMastLogicList.clear();
091        for (int i = 0; i < source.size(); i++) {
092            List<SignalMast> destList = source.get(i).getDestinationList();
093            source.get(i).addPropertyChangeListener(this);
094            source.get(i).getSourceMast().addPropertyChangeListener(this);
095            for (int j = 0; j < destList.size(); j++) {
096                Hashtable<SignalMastLogic, SignalMast> hash = new Hashtable<>(1);
097                hash.put(source.get(i), destList.get(j));
098                destList.get(j).addPropertyChangeListener(this);
099                getSMLList().add(hash);
100            }
101        }
102    }
103
104    public void setSuppressUpdate(boolean newVal) {
105        suppressUpdate = newVal;
106    }
107
108    //Will need to redo this so that we work out the row number from looking in the signalmastlogiclist.
109    @Override
110    public void propertyChange(java.beans.PropertyChangeEvent e) {
111        if (suppressUpdate) {
112            return;
113        }
114        // updateNameList();
115        if (e.getPropertyName().equals("length") || e.getPropertyName().equals("updatedDestination") || e.getPropertyName().equals("updatedSource")) {
116            updateNameList();
117            log.debug("Table changed length to {}", getSMLList().size());
118            fireTableDataChanged();
119        } else if (e.getSource() instanceof SignalMastLogic) {
120            SignalMastLogic logic = (SignalMastLogic) e.getSource();
121            if (matchPropertyName(e)) {
122                for (int i = 0; i < getSMLList().size(); i++) {
123                    Hashtable<SignalMastLogic, SignalMast> b = getSMLList().get(i);
124                    Enumeration<SignalMastLogic> en = b.keys();
125                    while (en.hasMoreElements()) {
126                        SignalMastLogic sm = en.nextElement();
127                        if (sm == logic) {
128                            fireTableRowsUpdated(i, i);
129                        }
130                    }
131                }
132            }
133        } else if (e.getSource() instanceof SignalMast) {
134            SignalMast sigMast = (SignalMast) e.getSource();
135            for (int i = 0; i < getSMLList().size(); i++) {
136                Hashtable<SignalMastLogic, SignalMast> b = getSMLList().get(i);
137                Enumeration<SignalMastLogic> en = b.keys();
138                while (en.hasMoreElements()) {
139                    SignalMastLogic sm = en.nextElement();
140                    //SignalMast dest = b.get(sm);
141                    if (sm.getSourceMast() == sigMast) {
142                        fireTableRowsUpdated(i, i);
143                    }
144                }
145            }
146        }
147    }
148
149    /**
150     * Is this property event announcing a change this table should
151     * display?
152     * <p>
153     * Note that events will come both from the NamedBeans and also from
154     * the manager
155     */
156    @Override
157    protected boolean matchPropertyName(java.beans.PropertyChangeEvent e) {
158        return ((e.getPropertyName().contains("Comment")) || (e.getPropertyName().contains("Enable")));
159    }
160
161    @Override
162    public int getColumnCount() {
163        return COLUMNCOUNT;
164    }
165
166    @Override
167    public int getRowCount() {
168        return getSMLList().size();
169    }
170
171    @Override
172    public Object getValueAt(int row, int col) {
173        // some error checking
174        if (row >= getSMLList().size()) {
175            log.debug("row index is greater than signalMastLogicList size");
176            return null;
177        }
178        SignalMastLogic b = getLogicFromRow(row);
179        if (b==null){
180            return null;
181        }
182        SignalMast destMast;
183        switch (col) {
184            case SOURCECOL:
185                return b.getSourceMast().getDisplayName();
186            case DESTCOL:  // return user name
187                // sometimes, the TableSorter invokes this on rows that no longer exist, so we check
188                destMast = getDestMastFromRow(row);
189                return ( destMast != null ? destMast.getDisplayName() : null);
190            case SOURCEAPPCOL:  //
191                return b.getSourceMast().getAspect();
192            case DESTAPPCOL:  //
193                destMast = getDestMastFromRow(row);
194                return ( destMast != null ? destMast.getAspect() : null);
195            case COMCOL:
196                return b.getComment(getDestMastFromRow(row));
197            case DELCOL:
198                return Bundle.getMessage("ButtonDelete");
199            case EDITLOGICCOL:
200                return Bundle.getMessage("ButtonEdit");
201            case ENABLECOL:
202                return b.isEnabled(getDestMastFromRow(row));
203            case MAXSPEEDCOL:
204                return b.getMaximumSpeed(getDestMastFromRow(row));
205            default:
206                return super.getValueAt(row, col);
207        }
208    }
209
210    @Override
211    public void setValueAt(Object value, int row, int col) {
212        SignalMastLogic rowLogic = getLogicFromRow(row);
213        if ( rowLogic == null ){
214            return;
215        }
216        switch (col) {
217            case COMCOL:
218                rowLogic.setComment((String) value, getDestMastFromRow(row));
219                break;
220            case EDITLOGICCOL:
221                SwingUtilities.invokeLater(() -> {
222                    editLogic(row);
223                });
224                break;
225            case DELCOL:
226                // button fired, delete Bean
227                deleteLogic(row);
228                break;
229            case ENABLECOL:
230                SignalMast destMast = getDestMastFromRow(row);
231                if (destMast==null){
232                    break;
233                }
234                if ((Boolean) value) {
235                    rowLogic.setEnabled(destMast);
236                } else {
237                    rowLogic.setDisabled(destMast);
238                }
239                break;
240            default:
241                super.setValueAt(value, row, col);
242        }
243    }
244
245    @Override
246    public String getColumnName(int col) {
247        switch (col) {
248            case SOURCECOL:
249                return Bundle.getMessage("Source");
250            case DESTCOL:
251                return Bundle.getMessage("Destination");
252            case SOURCEAPPCOL:
253                return Bundle.getMessage("LabelAspectType");
254            case DESTAPPCOL:
255                return Bundle.getMessage("LabelAspectType");
256            case COMCOL:
257                return Bundle.getMessage("Comment");
258            case DELCOL:
259                return ""; // override default, no title for Delete column
260            case EDITLOGICCOL:
261                return ""; // override default, no title for Edit column
262            case ENABLECOL:
263                return Bundle.getMessage("ColumnHeadEnabled");
264            case MAXSPEEDCOL:
265                return Bundle.getMessage("LabelMaxSpeed");
266            default:
267                return super.getColumnName(col);
268        }
269    }
270
271    @Override
272    public Class<?> getColumnClass(int col) {
273        switch (col) {
274            case SOURCECOL:
275            case DESTCOL:
276            case SOURCEAPPCOL:
277            case COMCOL:
278            case DESTAPPCOL:
279                return String.class;
280            case ENABLECOL:
281                return Boolean.class;
282            case EDITLOGICCOL:
283            case DELCOL:
284                return JButton.class;
285            case MAXSPEEDCOL:
286                return Float.class;
287            default:
288                return super.getColumnClass(col);
289        }
290    }
291
292    @Override
293    public boolean isCellEditable(int row, int col) {
294        switch (col) {
295            case COMCOL:
296            case EDITLOGICCOL:
297            case DELCOL:
298            case ENABLECOL:
299                return true;
300            default:
301                return false;
302        }
303    }
304
305    final jmri.jmrit.signalling.SignallingAction sigLog = new jmri.jmrit.signalling.SignallingAction();
306    
307    void editLogic(int row) {
308        SignalMastLogic sml = getLogicFromRow(row);
309        if ( sml != null ) {
310            
311            sigLog.setMast(sml.getSourceMast(), getDestMastFromRow(row));
312            sigLog.actionPerformed(null);
313        }
314    }
315
316    void deleteLogic(int row) {
317        //This needs to be looked at
318        SignalMastLogic sml = getLogicFromRow(row);
319        SignalMast destMast = getDestMastFromRow(row);
320        if ( sml != null && destMast !=null ) {
321            InstanceManager.getDefault(SignalMastLogicManager.class).removeSignalMastLogic(sml, destMast);
322        }
323    }
324
325    @CheckForNull
326    public SignalMast getDestMastFromRow(int row) {
327        // if object has been deleted, it's not here; ignore it
328        Hashtable<SignalMastLogic, SignalMast> b = getSMLList().get(row);
329        Enumeration<SignalMastLogic> en = b.keys();
330        while (en.hasMoreElements()) {
331            return b.get(en.nextElement());
332        }
333        return null;
334    }
335
336    @CheckForNull
337    public SignalMastLogic getLogicFromRow(int row) {
338        Hashtable<SignalMastLogic, SignalMast> b = getSMLList().get(row);
339        Enumeration<SignalMastLogic> en = b.keys();
340        while (en.hasMoreElements()) {
341            return en.nextElement();
342        }
343        return null;
344    }
345
346    @Override
347    public int getPreferredWidth(int col) {
348        switch (col) {
349            case SOURCECOL:
350            case DESTCOL:
351            case DESTAPPCOL:
352            case SOURCEAPPCOL:
353            case MAXSPEEDCOL:
354                return new JTextField(10).getPreferredSize().width;
355            case COMCOL:
356                return 75;
357            case EDITLOGICCOL: // not actually used due to the configureTable, setColumnToHoldButton, configureButton
358                return new JTextField(6).getPreferredSize().width;
359            case DELCOL: // not actually used due to the configureTable, setColumnToHoldButton, configureButton
360            case ENABLECOL:
361                return new JTextField(5).getPreferredSize().width;
362            default:
363                return super.getPreferredWidth(col);
364        }
365    }
366
367    @Override
368    public void configureTable(JTable table) {
369        setColumnToHoldButton(table, EDITLOGICCOL,
370                new JButton(Bundle.getMessage("ButtonEdit")));
371        table.getTableHeader().setReorderingAllowed(true);
372
373        // have to shut off autoResizeMode to get horizontal scroll to work (JavaSwing p 541)
374        table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
375
376        // resize columns as requested
377        for (int i = 0; i < table.getColumnCount(); i++) {
378            int width = getPreferredWidth(i);
379            table.getColumnModel().getColumn(i).setPreferredWidth(width);
380        }
381        table.sizeColumnsToFit(-1);
382
383        // configValueColumn(table);
384        configDeleteColumn(table);
385    }
386
387    @Override
388    public SignalMastLogic getBySystemName(@Nonnull String name) {
389        return null;
390    }
391
392    @Override
393    public SignalMastLogic getByUserName(@Nonnull String name) {
394        return null;
395    }
396
397    @Override
398    synchronized public void dispose() {
399
400        getManager().removePropertyChangeListener(this);
401        
402            for (int i = 0; i < getSMLList().size(); i++) {
403                SignalMastLogic b = getLogicFromRow(i);
404                if (b != null) {
405                    b.removePropertyChangeListener(this);
406                }
407            }
408
409        super.dispose();
410    }
411
412    @Override
413    protected void configDeleteColumn(JTable table) {
414        // have the delete column hold a button
415        setColumnToHoldButton(table, DELCOL,
416                new JButton(Bundle.getMessage("ButtonDelete")));
417    }
418
419    @Override
420    protected String getBeanType() {
421        return "Signal Mast Logic";
422    }
423
424    @Override
425    protected void showPopup(JmriMouseEvent e) {
426    }
427
428    @Override
429    protected void setColumnIdentities(JTable table) {
430        super.setColumnIdentities(table);
431        Enumeration<TableColumn> columns;
432        if (table.getColumnModel() instanceof XTableColumnModel) {
433            columns = ((XTableColumnModel) table.getColumnModel()).getColumns(false);
434        } else {
435            columns = table.getColumnModel().getColumns();
436        }
437        while (columns.hasMoreElements()) {
438            TableColumn column = columns.nextElement();
439            switch (column.getModelIndex()) {
440                case SOURCEAPPCOL:
441                    column.setIdentifier("SrcAspect");
442                    break;
443                case DESTAPPCOL:
444                    column.setIdentifier("DstAspect");
445                    break;
446                case DELCOL:
447                    column.setIdentifier("Delete");
448                    break;
449                case EDITLOGICCOL:
450                    column.setIdentifier("Edit");
451                    break;
452                default:
453                // use existing value
454            }
455        }
456    }
457
458    @Override
459    public String getCellToolTip(JTable table, int row, int col) {
460
461        SignalMastLogic sml = getLogicFromRow(row);
462        if ( sml == null ) {
463            return null;
464        }
465
466        String tip = null;
467        SignalMast dest;
468        switch (col) {
469            case SOURCECOL:
470                tip = formatToolTip(sml.getSourceMast().getComment());
471                break;
472            case DESTCOL:
473                dest = this.getDestMastFromRow(row);
474                if ( dest != null) {
475                    tip = formatToolTip(dest.getComment());
476                }
477                break;
478            case COMCOL:
479                dest = this.getDestMastFromRow(row);
480                if ( dest != null) {
481                    tip = formatToolTip(sml.getComment(dest));
482                }
483                break;
484            default:
485                break;
486        }
487        return tip;
488    }
489    
490    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SignalMastLogicTableDataModel.class);
491}