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