001package jmri.jmrit.conditional;
002
003import java.awt.Component;
004import java.awt.Container;
005import java.awt.Dimension;
006import java.awt.FlowLayout;
007import java.awt.Font;
008import java.util.SortedSet;
009
010import javax.swing.*;
011import javax.swing.border.Border;
012import javax.swing.table.*;
013
014import jmri.*;
015
016import jmri.jmrit.conditional.ConditionalEditBase.SelectionMode;
017import jmri.jmrit.entryexit.EntryExitPairs;
018import jmri.jmrit.logix.OBlockManager;
019import jmri.jmrit.logix.WarrantManager;
020import jmri.util.swing.JmriJOptionPane;
021import jmri.util.table.ButtonEditor;
022import jmri.util.table.ButtonRenderer;
023
024/**
025 * Extracted from ConditionalEditList.
026 * Allows ConditionalEditList to open alternate frame
027 * for copying Conditionals.
028 * 
029 * @author Pete Cressman Copyright (C) 2020
030 */
031public class ConditionalCopyFrame extends ConditionalFrame {
032
033    CopyTableModel _actionTableModel = null;
034    CopyTableModel _variableTableModel = null;
035    JTextField _antecedentField;
036    JPanel _antecedentPanel;
037
038    Conditional.ItemType _saveType = Conditional.ItemType.NONE;
039
040    // ------------------------------------------------------------------
041    
042    ConditionalCopyFrame(String title, Conditional conditional, ConditionalList parent) {
043        super(title, conditional, parent);
044        makeConditionalFrame(conditional);
045    }
046
047    void makeConditionalFrame(Conditional conditional) {
048        addHelpMenu(
049                "package.jmri.jmrit.conditional.ConditionalCopy", true);  // NOI18N
050        Container contentPane = getContentPane();
051        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
052        contentPane.add(makeTopPanel(conditional));
053
054        // add Logical Expression Section
055        JPanel logicPanel = new JPanel();
056        logicPanel.setLayout(new BoxLayout(logicPanel, BoxLayout.Y_AXIS));
057
058        // add Antecedent Expression Panel - ONLY appears for MIXED operator statements
059        _antecedentField = new JTextField(65);
060        _antecedentField.setText(ConditionalEditBase.translateAntecedent(_antecedent, false));
061        _antecedentField.setFont(new Font("SansSerif", Font.BOLD, 14));  // NOI18N
062        _antecedentPanel = makeEditPanel(_antecedentField, "LabelAntecedent", "LabelAntecedentHint");  // NOI18N
063        _antecedentPanel.setVisible(_logicType == Conditional.AntecedentOperator.MIXED);
064        logicPanel.add(_antecedentPanel);
065
066        // add state variable table title
067        JPanel varTitle = new JPanel();
068        varTitle.setLayout(new FlowLayout());
069        varTitle.add(new JLabel(Bundle.getMessage("StateVariableTableTitle")));  // NOI18N
070        logicPanel.add(varTitle);
071        // initialize table of state variables
072        _variableTableModel = new VariableCopyTableModel(false, _parent._selectionMode != SelectionMode.USESINGLE);
073        JTable variableTable = new JTable(_variableTableModel);
074        variableTable.setRowSelectionAllowed(false);
075        int rowHeight = variableTable.getRowHeight();
076
077        TableColumnModel variableColumnModel = variableTable.getColumnModel();
078
079        TableColumn rowColumn = variableColumnModel.getColumn(VariableCopyTableModel.ROWNUM_COLUMN);
080        rowColumn.setResizable(false);
081        rowColumn.setMaxWidth(new JTextField(3).getPreferredSize().width);
082
083        TableColumn nameColumn = variableColumnModel.getColumn(VariableCopyTableModel.NAME_COLUMN);
084        nameColumn.setResizable(false);
085        if (_parent._selectionMode != SelectionMode.USESINGLE) {
086            nameColumn.setCellEditor(new NameCellEditor(new JComboBox<String>()));
087        } else {
088            nameColumn.setCellEditor(new NameCellEditor(new JTextField()));            
089        }
090        nameColumn.setMinWidth(40);
091        nameColumn.setResizable(true);
092
093        TableColumn descColumn = variableColumnModel.getColumn(VariableCopyTableModel.DESCRIPTION_COLUMN);
094        descColumn.setMinWidth(300);
095        descColumn.setResizable(true);
096
097        // add a scroll pane
098        JScrollPane variableTableScrollPane = new JScrollPane(variableTable);
099        Dimension dim = variableTable.getPreferredSize();
100        dim.height = 7 * rowHeight;
101        variableTableScrollPane.getViewport().setPreferredSize(dim);
102
103        logicPanel.add(variableTableScrollPane);
104
105
106        Border logicPanelBorder = BorderFactory.createEtchedBorder();
107        Border logicPanelTitled = BorderFactory.createTitledBorder(
108                logicPanelBorder, Bundle.getMessage("TitleLogicalExpression") + " ");  // NOI18N
109        logicPanel.setBorder(logicPanelTitled);
110        contentPane.add(logicPanel);
111        // End of Logic Expression Section
112
113        // add Action Consequents Section
114        JPanel conseqentPanel = new JPanel();
115        conseqentPanel.setLayout(new BoxLayout(conseqentPanel, BoxLayout.Y_AXIS));
116
117        JPanel actTitle = new JPanel();
118        actTitle.setLayout(new FlowLayout());
119        actTitle.add(new JLabel(Bundle.getMessage("ActionTableTitle")));  // NOI18N
120        conseqentPanel.add(actTitle);
121
122        // set up action consequents table
123        _actionTableModel = new ActionCopyTableModel(true, _parent._selectionMode != SelectionMode.USESINGLE);
124        JTable actionTable = new JTable(_actionTableModel);
125        actionTable.setRowSelectionAllowed(false);
126        ButtonRenderer buttonRenderer = new ButtonRenderer();
127        actionTable.setDefaultRenderer(JButton.class, buttonRenderer);
128        TableCellEditor buttonEditor = new ButtonEditor(new JButton());
129        actionTable.setDefaultEditor(JButton.class, buttonEditor);
130        JButton testButton = new JButton("XXXXXX");  // NOI18N
131        actionTable.setRowHeight(testButton.getPreferredSize().height);
132        TableColumnModel actionColumnModel = actionTable.getColumnModel();
133
134        nameColumn = actionColumnModel.getColumn(ActionCopyTableModel.NAME_COLUMN);
135        nameColumn.setResizable(false);
136        if (_parent._selectionMode != SelectionMode.USESINGLE) {
137            nameColumn.setCellEditor(new NameCellEditor(new JComboBox<String>()));
138        } else {
139            nameColumn.setCellEditor(new NameCellEditor(new JTextField()));            
140        }
141        nameColumn.setMinWidth(40);
142        nameColumn.setResizable(true);
143
144        descColumn = actionColumnModel.getColumn(ActionCopyTableModel.DESCRIPTION_COLUMN);
145        descColumn.setMinWidth(300);
146        descColumn.setResizable(true);
147
148        TableColumn deleteColumn = actionColumnModel.getColumn(ActionCopyTableModel.DELETE_COLUMN);
149        // ButtonRenderer and TableCellEditor already set
150        deleteColumn.setMinWidth(testButton.getPreferredSize().width);
151        deleteColumn.setMaxWidth(testButton.getPreferredSize().width);
152        deleteColumn.setResizable(false);
153        
154        // add a scroll pane
155        JScrollPane actionTableScrollPane = new JScrollPane(actionTable);
156        dim = actionTableScrollPane.getPreferredSize();
157        dim.height = 7 * rowHeight;
158        actionTableScrollPane.getViewport().setPreferredSize(dim);
159        conseqentPanel.add(actionTableScrollPane);
160
161
162        Border conseqentPanelBorder = BorderFactory.createEtchedBorder();
163        Border conseqentPanelTitled = BorderFactory.createTitledBorder(
164                conseqentPanelBorder, Bundle.getMessage("TitleAction"));  // NOI18N
165        conseqentPanel.setBorder(conseqentPanelTitled);
166        contentPane.add(conseqentPanel);
167        // End of Action Consequents Section
168
169        contentPane.add(_parent.makeBottomPanel());
170        
171        // setup window closing listener
172        this.addWindowListener(
173                new java.awt.event.WindowAdapter() {
174            @Override
175            public void windowClosing(java.awt.event.WindowEvent e) {
176                cancelConditionalPressed();
177            }
178        });
179        // initialize state variable table
180        _variableTableModel.fireTableDataChanged();
181        // initialize action variables
182        _actionTableModel.fireTableDataChanged();
183    }   // end makeConditionalFrame
184
185    class NameCellEditor extends DefaultCellEditor {
186
187        NameCellEditor(JComboBox<String> comboBox) {
188            super(comboBox);
189            log.debug("New JComboBox<String> NameCellEditor");
190        }
191
192        NameCellEditor(JTextField textField) {
193            super(textField);
194            log.debug("New JTextField NameCellEditor");
195        }
196
197        @SuppressWarnings("unchecked") // getComponent call requires an unchecked cast
198        @Override
199        public Component getTableCellEditorComponent(JTable table, Object value,
200                boolean isSelected, int row, int column) {
201            CopyTableModel model = (CopyTableModel) table.getModel();
202            if (log.isDebugEnabled()) {
203                log.debug("getTableCellEditorComponent: row= {}, column= {} selected = {} isComboTable= {}",
204                        row, column, isSelected, model._isComboTable);
205            }
206            Conditional.ItemType itemType;
207            String name;
208            if (model.isActionTable()) {
209                ConditionalAction action = _actionList.get(row);
210                itemType = action.getType().getItemType();
211                name = action.getDeviceName();
212            } else {
213                ConditionalVariable variable = _variableList.get(row);
214                itemType = variable.getType().getItemType();
215                name = variable.getName();
216            }
217            if (model._isComboTable) {
218                SortedSet<NamedBean> namedBeans = (SortedSet<NamedBean>)getItemNamedBeamns(itemType);
219                JComboBox<String> comboBox = (JComboBox<String>)getComponent();
220                comboBox.removeAllItems();
221                for (NamedBean b : namedBeans) {
222                    comboBox.addItem(b.getDisplayName());
223                }
224            } else {
225                if (_saveType != itemType) {
226                    _parent.closeSinglePanelPickList();
227                    _parent.createSinglePanelPickList(itemType, null, true);
228                    _saveType = itemType;
229                }
230                JTextField field = (JTextField)getComponent();
231                field.setText(name);
232            }
233            return super.getTableCellEditorComponent(table, value, isSelected, row, column);
234        }
235    }
236
237    SortedSet<?> getItemNamedBeamns(Conditional.ItemType itemType) {
238        switch (itemType) {
239            case SENSOR:      // 1
240                return InstanceManager.getDefault(SensorManager.class).getNamedBeanSet();
241            case TURNOUT:     // 2
242                return InstanceManager.getDefault(TurnoutManager.class).getNamedBeanSet();
243            case LIGHT:       // 3
244                return InstanceManager.getDefault(LightManager.class).getNamedBeanSet();
245            case SIGNALHEAD:  // 4
246                return InstanceManager.getDefault(SignalHeadManager.class).getNamedBeanSet();
247            case SIGNALMAST:  // 5
248                return InstanceManager.getDefault(SignalMastManager.class).getNamedBeanSet();
249            case MEMORY:      // 6
250                return InstanceManager.getDefault(MemoryManager.class).getNamedBeanSet();
251            case LOGIX:       // 7
252                return InstanceManager.getDefault(LogixManager.class).getNamedBeanSet();
253            case WARRANT:     // 8
254                return InstanceManager.getDefault(WarrantManager.class).getNamedBeanSet();
255            case OBLOCK:      // 10
256                return InstanceManager.getDefault(OBlockManager.class).getNamedBeanSet();
257            case ENTRYEXIT:   // 11
258                return InstanceManager.getDefault(EntryExitPairs.class).getNamedBeanSet();
259            case OTHER:   // 14
260                return InstanceManager.getDefault(jmri.RouteManager.class).getNamedBeanSet();
261            default:
262                return new java.util.TreeSet<String>();             // Skip any other items.
263        }
264    }
265    /**
266     * Respond to the Cancel button in the Edit Conditional frame.
267     * <p>
268     * Does the cleanup from updateConditionalPressed
269     * and _editConditionalFrame window closer.
270     */
271    @Override
272    void cancelConditionalPressed() {
273        super.cancelConditionalPressed();
274    }
275
276    /**
277     * Validate Variable name change.
278     * <p>
279     * Messages are sent to the user for any errors found. This routine returns
280     * false immediately after finding the first error, even if there might be
281     * more errors.
282     *
283     * @param name name of the ConditionalVariable
284     * @param variable ConditionalVariable to validate
285     * @return true if all data checks out OK, otherwise false
286     */
287    boolean validateVariable(String name, ConditionalVariable variable) {
288        Conditional.ItemType itemType = variable.getType().getItemType();
289
290        if (!checkIsAction(name, itemType) ) {
291            return false;
292        }
293        if (!isValidType(itemType, name)) {
294            return false;
295        }
296        return (true);
297    }
298    /**
299     * Validate Action item name change.
300     * <p>
301     * Messages are sent to the user for any errors found. This routine returns
302     * false immediately after finding an error, even if there might be more
303     * errors.
304     *
305     * @param name name of the action
306     * @param action ConditionalAction to validate
307     * @return true if all data checks out OK, otherwise false
308     */
309    boolean validateAction(String name, ConditionalAction action) {
310        if (!checkReferenceByMemory(name)) {
311            return false;
312        }
313        Conditional.ItemType itemType = action.getType().getItemType();
314        if (_referenceByMemory) {
315            if (itemType == Conditional.ItemType.MEMORY) {
316                JmriJOptionPane.showMessageDialog(this,
317                        Bundle.getMessage("Warn6"),
318                        Bundle.getMessage("WarningTitle"), // NOI18N
319                        JmriJOptionPane.WARNING_MESSAGE);
320                return false;                
321            }
322        } else {
323            if (!checkIsVariable(name, itemType) ) {
324                return false;
325            }
326            if (!isValidType(itemType, name)) {
327                return false;
328            }
329        }
330        return (true);
331    }
332
333    boolean isValidType( Conditional.ItemType itemType, String name) {
334        switch (itemType) {
335            case SENSOR:
336                name = _parent.validateSensorReference(name);
337                if (name == null) {
338                    return false;
339                }
340                break;
341            case TURNOUT:
342                name = _parent.validateTurnoutReference(name);
343                if (name == null) {
344                    return false;
345                }
346                break;
347            case CONDITIONAL:
348                name = _parent.validateConditionalReference(name);
349                if (name == null) {
350                    return false;
351                }
352                break;
353            case LIGHT:
354                name = _parent.validateLightReference(name);
355                if (name == null) {
356                    return false;
357                }
358                break;
359            case SIGNALHEAD:
360                name = _parent.validateSignalHeadReference(name);
361                if (name == null) {
362                    return false;
363                }
364                break;
365            case SIGNALMAST:
366                name = _parent.validateSignalMastReference(name);
367                if (name == null) {
368                    return false;
369                }
370                break;
371            case MEMORY:
372                name = _parent.validateMemoryReference(name);
373                if (name == null) {
374                    return false;
375                }
376                break;
377            case LOGIX:
378                name = _parent.validateLogixReference(name);
379                if (name == null) {
380                    return false;
381                }
382                break;
383            case WARRANT:
384                name = _parent.validateWarrantReference(name);
385                if (name == null) {
386                    return false;
387                }
388                break;
389            case OBLOCK:
390                name = _parent.validateOBlockReference(name);
391                if (name == null) {
392                    return false;
393                }
394                break;
395            case ENTRYEXIT:
396                name = _parent.validateEntryExitReference(name);
397                if (name == null) {
398                    return false;
399                }
400                break;
401            default:
402                break;
403        }
404        return true;
405    }
406
407    //------------------- Table Models ----------------------
408
409    abstract class CopyTableModel extends AbstractTableModel{
410        
411        boolean _isActionTable;
412        boolean _isComboTable;
413
414        CopyTableModel(boolean isAction, boolean isCombo) {
415            _isActionTable = isAction;
416            _isComboTable = isCombo;
417            log.debug("CopyTableModel: isAction= {}, _isComboTable= {}", isAction, _isComboTable);
418        }
419
420        boolean isActionTable() {
421            return _isActionTable;
422        }
423
424        boolean isComboTable() {
425            return _isComboTable;
426        }
427    }
428
429    class VariableCopyTableModel extends CopyTableModel{
430        
431        static final int ROWNUM_COLUMN = 0;
432        static final int NAME_COLUMN = 1;
433        static final int DESCRIPTION_COLUMN = 2;
434
435        VariableCopyTableModel(boolean isAction, boolean isCombo) {
436            super(isAction, isCombo);
437        }
438
439        @Override
440        public Class<?> getColumnClass(int c) {
441            switch (c) {
442                case NAME_COLUMN:
443                    if (_isComboTable) {
444                        return JComboBox.class;
445                    } else {
446                        return JTextField.class;
447                    }
448                case DESCRIPTION_COLUMN:
449                    return String.class;
450                default:
451                    // fall through
452                    break;
453            }
454            return String.class;
455        }
456
457        @Override
458        public int getColumnCount() {
459            return 3;
460        }
461
462        @Override
463        public boolean isCellEditable(int r, int col) {
464            if (col == NAME_COLUMN) {
465                return true;
466            }
467            return false;
468        }
469
470        @Override
471        public String getColumnName(int col) {
472            switch (col) {
473                case ROWNUM_COLUMN:
474                    return (Bundle.getMessage("ColumnLabelRow"));  // NOI18N
475                case NAME_COLUMN:
476                    return (Bundle.getMessage("ColumnLabelName"));  // NOI18N
477                case DESCRIPTION_COLUMN:
478                    return (Bundle.getMessage("ColumnLabelDescription"));  // NOI18N
479                default:
480                    break;
481            }
482            return "";
483        }
484
485        public int getPreferredWidth(int col) {
486            switch (col) {
487                case ROWNUM_COLUMN:
488                    return 10;
489                case NAME_COLUMN:
490                    return 200;
491                case DESCRIPTION_COLUMN:
492                    return 600;
493                default:
494                    break;
495            }
496            return 10;
497        }
498
499        @Override
500        public int getRowCount() {
501            return _variableList.size();
502        }
503
504        @Override
505        public Object getValueAt(int row, int col) {
506            if (row >= getRowCount()) {
507                return null;
508            }
509            switch (col) {
510                case ROWNUM_COLUMN:
511                    return ("R" + (row + 1)); // NOI18N
512                case NAME_COLUMN:
513                    return _variableList.get(row).getName();  // NOI18N
514               case DESCRIPTION_COLUMN:
515                    return _variableList.get(row).toString();
516                default:
517                    break;
518            }
519            return null;
520        }
521
522        @Override
523        public void setValueAt(Object value, int row, int col) {
524            if (row >= getRowCount()) {
525                return;
526            }
527            if (col == NAME_COLUMN) {
528                String name = (String)value;
529                ConditionalVariable variable = _variableList.get(row);
530                if (validateVariable(name, variable)) {
531                    variable.setName(name);
532                    this.fireTableRowsDeleted(row, row);
533                }
534            }
535        }        
536    }
537
538    class ActionCopyTableModel extends CopyTableModel {
539        
540        static final int NAME_COLUMN = 0;
541        static final int DESCRIPTION_COLUMN = 1;
542        static final int DELETE_COLUMN = 2;
543
544        boolean _isActionTable;
545        boolean _isComboTable;
546
547        ActionCopyTableModel(boolean isAction, boolean isCombo) {
548            super(isAction, isCombo);
549        }
550
551        @Override
552        public Class<?> getColumnClass(int c) {
553            switch (c) {
554                case NAME_COLUMN:
555                    if (_isComboTable) {
556                        return JComboBox.class;
557                    } else {
558                        return JTextField.class;
559                    }
560                case DESCRIPTION_COLUMN:
561                    return String.class;
562                case DELETE_COLUMN:
563                    return JButton.class;
564                default:
565                    // fall through
566                    break;
567            }
568            return String.class;
569        }
570
571        @Override
572        public int getColumnCount() {
573            return 3;
574        }
575
576        @Override
577        public boolean isCellEditable(int r, int col) {
578            if (col == DESCRIPTION_COLUMN) {
579                return false;
580            }
581            return true;
582        }
583
584        @Override
585        public String getColumnName(int col) {
586            switch (col) {
587                case NAME_COLUMN:
588                    return (Bundle.getMessage("ColumnLabelName"));  // NOI18N
589                case DESCRIPTION_COLUMN:
590                    return (Bundle.getMessage("ColumnLabelDescription"));  // NOI18N
591                case DELETE_COLUMN:
592                    return "";
593                default:
594                    break;
595            }
596            return "";
597        }
598
599        public int getPreferredWidth(int col) {
600            switch (col) {
601                case NAME_COLUMN:
602                    return 200;
603                case DESCRIPTION_COLUMN:
604                    return 600;
605                default:
606                    break;
607            }
608            return 10;
609        }
610
611        @Override
612        public int getRowCount() {
613            return _actionList.size();
614        }
615
616        @Override
617        public Object getValueAt(int row, int col) {
618            if (row >= getRowCount()) {
619                return null;
620            }
621            ConditionalAction action = _actionList.get(row);
622            switch (col) {
623                case NAME_COLUMN:
624                    return action.getDeviceName();
625               case DESCRIPTION_COLUMN:
626                   return action.description(_parent._curConditional.getTriggerOnChange());
627               case DELETE_COLUMN:
628                   return Bundle.getMessage("ButtonDelete");  // NOI18N
629                default:
630                    break;
631            }
632            return null;
633        }
634
635        @Override
636        public void setValueAt(Object value, int row, int col) {
637            if (row >= getRowCount()) {
638                return;
639            }
640            if (col == NAME_COLUMN) {
641                ConditionalAction action = _actionList.get(row);
642                String name = (String)value;
643                if (validateAction(name, action)) {
644                    action.setDeviceName(name);
645                    this.fireTableRowsDeleted(row, row);
646                }
647            } else if (col == DELETE_COLUMN) {
648                _actionList.remove(row);
649                this.fireTableRowsDeleted(row, row);
650            }
651        }        
652    }
653
654    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ConditionalCopyFrame.class);
655}