001package jmri.jmrit.display.palette;
002
003import java.awt.BorderLayout;
004import java.awt.Dimension;
005import java.awt.datatransfer.DataFlavor;
006import java.awt.datatransfer.UnsupportedFlavorException;
007import java.awt.event.ActionEvent;
008import java.awt.event.ActionListener;
009import java.io.IOException;
010import java.util.HashMap;
011import java.util.Map.Entry;
012import javax.swing.JButton;
013import javax.swing.JDialog;
014import javax.swing.JLabel;
015import javax.swing.JOptionPane;
016import javax.swing.JPanel;
017import javax.swing.JScrollPane;
018import javax.swing.JTable;
019import javax.swing.JTextField;
020import javax.swing.SwingConstants;
021import javax.swing.event.ListSelectionEvent;
022import javax.swing.event.ListSelectionListener;
023import jmri.NamedBean;
024import jmri.jmrit.beantable.AbstractTableAction;
025import jmri.jmrit.beantable.BeanTableFrame;
026import jmri.jmrit.beantable.TurnoutTableAction;
027import jmri.jmrit.beantable.SensorTableAction;
028import jmri.jmrit.beantable.LightTableAction;
029import jmri.jmrit.beantable.ReporterTableAction;
030import jmri.jmrit.beantable.SignalHeadTableAction;
031import jmri.jmrit.beantable.SignalMastTableAction;
032import jmri.jmrit.beantable.MemoryTableAction;
033import jmri.jmrit.catalog.DragJLabel;
034import jmri.jmrit.catalog.NamedIcon;
035import jmri.jmrit.display.DisplayFrame;
036import jmri.jmrit.display.Editor;
037import jmri.jmrit.display.LightIcon;
038import jmri.jmrit.display.SensorIcon;
039import jmri.jmrit.display.SignalMastIcon;
040import jmri.jmrit.display.TurnoutIcon;
041import jmri.jmrit.picker.PickListModel;
042import org.slf4j.Logger;
043import org.slf4j.LoggerFactory;
044
045/**
046 * FamilyItemPanel extension for placing of CPE item types that come from tool Tables
047 * - e.g. Turnouts, Sensors, Lights, Signal Heads, etc.
048 *
049 * @author Pete Cressman Copyright (c) 2010, 2011, 2020
050 */
051
052public class TableItemPanel<E extends NamedBean> extends FamilyItemPanel implements ListSelectionListener {
053
054    int ROW_HEIGHT;
055
056    protected JTable _table;
057    protected PickListModel<E> _model;
058    AbstractTableAction<E> _tableAction;
059
060    JScrollPane _scrollPane;
061    JDialog _addTableDialog;
062    JTextField _sysNametext = new JTextField();
063    JTextField _userNametext = new JTextField();
064    JButton _addTableButton;
065
066    /**
067     * Constructor for all table types. When item is a bean, the itemType is the
068     * name key for the item in jmri.NamedBeanBundle.properties.
069     *
070     * @param parentFrame the enclosing parentFrame
071     * @param type        item type
072     * @param family      icon family
073     * @param model       list model
074     */
075    @SuppressWarnings("unchecked")
076    public TableItemPanel(DisplayFrame parentFrame, String type, String family, PickListModel<E> model) {
077        super(parentFrame, type, family);
078        _model = model;
079        _tableAction = (AbstractTableAction<E>)getTableAction(type);
080    }
081
082    /**
083     * Init for creation insert table.
084     */
085    @Override
086    public void init() {
087        if (!_initialized) {
088            super.init();
089            add(initTablePanel(_model), 0); // top of Panel
090        }
091        hideIcons();
092    }
093
094    /**
095     * Init for update of existing indicator turnout _bottom3Panel has "Update
096     * Panel" button put into _bottom1Panel.
097     */
098    @Override
099    public void init(ActionListener doneAction, HashMap<String, NamedIcon> iconMap) {
100        super.init(doneAction, iconMap);
101        add(initTablePanel(_model), 0);
102    }
103
104    private AbstractTableAction<?> getTableAction(String type) {
105        switch (type) {
106            case "Turnout":
107            case "IndicatorTO":
108                return new TurnoutTableAction(Bundle.getMessage("CreateNewItem"));
109            case "Sensor":
110            case "MultiSensor":
111                return new SensorTableAction(Bundle.getMessage("CreateNewItem"));
112            case "SignalHead":
113                return new SignalHeadTableAction(Bundle.getMessage("CreateNewItem"));
114            case "SignalMast":
115                return new SignalMastTableAction(Bundle.getMessage("CreateNewItem"));
116            case "Memory":
117                return new MemoryTableAction(Bundle.getMessage("CreateNewItem"));
118            case "Light":
119                return new LightTableAction(Bundle.getMessage("CreateNewItem"));
120            case "Reporter":
121                return new ReporterTableAction(Bundle.getMessage("CreateNewItem"));
122            default:
123                return null;
124        }
125    }
126    /*
127     * Top Panel.
128     */
129    protected JPanel initTablePanel(PickListModel<E> model) {
130        _table = model.makePickTable();
131        ROW_HEIGHT = _table.getRowHeight();
132        JPanel topPanel = new JPanel();
133        topPanel.setLayout(new BorderLayout());
134        topPanel.add(new JLabel(model.getName(), SwingConstants.CENTER), BorderLayout.NORTH);
135        _scrollPane = new JScrollPane(_table);
136        int cnt = Math.max(Math.min(10, _table.getRowCount()), 4);  // at least 4 rows, no more than 10
137        _scrollPane.setPreferredSize(new Dimension(_scrollPane.getPreferredSize().width, cnt*ROW_HEIGHT));
138        topPanel.add(_scrollPane, BorderLayout.CENTER);
139        topPanel.setToolTipText(Bundle.getMessage("ToolTipDragTableRow"));
140
141        JPanel panel = new JPanel();
142        _addTableButton = new JButton(Bundle.getMessage("CreateNewItem"));
143        _addTableButton.addActionListener(_tableAction);
144        _addTableButton.setToolTipText(Bundle.getMessage("ToolTipAddToTable"));
145        panel.add(_addTableButton);
146        JButton clearSelectionButton = new JButton(Bundle.getMessage("ClearSelection"));
147        clearSelectionButton.addActionListener(a -> {
148            _table.clearSelection();
149            hideIcons();
150        });
151        clearSelectionButton.setToolTipText(Bundle.getMessage("ToolTipClearSelection"));
152        panel.add(clearSelectionButton);
153        topPanel.add(panel, BorderLayout.SOUTH);
154        _table.getSelectionModel().addListSelectionListener(this);
155        _table.setToolTipText(Bundle.getMessage("ToolTipDragTableRow"));
156        _scrollPane.setToolTipText(Bundle.getMessage("ToolTipDragTableRow"));
157        topPanel.setToolTipText(Bundle.getMessage("ToolTipDragTableRow"));
158        return topPanel;
159    }
160
161    protected void makeAddToTableWindow() {
162        _addTableDialog = new JDialog(_frame, Bundle.getMessage("AddToTableTitle"), true);
163
164        ActionListener cancelListener = this::cancelPressed;
165        ActionListener okListener = new ActionListener() {
166            /** {@inheritDoc} */
167            @Override
168            public void actionPerformed(ActionEvent a) {
169                addToTable();
170            }
171        };
172        jmri.util.swing.JmriPanel addPanel = new jmri.jmrit.beantable.AddNewDevicePanel(
173                _sysNametext, _userNametext, "addToTable", okListener, cancelListener);
174        _addTableDialog.getContentPane().add(addPanel);
175        _addTableDialog.pack();
176        _addTableDialog.setSize(_frame.getSize().width - 20, _addTableDialog.getPreferredSize().height);
177        _addTableDialog.setLocation(10, 35);
178        _addTableDialog.setLocationRelativeTo(_frame);
179        _addTableDialog.toFront();
180        _addTableDialog.setVisible(true);
181    }
182
183    void cancelPressed(ActionEvent e) {
184        _addTableDialog.setVisible(false);
185        _addTableDialog.dispose();
186        _addTableDialog = null;
187    }
188
189    protected void addToTable() {
190        String sysname = _sysNametext.getText();
191        if (sysname != null && sysname.length() > 1) {
192            String uname = _userNametext.getText();
193            if (uname != null && uname.trim().length() == 0) {
194                uname = null;
195            }
196            try {
197                E bean = _model.addBean(sysname, uname);
198                if (bean != null) {
199                    int setRow = _model.getIndexOf(bean);
200                    if (log.isDebugEnabled()) {
201                        log.debug("addToTable: row = {}, bean = {}", setRow, bean.getDisplayName());
202                    }
203                    _table.setRowSelectionInterval(setRow, setRow);
204                    _scrollPane.getVerticalScrollBar().setValue(setRow * ROW_HEIGHT);
205                }
206                _addTableDialog.dispose();
207            } catch (IllegalArgumentException ex) {
208                JOptionPane.showMessageDialog(_frame, ex.getMessage(),
209                        Bundle.getMessage("WarningTitle"), JOptionPane.WARNING_MESSAGE);
210            }
211        }
212        _sysNametext.setText("");
213        _userNametext.setText("");
214    }
215
216    /**
217     * Used by Panel Editor to make the final installation of the icon(s) into
218     * the user's Panel.
219     * <p>
220     * Note: the selection is cleared. When two successive calls are made, the
221     * 2nd will always return null, regardless of the 1st return.
222     *
223     * @return bean selected in the table
224     */
225    public E getTableSelection() {
226        int row = _table.getSelectedRow();
227        row = _table.convertRowIndexToModel(row);
228        if (row >= 0) {
229            E b = _model.getBeanAt(row);
230            _table.clearSelection();
231            if (log.isDebugEnabled()) {
232                log.debug("getTableSelection: row = {}, bean = {}", row, (b == null ? "null" : b.getDisplayName()));
233            }
234            return b;
235        } else if (log.isDebugEnabled()) {
236            log.debug("getTableSelection: row = {}", row);
237        }
238        return null;
239    }
240
241    public void setSelection(E bean) {
242        int row = _model.getIndexOf(bean);
243        row = _table.convertRowIndexToView(row);
244        log.debug("setSelection: NamedBean = {}, row = {}", bean, row);
245        if (row >= 0) {
246            _table.addRowSelectionInterval(row, row);
247            _scrollPane.getVerticalScrollBar().setValue(row * ROW_HEIGHT);
248        } else {
249            valueChanged(null);
250        }
251    }
252
253    /**
254     * ListSelectionListener action.
255     */
256    @Override
257    public void valueChanged(ListSelectionEvent e) {
258        if (_table == null) {
259            return;
260        }
261        int row = _table.getSelectedRow();
262        log.debug("Table valueChanged: row = {}", row);
263        if (_updateButton != null) {
264            if (row >= 0) {
265                _updateButton.setEnabled(true);
266                _updateButton.setToolTipText(null);
267
268            } else {
269                _updateButton.setEnabled(false);
270                _updateButton.setToolTipText(Bundle.getMessage("ToolTipPickFromTable"));
271            }
272        }
273        hideIcons();
274    }
275
276    protected E getDeviceNamedBean() {
277        int row = _table.getSelectedRow();
278        log.debug("getDeviceNamedBean: from table \"{}\" at row {}", _itemType, row);
279        if (row < 0) {
280            return null;
281        }
282        return _model.getBySystemName((String) _table.getValueAt(row, 0));
283    }
284
285    @Override
286    protected String getDisplayKey() {
287        if (_itemType.equals("Turnout")) {
288            return "TurnoutStateClosed";
289        }
290        if (_itemType.equals("Sensor")) {
291            return "SensorStateActive";
292        }
293        if (_itemType.equals("Light")) {
294            return "StateOn";
295        }
296        return null;
297    }
298
299    @Override
300    public void closeDialogs() {
301        if (_tableAction != null) {
302            _tableAction.dispose();
303            BeanTableFrame<E> frame = _tableAction.getFrame();
304            if (frame != null) {
305                frame.dispose();
306            }
307        }
308        super.closeDialogs();
309    }
310
311    /** {@inheritDoc} */
312    @Override
313    protected JLabel getDragger(DataFlavor flavor, HashMap<String, NamedIcon> map, NamedIcon icon) {
314        return new IconDragJLabel(flavor, map, icon);
315    }
316
317    protected class IconDragJLabel extends DragJLabel {
318
319        HashMap<String, NamedIcon> iMap;
320
321        public IconDragJLabel(DataFlavor flavor, HashMap<String, NamedIcon> map, NamedIcon icon) {
322            super(flavor, icon);
323            iMap = map;
324        }
325
326        /** {@inheritDoc} */
327        @Override
328        protected boolean okToDrag() {
329            E bean = getDeviceNamedBean();
330            if (bean == null) {
331                JOptionPane.showMessageDialog(this, Bundle.getMessage("noRowSelected"),
332                        Bundle.getMessage("WarningTitle"), JOptionPane.WARNING_MESSAGE);
333                return false;
334            }
335            return true;
336        }
337
338        /** {@inheritDoc} */
339        @Override
340        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
341            if (!isDataFlavorSupported(flavor)) {
342                return null;
343            }
344            E bean = getDeviceNamedBean();
345            if (bean == null) {
346                return null;
347            }
348            // jmri.NamedBeanHandle<E> bh = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(bean.getDisplayName(), bean);
349
350            Editor editor = _frame.getEditor();
351            if (flavor.isMimeTypeEqual(Editor.POSITIONABLE_FLAVOR)) {
352                switch (_itemType) {
353                    case "Turnout":
354                        TurnoutIcon t = new TurnoutIcon(editor);
355                        t.setTurnout(bean.getDisplayName());
356                        for (Entry<String, NamedIcon> ent : iMap.entrySet()) {
357                            t.setIcon(ent.getKey(), new NamedIcon(ent.getValue()));
358                        }
359                        t.setFamily(_family);
360                        t.setLevel(Editor.TURNOUTS);
361                        return t;
362                    case "Sensor":
363                        SensorIcon s = new SensorIcon(new NamedIcon("resources/icons/smallschematics/tracksegments/circuit-error.gif", "resources/icons/smallschematics/tracksegments/circuit-error.gif"), editor);
364                        for (Entry<String, NamedIcon> ent : iMap.entrySet()) {
365                            s.setIcon(ent.getKey(), new NamedIcon(ent.getValue()));
366                        }
367                        s.setSensor(bean.getDisplayName());
368                        s.setFamily(_family);
369                        s.setLevel(Editor.SENSORS);
370                        return s;
371                    case "SignalMast":
372                        SignalMastIcon sm = new SignalMastIcon(_frame.getEditor());
373                        sm.setSignalMast(bean.getDisplayName());
374                        sm.setLevel(Editor.SIGNALS);
375                        return sm;
376                    case "Light":
377                        LightIcon l = new LightIcon(editor);
378                        l.setOffIcon(iMap.get("StateOff"));
379                        l.setOnIcon(iMap.get("StateOn"));
380                        l.setInconsistentIcon(iMap.get("BeanStateInconsistent"));
381                        l.setUnknownIcon(iMap.get("BeanStateUnknown"));
382                        l.setLight((jmri.Light) bean);
383                        l.setLevel(Editor.LIGHTS);
384                        return l;
385                    default:
386                        return null;
387                }
388            } else if (DataFlavor.stringFlavor.equals(flavor)) {
389                return _itemType + " icons for \"" + bean.getDisplayName() + "\"";
390            }
391            return null;
392        }
393    }
394
395    private final static Logger log = LoggerFactory.getLogger(TableItemPanel.class);
396
397}