001package jmri.jmrit.display;
002
003import java.awt.*;
004import java.awt.datatransfer.DataFlavor;
005import java.awt.datatransfer.Transferable;
006import java.awt.datatransfer.UnsupportedFlavorException;
007import java.awt.dnd.DnDConstants;
008import java.awt.dnd.DropTarget;
009import java.awt.dnd.DropTargetDragEvent;
010import java.awt.dnd.DropTargetDropEvent;
011import java.awt.dnd.DropTargetEvent;
012import java.awt.dnd.DropTargetListener;
013import java.awt.event.ActionEvent;
014import java.awt.event.ActionListener;
015import java.io.IOException;
016import java.text.MessageFormat;
017import java.util.*;
018import java.util.List;
019
020import javax.swing.BorderFactory;
021import javax.swing.Box;
022import javax.swing.BoxLayout;
023import javax.swing.ButtonGroup;
024import javax.swing.JButton;
025import javax.swing.JComponent;
026import javax.swing.JLabel;
027import javax.swing.JPanel;
028import javax.swing.JRadioButton;
029import javax.swing.JSeparator;
030import javax.swing.TransferHandler;
031import javax.swing.event.ListSelectionEvent;
032
033import jmri.CatalogTreeManager;
034import jmri.InstanceManager;
035import jmri.NamedBeanHandle;
036import jmri.Sensor;
037import jmri.CatalogTreeLeaf;
038import jmri.CatalogTreeNode;
039import jmri.jmrit.catalog.NamedIcon;
040import jmri.util.swing.JmriJOptionPane;
041
042/**
043 * Provides a simple editor for creating a MultiSensorIcon object. Allows drops
044 * from icons dragged from a Catalog preview pane. Also implements dragging a
045 * row from the Sensor table to be dropped on a Sensor label
046 * <p>
047 * To work right, the MultiSensorIcon needs to have all images the same size,
048 * but this is not enforced here. It should be. -Done 6/16/09
049 *
050 * @author Bob Jacobsen Copyright (c) 2007
051 * @author Pete Cressman Copyright (c) 2009
052 *
053 */
054public class MultiSensorIconAdder extends IconAdder {
055
056    JRadioButton _updown;
057    JRadioButton _rightleft;
058
059    HashMap<String, NamedBeanHandle<Sensor>> _sensorMap = new HashMap<>();
060
061    public static final String NamedBeanFlavorMime = DataFlavor.javaJVMLocalObjectMimeType
062            + ";class=jmri.NamedBean";
063
064    public MultiSensorIconAdder() {
065        super();
066    }
067
068    public MultiSensorIconAdder(String type) {
069        super(type);
070    }
071
072    @Override
073    public void reset() {
074        _sensorMap = new HashMap<>();
075        super.reset();
076    }
077
078    /**
079     * Build iconMap and orderArray from user's choice of defaults (override).
080     */
081    @Override
082    protected void makeIcons(CatalogTreeNode n) {
083        if (log.isDebugEnabled()) {
084            log.debug("makeIcons from node= {}, numChildren= {}, NumLeaves= {}",
085                    n.toString(), n.getChildCount(), n.getNumLeaves());
086        }
087        _iconMap = new HashMap<>(10);
088        _iconOrderList = new ArrayList<>();
089        ArrayList<CatalogTreeLeaf> list = n.getLeaves();
090        // adjust order of icons
091        for (int i = list.size() - 1; i >= 0; i--) {
092            CatalogTreeLeaf leaf = list.get(i);
093            String name = leaf.getName();
094            String path = leaf.getPath();
095            if ("BeanStateInconsistent".equals(name)) {
096                setIcon(0, name, new NamedIcon(path, path));
097            } else if ("BeanStateUnknown".equals(name)) {
098                setIcon(1, name, new NamedIcon(path, path));
099            } else if ("SensorStateInactive".equals(name)) {
100                setIcon(2, name, new NamedIcon(path, path));
101            } else {
102                int k = Character.digit(name.charAt(name.length() - 1), 10);
103                setIcon(k + 3, name, new NamedIcon(path, path));
104            }
105        }
106    }
107
108    void setMultiIcon(List<MultiSensorIcon.Entry> icons) {
109        for (int i = 0; i < icons.size(); i++) {
110            MultiSensorIcon.Entry entry = icons.get(i);
111            String label = "MultiSensorPosition " + i;
112            String url = entry.icon.getURL();
113            if (url != null) {
114                setIcon(i + 3, label, url);
115                _sensorMap.put(label, entry.namedSensor);
116            }
117        }
118        if (log.isDebugEnabled()) {
119            log.debug("setMultiIcon: Size: sensors= {}, icons= {}",
120                    _sensorMap.size(), _iconMap.size());
121        }
122    }
123
124    /**
125     * First look for a table selection to set the sensor. If not, then look to
126     * change the icon image (super).
127     */
128    @Override
129    protected void doIconPanel() {
130        if (log.isDebugEnabled()) {
131            log.debug("doIconPanel: Sizes: _iconMap= {} _iconOrderList.size()= {}, _sensorMap.size()= {}",
132                    _iconMap.size(), _iconOrderList.size(), _sensorMap.size());
133        }
134        Dimension dim = null;
135        JPanel rowPanel = null;
136        int cnt = 0;
137        for (int i = 3; i < _iconOrderList.size(); i++) {
138            if (rowPanel == null) {
139                rowPanel = new JPanel();
140                rowPanel.setLayout(new BoxLayout(rowPanel, BoxLayout.X_AXIS));
141                rowPanel.add(Box.createHorizontalStrut(STRUT_SIZE));
142            }
143            String key = _iconOrderList.get(i);
144            if (key.equals("placeHolder")) {
145                continue;
146            }
147            JPanel p1 = new JPanel();
148            p1.setLayout(new BoxLayout(p1, BoxLayout.Y_AXIS));
149            String label = MessageFormat.format(Bundle.getMessage("MultiSensorPosition"), cnt + 1);
150            p1.add(new JLabel(label));
151            p1.add(_iconMap.get(key));
152
153            JPanel p2 = new JPanel();
154            JButton delete = new JButton(Bundle.getMessage("ButtonDelete"));
155            ActionListener action = new ActionListener() {
156                String key;
157
158                @Override
159                public void actionPerformed(ActionEvent a) {
160                    delete(key);
161                }
162
163                ActionListener init(String k) {
164                    key = k;
165                    return this;
166                }
167            }.init(key);
168            delete.addActionListener(action);
169            p2.add(delete);
170
171            JPanel p3 = new DropPanel();
172            p3.setLayout(new BoxLayout(p3, BoxLayout.Y_AXIS));
173            JLabel k = new JLabel(key);
174            k.setName(key);
175            k.setVisible(false);
176            p3.add(k);
177            JPanel p4 = new JPanel();
178            p4.add(new JLabel(Bundle.getMessage("BeanNameSensor")));
179            p3.add(p4);
180            p4 = new JPanel();
181            NamedBeanHandle<Sensor> sensor = _sensorMap.get(key);
182            String name = Bundle.getMessage("notSet");
183            Color color = Color.RED;
184            if (sensor != null) {
185                name = sensor.getName();
186                /*name = sensor.getUserName();
187                 if (name == null)  {
188                 name = sensor.getSystemName();
189                 }*/
190                color = Color.BLACK;
191            }
192            p4.setBorder(BorderFactory.createLineBorder(color));
193            p4.add(new JLabel(name));
194            p4.setMaximumSize(p4.getPreferredSize());
195            p3.add(p4);
196            JPanel p13 = new JPanel();
197            p13.setLayout(new BoxLayout(p13, BoxLayout.X_AXIS));
198            p13.add(p3);
199            p13.add(p1);
200
201            JPanel panel = new JPanel();
202            panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
203            panel.add(p13);
204            panel.add(p2);
205            panel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
206
207            rowPanel.add(panel);
208            rowPanel.add(Box.createHorizontalStrut(STRUT_SIZE));
209
210            cnt++;
211            if ((cnt % 3) == 0) {
212                _iconPanel.add(rowPanel);
213                rowPanel = null;
214            }
215            dim = panel.getPreferredSize();
216        }
217        while ((cnt % 3) != 0) {
218            Objects.requireNonNull(rowPanel, "should not have found rowPanel null here");
219            rowPanel.add(Box.createRigidArea(dim));
220            cnt++;
221        }
222        if (rowPanel != null) {
223            _iconPanel.add(rowPanel);
224            _iconPanel.add(Box.createVerticalStrut(STRUT_SIZE));
225        }
226        rowPanel = new JPanel();
227        rowPanel.setLayout(new BoxLayout(rowPanel, BoxLayout.X_AXIS));
228        rowPanel.add(Box.createHorizontalStrut(STRUT_SIZE));
229        for (int i = 0; i < 3; i++) {
230            String key = _iconOrderList.get(i);
231            JPanel p = new JPanel();
232            p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
233            p.add(new JLabel(Bundle.getMessage(key)));
234            p.add(_iconMap.get(key));
235            rowPanel.add(p);
236            rowPanel.add(Box.createHorizontalStrut(STRUT_SIZE));
237        }
238        _iconPanel.add(rowPanel);
239        _iconPanel.add(Box.createVerticalStrut(STRUT_SIZE));
240        this.add(_iconPanel, 0);
241        valueChanged(null);
242        pack();
243    }
244
245    @Override
246    public void complete(ActionListener addIconAction, boolean changeIcon,
247            boolean addToTable, boolean update) {
248        ButtonGroup group = new ButtonGroup();
249        _updown = new JRadioButton(Bundle.getMessage("UpDown"));
250        _rightleft = new JRadioButton(Bundle.getMessage("RightLeft"));
251        _rightleft.setSelected(true);
252        group.add(_updown);
253        group.add(_rightleft);
254        JPanel p = new JPanel();
255        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
256        p.add(_updown);
257        p.add(_rightleft);
258        p.add(Box.createHorizontalStrut(STRUT_SIZE));
259        JButton addIcon = new JButton(Bundle.getMessage("AddMultiSensorIcon"));
260        addIcon.addActionListener((ActionEvent e) -> addIcon());
261        p.add(addIcon);
262        this.add(p);
263        this.add(new JSeparator());
264        super.complete(addIconAction, changeIcon, addToTable, update);
265        _table.setDragEnabled(true);
266        _table.setTransferHandler(new ExportHandler());
267        valueChanged(null);
268    }
269
270    class ExportHandler extends TransferHandler {
271
272        @Override
273        public int getSourceActions(JComponent c) {
274            return COPY;
275        }
276
277        @Override
278        public Transferable createTransferable(JComponent c) {
279            return new TransferableNamedBean();
280        }
281
282        @Override
283        public void exportDone(JComponent c, Transferable t, int action) {
284        }
285    }
286
287    class TransferableNamedBean implements Transferable {
288
289        DataFlavor dataFlavor;
290
291        TransferableNamedBean() {
292            try {
293                dataFlavor = new DataFlavor(NamedBeanFlavorMime);
294            } catch (ClassNotFoundException cnfe) {
295                log.error("Unable to find class", cnfe);
296            }
297        }
298
299        @Override
300        public DataFlavor[] getTransferDataFlavors() {
301            //log.debug("TransferableNamedBean.getTransferDataFlavors");
302            return new DataFlavor[]{dataFlavor};
303        }
304
305        @Override
306        public boolean isDataFlavorSupported(DataFlavor flavor) {
307            //log.debug("TransferableNamedBean.isDataFlavorSupported");
308            return dataFlavor.equals(flavor);
309        }
310
311        @Override
312        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
313            log.debug("TransferableNamedBean.getTransferData");
314            if (isDataFlavorSupported(flavor)) {
315                return getTableSelection();
316            }
317            return null;
318        }
319    }
320
321    private void addIcon() {
322        int index = _iconOrderList.size();
323        String path = "resources/icons/misc/X-red.gif"; //"resources/icons/USS/plate/levers/l-vertical.gif";
324        String label = "MultiSensorPosition " + (index - 3);
325        super.setIcon(index, label, new NamedIcon(path, path));
326        valueChanged(null);
327        if (!_update) {
328            _defaultIcons.addLeaf(label, path);
329            InstanceManager.getDefault(CatalogTreeManager.class).indexChanged(true);
330        }
331        makeIconPanel(!_update);
332        this.invalidate();
333    }
334
335    /**
336     * Activate Add to Panel button when all icons are assigned sensors.
337     *
338     * @param e the triggering event
339     */
340    @Override
341    public void valueChanged(ListSelectionEvent e) {
342        if (_addButton == null) {
343            return;
344        }
345        if (_sensorMap.size() == (_iconMap.size() - 3)) {
346            _addButton.setEnabled(true);
347            _addButton.setToolTipText(null);
348            //checkIconSizes();
349        } else {
350            _addButton.setEnabled(false);
351            _addButton.setToolTipText(Bundle.getMessage("ToolTipAssignSensors"));
352        }
353    }
354
355    void delete(String key) {
356        _iconMap.remove(key);
357        _sensorMap.remove(key);
358        int index = _iconOrderList.indexOf(key);
359        _iconOrderList.remove(key);
360        if (!_update) {
361            _defaultIcons.deleteLeaves(key);
362            //  update labels
363            for (int k = index; k < _iconOrderList.size(); k++) {
364                String label = _iconOrderList.get(k);
365                ArrayList<CatalogTreeLeaf> leaves = _defaultIcons.getLeaves(label);
366                for (CatalogTreeLeaf leaf : leaves) {
367                    String path = leaf.getPath();
368                    _defaultIcons.deleteLeaves(label);
369                    _defaultIcons.addLeaf("MultiSensorPosition " + (k - 3), path);
370                    // break;
371                }
372            }
373            InstanceManager.getDefault(CatalogTreeManager.class).indexChanged(true);
374        }
375        makeIconPanel(!_update);
376    }
377
378    /**
379     * Get a new NamedIcon object for your own use. see NamedIcon
380     * getIcon(String key) in super.
381     *
382     * @param index of key
383     * @return Unique object
384     */
385    public NamedIcon getIcon(int index) {
386        if (index >= _iconOrderList.size()) {
387            JmriJOptionPane.showMessageDialog(this, java.text.MessageFormat.format(
388                    Bundle.getMessage("NoIconAt"), index - 2),
389                    Bundle.getMessage("ErrorTitle"),
390                    JmriJOptionPane.ERROR_MESSAGE);
391            return null;
392        }
393        return (NamedIcon) _iconMap.get(_iconOrderList.get(index)).getIcon();
394    }
395
396    /**
397     * Get a Sensor object for your own use. see NamedIcon getIcon(String
398     * key) in super.
399     *
400     * @param index of key
401     * @return Unique object
402     */
403    public NamedBeanHandle<Sensor> getSensor(int index) {
404        if (index >= _iconOrderList.size()) {
405            JmriJOptionPane.showMessageDialog(this, java.text.MessageFormat.format(
406                    Bundle.getMessage("NoSensorAt"), index - 2),
407                    Bundle.getMessage("ErrorTitle"),
408                    JmriJOptionPane.ERROR_MESSAGE);
409            return null;
410        }
411        return _sensorMap.get(_iconOrderList.get(index));
412    }
413
414    public boolean getUpDown() {
415        return _updown.isSelected();
416    }
417
418    private boolean putSensor(String key, Sensor sensor) {
419        String name = sensor.getDisplayName();
420        log.debug("putSensor: key= {} sensor= {}", key, name);
421        for (NamedBeanHandle<Sensor> sensorNamedBeanHandle : _sensorMap.values()) {
422            if (name.equals(sensorNamedBeanHandle.getName())) {
423                JmriJOptionPane.showMessageDialog(this, MessageFormat.format(Bundle.getMessage("DupSensorName"), name), Bundle.getMessage("ErrorTitle"), JmriJOptionPane.ERROR_MESSAGE);
424                return false;
425            }
426        }
427        _sensorMap.put(key, jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class).getNamedBeanHandle(name, sensor));
428        return true;
429    }
430
431    /**
432     * Enable the active MultiSensor icons to receive dragged icons.
433     */
434    class DropPanel extends JPanel implements DropTargetListener {
435
436        DataFlavor dataFlavor;
437
438        DropPanel() {
439            try {
440                dataFlavor = new DataFlavor(NamedBeanFlavorMime);
441            } catch (ClassNotFoundException cnfe) {
442                log.error("Class not found.", cnfe);
443            }
444            new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, this);
445            //log.debug("DropPanel ctor");
446        }
447
448        @Override
449        public void dragExit(DropTargetEvent dte) {
450        }
451
452        @Override
453        public void dragEnter(DropTargetDragEvent dtde) {
454        }
455
456        @Override
457        public void dragOver(DropTargetDragEvent dtde) {
458            //log.debug("DropPanel.dragOver");
459        }
460
461        @Override
462        public void dropActionChanged(DropTargetDragEvent dtde) {
463        }
464
465        @Override
466        public void drop(DropTargetDropEvent e) {
467            try {
468                Transferable tr = e.getTransferable();
469                if (e.isDataFlavorSupported(dataFlavor)) {
470                    Sensor sensor = (Sensor) tr.getTransferData(dataFlavor);
471                    if (sensor != null) {
472                        e.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
473                        DropTarget target = (DropTarget) e.getSource();
474                        JPanel panel = (JPanel) target.getComponent();
475                        JComponent comp = (JLabel) panel.getComponent(0);
476                        if (putSensor(comp.getName(), sensor)) {
477                            makeIconPanel(!_update);
478                        }
479                        e.dropComplete(true);
480                        if (log.isDebugEnabled()) {
481                            log.debug("DropPanel.drop COMPLETED for {}", comp.getName());
482                        }
483                    } else {
484                        log.debug("DropPanel.drop REJECTED!");
485                        e.rejectDrop();
486                    }
487                }
488            } catch (IOException | UnsupportedFlavorException ioe) {
489                log.debug("DropPanel.drop REJECTED!");
490                e.rejectDrop();
491            }
492        }
493    }
494
495    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MultiSensorIconAdder.class);
496
497}