001package jmri.jmrit.display;
002
003import java.awt.*;
004import java.awt.event.ActionEvent;
005import java.awt.event.ActionListener;
006import java.beans.PropertyChangeEvent;
007import java.beans.PropertyChangeListener;
008import java.util.*;
009import java.util.List;
010import java.util.regex.Matcher;
011import java.util.regex.Pattern;
012
013import javax.annotation.Nonnull;
014import javax.swing.*;
015import javax.swing.table.*;
016
017import jmri.*;
018import jmri.NamedBean.DisplayOptions;
019import jmri.jmrit.logixng.Module;
020import jmri.jmrit.logixng.*;
021import jmri.jmrit.logixng.NamedTable.NamedTablePropertyChangeEvent;
022import jmri.jmrit.logixng.actions.*;
023import jmri.jmrit.logixng.implementation.*;
024import jmri.jmrit.logixng.util.LogixNG_Thread;
025import jmri.util.swing.*;
026import jmri.util.table.ButtonEditor;
027import jmri.util.table.ButtonRenderer;
028
029/**
030 * An icon to display a NamedTable and let the user edit it.
031 *
032 * @author Pete Cressman    Copyright (c) 2009
033 * @author Daniel Bergqvist Copyright (C) 2025
034 * @since 5.15.1
035 */
036public final class LogixNGTableIcon extends PositionableJPanel {
037
038    private TableModel _tableModel = null;
039
040    // the associated NamedTable object
041    private final JTable _table;
042    private final JList<Object> _rowHeader;
043    private final JScrollPane _scrollPane;
044
045    private final java.awt.event.MouseListener _mouseListener = JmriMouseListener.adapt(this);
046
047    public LogixNGTableIcon(String tableName, Editor editor) {
048        super(editor);
049        setDisplayLevel(Editor.LABELS);
050
051        setLayout(new java.awt.BorderLayout());
052
053        _tableModel = new TableModel(tableName);
054
055        _table = new JTable(_tableModel);
056        _table.setCellSelectionEnabled(true);
057        _table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
058        _table.setAutoResizeMode( JTable.AUTO_RESIZE_OFF );
059        _table.getTableHeader().setReorderingAllowed(false);
060
061        for (int col=0; col < _tableModel.getColumnCount(); col++) {
062            TableModel.HeaderType headerType = _tableModel._headers[col];
063            if (headerType._cellEditor != null) {
064                _table.getColumnModel().getColumn(col).setCellEditor(headerType._cellEditor);
065            }
066        }
067
068        ButtonRenderer buttonRenderer = new ButtonRenderer();
069        _table.setDefaultRenderer(JButton.class, buttonRenderer);
070        TableCellEditor editButEditor = new ButtonEditor(new JButton());
071        _table.setDefaultEditor(JButton.class, editButEditor);
072
073        ListModel<Object> lm = new RowHeaderListModel();
074
075        _rowHeader = new JList<>(lm);
076        _rowHeader.setFixedCellHeight(
077                _table.getRowHeight()
078        );
079        _rowHeader.setCellRenderer(new RowHeaderRenderer(_table));
080
081        _scrollPane = new JScrollPane(_table);
082        _scrollPane.setRowHeaderView(_rowHeader);
083        add(_scrollPane, BorderLayout.CENTER);
084
085        _table.addMouseListener(_mouseListener);
086        _rowHeader.addMouseListener(_mouseListener);
087        _scrollPane.addMouseListener(_mouseListener);
088        setPopupUtility(new PositionablePopupUtil(this, _table));
089    }
090
091    @Override
092    public Positionable deepClone() {
093        LogixNGTableIcon pos = new LogixNGTableIcon(
094                _tableModel.getTable().getDisplayName(), _editor);
095        return finishClone(pos);
096    }
097
098    protected Positionable finishClone(LogixNGTableIcon pos) {
099        _tableModel.setTable(_tableModel.getTable());
100        return super.finishClone(pos);
101    }
102
103    public TableModel getTableModel() {
104        return _tableModel;
105    }
106
107    public JTable getJTable() {
108        return _table;
109    }
110
111    @Override
112    @Nonnull
113    public String getTypeString() {
114        return Bundle.getMessage("PositionableType_LogixNGTableIcon");
115    }
116
117    @Override
118    public String getNameString() {
119        String name;
120        if (_tableModel.getTable() == null) {
121            name = Bundle.getMessage("NotConnected");
122        } else {
123            name = _tableModel.getTable().getDisplayName(DisplayOptions.USERNAME_SYSTEMNAME);
124        }
125        return name;
126    }
127
128    private void configureLogixNGTable() {
129        NamedTable namedTable = _tableModel.getTable();
130
131        JComboBox<ModuleItem> _validateModuleComboBox;
132        _validateModuleComboBox = new JComboBox<>();
133        _validateModuleComboBox.addItem(new ModuleItem(null));
134        for (Module m : InstanceManager.getDefault(ModuleManager.class).getNamedBeanSet()) {
135            if ("DefaultFemaleDigitalActionSocket".equals(m.getRootSocketType().getName())
136                    && m.isVisible()) {
137                ModuleItem mi = new ModuleItem(m);
138                _validateModuleComboBox.addItem(mi);
139                Module _validateModule = _tableModel.getValidateModule();
140                if (_validateModule == m) {
141                    _validateModuleComboBox.setSelectedItem(mi);
142                }
143            }
144        }
145        JComboBoxUtil.setupComboBoxMaxRows(_validateModuleComboBox);
146
147        Map<String,Integer> columnIndexes = new HashMap<>();
148        List<String> columns = new ArrayList<>();
149        for (int col=0; col < namedTable.numColumns(); col++) {
150            String header = getObjectAsString(namedTable.getCell(0, col+1));
151            if (!header.isEmpty()) {
152                columns.add(header);
153                columnIndexes.put(header, col);
154            }
155        }
156        JList<String> columnList = new JList<>(columns.toArray(new String[0]));
157        for (String header : _tableModel._editableColumnsList) {
158            int index = columnIndexes.getOrDefault(header,-1);
159            if (index != -1) {
160                columnList.getSelectionModel().addSelectionInterval(index, index);
161            }
162        }
163        columnList.setBorder(BorderFactory.createLineBorder(Color.black));
164
165        JDialog dialog = new JDialog();
166        dialog.setTitle(Bundle.getMessage("ConfigureLogixNGTable"));
167        dialog.setLocationRelativeTo(this);
168        dialog.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
169
170        JPanel p;
171        p = new JPanel();
172        p.setLayout(new java.awt.GridBagLayout());
173        java.awt.GridBagConstraints c = new java.awt.GridBagConstraints();
174        c.gridwidth = 1;
175        c.gridheight = 1;
176        c.gridx = 0;
177        c.gridy = 0;
178        c.anchor = java.awt.GridBagConstraints.EAST;
179        //c.gridx = 0;
180        c.gridy = 1;
181        JLabel validateLabel = new JLabel(Bundle.getMessage("LogixNGTableIcon_ValidateLogixNGModule"));
182        p.add(validateLabel, c);
183        validateLabel.setLabelFor(_validateModuleComboBox);
184        c.gridy = 2;
185        JLabel allowEditLabel = new JLabel(Bundle.getMessage("LogixNGTableIcon_AllowEditTable"));
186        JCheckBox allowEdit = new JCheckBox();
187        allowEdit.setSelected(_tableModel.isEditable());
188        p.add(allowEditLabel, c);
189        allowEditLabel.setLabelFor(allowEdit);
190        c.gridy = 3;
191        p.add(new JLabel(Bundle.getMessage("LogixNGTableIcon_EditableColumns")), c);
192        c.gridx = 1;
193        c.gridy = 0;
194        p.add(Box.createHorizontalStrut(5), c);
195        c.gridx = 2;
196        c.gridy = 2;
197        c.anchor = java.awt.GridBagConstraints.WEST;
198        c.weightx = 1.0;
199        c.fill = java.awt.GridBagConstraints.HORIZONTAL;  // text field will expand
200        p.add(allowEdit, c);
201        c.gridx = 2;
202        c.gridy = 1;
203        p.add(_validateModuleComboBox, c);
204//        sys.setToolTipText(Bundle.getMessage("SysNameToolTip", "Y"));
205        c.gridy = 3;
206        p.add(new JScrollPane(columnList), c);
207        c.gridy = 4;
208        p.add(Box.createVerticalStrut(5), c);
209        c.gridx = 0;
210        c.gridy = 5;
211        c.gridwidth = 3;
212        p.add(new JLabel(Bundle.getMessage("LogixNGTableIcon_EditableColumnsInfo")), c);
213        add(p);
214
215        // cancel + add buttons at bottom of window
216        JPanel panelBottom = new JPanel();
217        panelBottom.setLayout(new FlowLayout(FlowLayout.TRAILING));
218
219        JButton cancel;
220        panelBottom.add(cancel = new JButton(Bundle.getMessage("ButtonCancel")));
221        cancel.addActionListener((evt) -> {
222            dialog.dispose();
223        });
224
225        JButton ok;
226//        panelBottom.add(ok = new JButton(Bundle.getMessage(addButtonLabel)));
227        panelBottom.add(ok = new JButton(Bundle.getMessage("ButtonOK")));
228        ok.addActionListener((evt) -> {
229            _tableModel.setValidateModule(_validateModuleComboBox.getItemAt(_validateModuleComboBox.getSelectedIndex())._module);
230            _tableModel.setEditable(allowEdit.isSelected());
231            _tableModel.setEditableColumns(String.join("\t", columnList.getSelectedValuesList()));
232            dialog.dispose();
233        });
234//        p.add(panelBottom);
235
236        c.gridwidth = 3;
237        c.gridx = 0;
238        c.gridy = 99;
239        c.anchor = java.awt.GridBagConstraints.CENTER;
240        c.fill = java.awt.GridBagConstraints.NONE;
241        p.add(panelBottom, c);
242        add(p);
243
244        dialog.getContentPane().add(p);
245        dialog.pack();
246        dialog.setModal(true);
247        dialog.setVisible(true);
248    }
249
250    @Override
251    public boolean setEditIconMenu(javax.swing.JPopupMenu popup) {
252        String txt = java.text.MessageFormat.format(Bundle.getMessage("EditItem"), Bundle.getMessage("BeanNameLogixNGTable"));
253        popup.add(new javax.swing.AbstractAction(txt) {
254            @Override
255            public void actionPerformed(ActionEvent e) {
256                edit();
257            }
258        });
259
260        txt = Bundle.getMessage("ConfigureLogixNGTable");
261        popup.add(new javax.swing.AbstractAction(txt) {
262            @Override
263            public void actionPerformed(ActionEvent e) {
264                configureLogixNGTable();
265            }
266        });
267        return true;
268    }
269
270    @Override
271    protected void edit() {
272        makeIconEditorFrame(this, "LogixNGTable", true, _iconEditor);
273        _iconEditor.setPickList(jmri.jmrit.picker.PickListModel.namedTablePickModelInstance());
274        ActionListener addIconAction = a -> editNamedTable();
275        _iconEditor.makeIconPanel(false);
276        _iconEditor.complete(addIconAction, false, false, true);
277        _iconEditor.setSelection(_tableModel.getTable());
278    }
279
280    void editNamedTable() {
281        _tableModel.setTable(_iconEditor.getTableSelection().getDisplayName());
282        _iconEditorFrame.dispose();
283        _iconEditorFrame = null;
284        _iconEditor = null;
285        validate();
286    }
287
288    @Override
289    void cleanup() {
290        if (_scrollPane != null) {
291            _table.removeMouseListener(_mouseListener);
292            _rowHeader.removeMouseListener(_mouseListener);
293            _scrollPane.removeMouseListener(_mouseListener);
294        }
295        _tableModel.setTable((NamedTable)null);
296        _tableModel.setValidateModule(null);
297    }
298
299    private static String getObjectAsString(Object o) {
300        return o != null ? o.toString() : "";
301    }
302
303    @Override
304    public void remove() {
305        _tableModel.dispose();
306        super.remove();
307    }
308
309    // ------------ Table Models ------------
310
311    /**
312     * Table model for Tables in the Edit NamedTable pane.
313     */
314    public final class TableModel extends AbstractTableModel
315            implements PropertyChangeListener {
316
317        private class HeaderType {
318            Class<?> _class;
319            String _type;
320            String _parameters;
321            String _header;
322            TableCellEditor _cellEditor;
323        }
324
325        private NamedBeanHandle<NamedTable> _namedTable;
326        private NamedBeanHandle<Module> _validateModule;
327        private boolean _editable = false;
328        private String _editableColumns = "";
329        List<String> _editableColumnsList;
330
331        private final LogixNG validateModuleLogixNG = new DefaultLogixNG("IQ:JMRI:LogixNGTableIcon", null);
332        private final ConditionalNG validateModuleConditionalNG =
333                new DefaultConditionalNG("IQC:JMRI:LogixNGTableIcon", null, LogixNG_Thread.DEFAULT_LOGIXNG_THREAD);
334        private final DigitalCallModule validateModuleAction = new DigitalCallModule("IQDA:JMRI:LogixNGTableIcon", null);
335        private Map<String, Object> _variablesWithValues;
336        private final MyData _myData = new MyData();
337        private final HeaderType[] _headers;
338
339
340        public TableModel(String tableName) {
341            initCallModule();
342            setEditableColumns("");
343            setTable(tableName);
344
345            if (_namedTable != null) {
346                _headers = new HeaderType[_namedTable.getBean().numColumns()];
347                initHeaders();
348            } else {
349                _headers = null;
350            }
351        }
352
353        private void initHeaders() {
354            for (int col=0; col < _namedTable.getBean().numColumns(); col++) {
355                HeaderType headerType = new HeaderType();
356                _headers[col] = headerType;
357
358                Object headerObj = _namedTable.getBean().getCell(0, col+1);
359                String header;
360                if (headerObj != null) {
361                    header = headerObj.toString();
362                } else {
363                    header = "";
364                }
365                if (header.startsWith("{{{")) {
366                    Pattern pattern = Pattern.compile("\\{\\{\\{(.+?)(\\:.+?)?\\}\\}\\}(.*)");
367                    Matcher matcher = pattern.matcher(header);
368                    if (matcher.matches()) {
369                        headerType._header = matcher.group(3);
370                        headerType._parameters = matcher.group(2);
371                        headerType._type = matcher.group(1).toLowerCase();
372                        switch (headerType._type) {
373                            case "button":
374                                headerType._class = JButton.class;
375                                break;
376                            case "list":
377                                String comboBoxTableName = headerType._parameters.substring(1);
378                                NamedTable comboBoxTable = InstanceManager
379                                        .getDefault(NamedTableManager.class)
380                                        .getNamedTable(comboBoxTableName);
381                                if (comboBoxTable != null) {
382                                    headerType._class = JComboBox.class;
383                                    JComboBox<String> comboBox = new JComboBox<>();
384                                    for (int row=1; row <= comboBoxTable.numRows(); row++) {
385                                        Object val = comboBoxTable.getCell(row, 1);
386                                        String value = val != null ? val.toString() : "";
387                                        comboBox.addItem(value);
388                                    }
389                                    headerType._cellEditor = new DefaultCellEditor(comboBox);
390                                } else {
391                                    log.warn("Cannot load LogixNG Table: {}", comboBoxTableName);
392                                }
393                                break;
394                            default:
395                                log.warn("Unknown column type: {}", matcher.group(1));
396                                headerType._type = null;
397                        }
398                    } else {
399                        log.warn("Unknown column definition: {}", header);
400                    }
401                } else {
402                    _headers[col]._header = header;
403                }
404            }
405        }
406
407        public NamedTable getTable() {
408            return _namedTable != null ? _namedTable.getBean() : null;
409        }
410
411        public void setTable(String tableName) {
412            if (_namedTable != null) {
413                _namedTable.getBean().removePropertyChangeListener(this);
414            }
415
416            NamedTable table = null;
417            if (tableName != null) {
418                table = InstanceManager.getDefault(NamedTableManager.class)
419                        .getNamedTable(tableName);
420            }
421
422            if (table != null) {
423                _namedTable = InstanceManager.getDefault(NamedBeanHandleManager.class)
424                        .getNamedBeanHandle(table.getDisplayName(), table);
425                if (_namedTable != null) {
426                    _namedTable.getBean().addPropertyChangeListener(this);
427                }
428            } else {
429                _namedTable = null;
430            }
431
432            fireTableStructureChanged();
433        }
434
435        public void setTable(NamedTable table) {
436            if (table != null) {
437                _namedTable = InstanceManager.getDefault(NamedBeanHandleManager.class)
438                        .getNamedBeanHandle(table.getDisplayName(), table);
439            } else {
440                _namedTable = null;
441            }
442            fireTableStructureChanged();
443        }
444
445        public Module getValidateModule() {
446            return _validateModule != null ? _validateModule.getBean() : null;
447        }
448
449        public void setValidateModule(Module module) {
450            if (module != null) {
451                _validateModule = InstanceManager.getDefault(NamedBeanHandleManager.class)
452                        .getNamedBeanHandle(module.getDisplayName(), module);
453            } else {
454                _validateModule = null;
455            }
456        }
457
458        public boolean isEditable() {
459            return _editable;
460        }
461
462        public void setEditable(boolean editable) {
463            _editable = editable;
464        }
465
466        public String getEditableColumns() {
467            return _editableColumns;
468        }
469
470        public void setEditableColumns(String editableColumns) {
471            _editableColumns = editableColumns;
472            _editableColumnsList = Arrays.asList(_editableColumns.split("\t"));
473        }
474
475        @Override
476        public int getColumnCount() {
477            if (_namedTable != null) {
478                return _namedTable.getBean().numColumns();
479            } else {
480                return 0;
481            }
482        }
483
484        @Override
485        public int getRowCount() {
486            if (_namedTable != null) {
487                return _namedTable.getBean().numRows();
488            } else {
489                return 0;
490            }
491        }
492
493        @Override
494        public String getColumnName(int col) {
495            return _headers[col]._header;
496        }
497
498        @Override
499        public Object getValueAt(int row, int col) {
500            if (_namedTable == null) {
501                return null;
502            }
503            return _namedTable.getBean().getCell(row+1, col+1);
504        }
505
506        @Override
507        public void setValueAt(Object val, int row, int col) {
508            callValidateModule(val, row, col);
509        }
510
511        @Override
512        public Class<?> getColumnClass(int col) {
513            Class<?> clazz = _headers[col]._class;
514            if (clazz != null) {
515                return clazz;
516            }
517            return super.getColumnClass(col);
518        }
519
520        @Override
521        public boolean isCellEditable(int rowIndex, int columnIndex) {
522            if (!_editable) {
523                return false;
524            }
525
526            String columnHeader = getObjectAsString(_namedTable.getBean().getCell(0, columnIndex+1));
527            boolean allowColumn = _editableColumns.isEmpty();
528            allowColumn |= _editableColumnsList.contains(columnHeader);
529
530            return allowColumn;
531        }
532
533        private void initCallModule() {
534            validateModuleLogixNG.addConditionalNG(validateModuleConditionalNG);
535
536            DigitalMany many = new DigitalMany("IQDA:JMRI:LogixNGTableIcon", null);
537            MaleSocket maleSocketMany = new DefaultMaleDigitalActionSocket(
538                    InstanceManager.getDefault(DigitalActionManager.class), many);
539            many.setParent(maleSocketMany);
540
541            maleSocketMany.addLocalVariable("iconId", SymbolTable.InitialValueType.String, null);
542            maleSocketMany.addLocalVariable("tableName", SymbolTable.InitialValueType.String, null);
543            maleSocketMany.addLocalVariable("row", SymbolTable.InitialValueType.String, null);
544            maleSocketMany.addLocalVariable("column", SymbolTable.InitialValueType.String, null);
545            maleSocketMany.addLocalVariable("type", SymbolTable.InitialValueType.String, null);
546            maleSocketMany.addLocalVariable("oldValue", SymbolTable.InitialValueType.String, null);
547            maleSocketMany.addLocalVariable("newValue", SymbolTable.InitialValueType.String, null);
548
549            try {
550                validateModuleConditionalNG.getFemaleSocket().connect(maleSocketMany);
551            } catch (SocketAlreadyConnectedException e) {
552                log.error("Exception when creating error handling LogixNG: ", e);
553            }
554
555            SetLocalVariables setLocalVariables = new SetLocalVariables("IQDA:JMRI:LogixNGTableIcon", null);
556            MaleSocket maleSocketSetLocalVariables = new DefaultMaleDigitalActionSocket(
557                    InstanceManager.getDefault(DigitalActionManager.class), setLocalVariables);
558            setLocalVariables.setParent(maleSocketSetLocalVariables);
559            _variablesWithValues = setLocalVariables.getMap();
560
561            try {
562                maleSocketMany.getChild(maleSocketMany.getChildCount()-1).connect(maleSocketSetLocalVariables);
563            } catch (SocketAlreadyConnectedException e) {
564                log.error("Exception when creating error handling LogixNG: ", e);
565            }
566
567            RunFinally runFinally = new RunFinally("IQDA:JMRI:LogixNGTableIcon", null, this::handleResult, _myData);
568            MaleSocket maleSocketRunFinally = new DefaultMaleDigitalActionSocket(
569                    InstanceManager.getDefault(DigitalActionManager.class), runFinally);
570            runFinally.setParent(maleSocketRunFinally);
571
572            try {
573                maleSocketMany.getChild(maleSocketMany.getChildCount()-1).connect(maleSocketRunFinally);
574            } catch (SocketAlreadyConnectedException e) {
575                log.error("Exception when creating error handling LogixNG: ", e);
576            }
577
578            validateModuleAction.addParameter("__iconId__", SymbolTable.InitialValueType.LocalVariable, "iconId", Module.ReturnValueType.None, null);
579            validateModuleAction.addParameter("__tableName__", SymbolTable.InitialValueType.LocalVariable, "tableName", Module.ReturnValueType.None, null);
580            validateModuleAction.addParameter("__row__", SymbolTable.InitialValueType.LocalVariable, "row", Module.ReturnValueType.None, null);
581            validateModuleAction.addParameter("__column__", SymbolTable.InitialValueType.LocalVariable, "column", Module.ReturnValueType.None, null);
582            validateModuleAction.addParameter("__type__", SymbolTable.InitialValueType.LocalVariable, "type", Module.ReturnValueType.None, null);
583            validateModuleAction.addParameter("__oldValue__", SymbolTable.InitialValueType.LocalVariable, "oldValue", Module.ReturnValueType.None, null);
584            validateModuleAction.addParameter("__newValue__", SymbolTable.InitialValueType.LocalVariable, "newValue", Module.ReturnValueType.LocalVariable, "newValue");
585            MaleSocket maleSocket = new DefaultMaleDigitalActionSocket(InstanceManager.getDefault(DigitalActionManager.class), validateModuleAction);
586            validateModuleAction.setParent(maleSocket);
587            try {
588                runFinally.getChild(0).connect(maleSocket);
589            } catch (SocketAlreadyConnectedException e) {
590                log.error("Exception when creating error handling LogixNG: ", e);
591            }
592            List<String> errors = new ArrayList<>();
593            validateModuleLogixNG.setParentForAllChildren(errors);
594            if (!errors.isEmpty()) {
595                for (String s : errors) {
596                    log.error("Error: {}", s);
597                }
598            }
599        }
600
601        private void callValidateModule(Object val, int row, int col) {
602            if (_validateModule == null ||
603                    !_validateModule.getBean().getRootSocket().isConnected()) {
604                _namedTable.getBean().setCell(val, row+1, col+1);
605                return;
606            }
607            validateModuleAction.getSelectNamedBean().setNamedBean(_validateModule);
608
609            NamedTable table = _namedTable.getBean();
610            _variablesWithValues.put("iconId", LogixNGTableIcon.this.getId());
611            _variablesWithValues.put("tableName", _namedTable.getName());
612            _variablesWithValues.put("row", table.getCell(row+1, 0));
613            _variablesWithValues.put("column", table.getCell(0, col+1));
614            _variablesWithValues.put("type", _headers[col]._type);
615            _variablesWithValues.put("oldValue", table.getCell(row+1, col+1));
616            _variablesWithValues.put("newValue", val);
617            _myData.setValues(_namedTable.getBean(), row, col);
618            validateModuleConditionalNG.execute();
619        }
620
621        private void handleResult(ConditionalNG conditionalNG, Exception ex, RunFinally.Data data) {
622            if (ex != null) {
623                if (ex instanceof ValidationErrorException) {
624                    JOptionPane.showMessageDialog(LogixNGTableIcon.this,
625                            ex.getMessage(),
626                            Bundle.getMessage("LogixNGTableIcon_ValidationError"),
627                            JOptionPane.ERROR_MESSAGE);
628                } else {
629                    JOptionPane.showMessageDialog(LogixNGTableIcon.this,
630                            ex.getMessage(),
631                            Bundle.getMessage("LogixNGTableIcon_Error"),
632                            JOptionPane.ERROR_MESSAGE);
633                }
634            } else {
635                if (!(data instanceof MyData)) {
636                    throw new IllegalArgumentException("data is not a MyData");
637                }
638                MyData myData = (MyData)data;
639                Object newValue = conditionalNG.getSymbolTable().getValue("newValue");
640                myData._namedTable.setCell(newValue, myData._row+1, myData._col+1);
641                TableModel.this.fireTableCellUpdated(myData._row, myData._col);
642            }
643        }
644
645        @Override
646        public void propertyChange(PropertyChangeEvent evt) {
647            if (evt instanceof NamedTablePropertyChangeEvent) {
648                var tableEvt = (NamedTablePropertyChangeEvent)evt;
649                if (NamedTable.PROPERTY_CELL_CHANGED.equals(evt.getPropertyName())) {
650                    fireTableCellUpdated(tableEvt.getRow()-1, tableEvt.getColumn()-1);
651                }
652            }
653        }
654
655        private void dispose() {
656            if (_namedTable != null) {
657                _namedTable.getBean().removePropertyChangeListener(this);
658            }
659        }
660
661    }
662
663
664    private static class MyData implements RunFinally.Data {
665
666        private NamedTable _namedTable;
667        private int _row;
668        private int _col;
669
670        private void setValues(NamedTable namedTable, int row, int col) {
671            this._namedTable = namedTable;
672            this._row = row;
673            this._col = col;
674        }
675    }
676
677
678    private class RowHeaderListModel extends AbstractListModel<Object> {
679        @Override
680        public int getSize() {
681            return _tableModel.getTable().numRows();
682        }
683
684        @Override
685        public Object getElementAt(int index) {
686            // Ensure the header has at least five characters and ensure
687            // there are at least two spaces at the end since the last letter
688            // doesn't fully fit at the row.
689            Object data = _tableModel.getTable().getCell(index+1, 0);
690            String padding = "  ";     // Two spaces
691            String str = data != null ? data.toString().concat(padding) : padding;
692            return str.length() < 5 ? str.concat("     ").substring(0, 7) : str;
693        }
694
695    }
696
697
698    private static final class RowHeaderRenderer extends JLabel implements ListCellRenderer<Object> {
699
700        RowHeaderRenderer(JTable table) {
701            JTableHeader header = table.getTableHeader();
702            setOpaque(true);
703            setBorder(UIManager.getBorder("TableHeader.cellBorder"));
704            setHorizontalAlignment(CENTER);
705            setForeground(header.getForeground());
706            setBackground(header.getBackground());
707            setFont(header.getFont());
708        }
709
710        @Override
711        public Component getListCellRendererComponent(JList<?> list, Object value,
712                int index, boolean isSelected, boolean cellHasFocus) {
713            setText((value == null) ? "" : value.toString());
714            return this;
715        }
716    }
717
718
719    private static class ModuleItem {
720
721        private final Module _module;
722
723        public ModuleItem(Module m) {
724            _module = m;
725        }
726
727        @Override
728        public String toString() {
729            if (_module == null) return "";
730            else return _module.getDisplayName();
731        }
732    }
733
734
735    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogixNGTableIcon.class);
736}