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