001/*============================================================================*
002 * WARNING      This class contains automatically modified code.      WARNING *
003 *                                                                            *
004 * The method initComponents() and the variable declarations between the      *
005 * "// Variables declaration - do not modify" and                             *
006 * "// End of variables declaration" comments will be overwritten if modified *
007 * by hand. Using the NetBeans IDE to edit this file is strongly recommended. *
008 *                                                                            *
009 * See http://jmri.org/help/en/html/doc/Technical/NetBeansGUIEditor.shtml for *
010 * more information.                                                          *
011 *============================================================================*/
012package jmri.util.usb;
013
014import java.awt.Image;
015import java.io.UnsupportedEncodingException;
016import java.util.List;
017import javax.swing.ImageIcon;
018import javax.swing.event.TreeSelectionEvent;
019import javax.swing.event.TreeSelectionListener;
020import javax.swing.table.AbstractTableModel;
021import javax.swing.tree.DefaultMutableTreeNode;
022import javax.swing.tree.DefaultTreeCellRenderer;
023import javax.swing.tree.DefaultTreeModel;
024import javax.swing.tree.TreeNode;
025import javax.swing.tree.TreePath;
026import javax.swing.tree.TreeSelectionModel;
027import javax.usb.UsbDevice;
028import javax.usb.UsbDisconnectedException;
029import javax.usb.UsbException;
030import javax.usb.UsbHostManager;
031import javax.usb.UsbHub;
032import javax.usb.UsbPort;
033import javax.usb.event.UsbServicesEvent;
034import javax.usb.event.UsbServicesListener;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038/**
039 *
040 * @author Randall Wood (C) 2017
041 */
042public class UsbBrowserPanel extends javax.swing.JPanel {
043
044    private UsbTreeNode root;
045    private final UsbDeviceTableModel deviceModel = new UsbDeviceTableModel();
046    private final static Logger log = LoggerFactory.getLogger(UsbBrowserPanel.class);
047    private transient final UsbServicesListener usbServicesListener = new UsbServicesListener() {
048        @Override
049        public void usbDeviceAttached(UsbServicesEvent use) {
050            // subtler method to add usbDevice to tree
051            UsbDevice usbDevice = use.getUsbDevice();
052            UsbPort usbPort = usbDevice.getParentUsbPort();
053            if (usbPort != null) {
054                UsbDevice parentUsbDevice = usbPort.getUsbHub();
055                UsbTreeNode parentNode = findNodeForDevice(root, parentUsbDevice);
056                if (parentNode != null) {
057                    UsbTreeNode node = new UsbTreeNode(usbDevice);
058                    parentNode.add(node);
059                    if (usbTree != null) {
060                        TreePath selection = usbTree.getSelectionPath();
061                        ((DefaultTreeModel) usbTree.getModel()).nodeChanged(parentNode);
062                        // .nodeChanged(parent) isn't enough
063                        ((DefaultTreeModel) usbTree.getModel()).reload(root);
064                        usbTree.setSelectionPath(selection);
065                    }
066                    return;
067                }
068            }
069            UsbTreeNode root = UsbBrowserPanel.this.getRootNode();
070            root.removeAllChildren();
071            UsbBrowserPanel.this.buildTree(root);
072        }
073
074        @Override
075        public void usbDeviceDetached(UsbServicesEvent use) {
076            // subtler method to remove usbDevice from tree
077            UsbTreeNode root = UsbBrowserPanel.this.getRootNode();
078            UsbDevice usbDevice = use.getUsbDevice();
079            UsbTreeNode usbTreeNode = findNodeForDevice(root, usbDevice);
080            if (usbTreeNode != null) {
081                TreeNode parentTreeNode = usbTreeNode.getParent();
082                usbTreeNode.removeFromParent();
083                if (usbTree != null) {
084                    TreePath selection = usbTree.getSelectionPath();
085                    if (parentTreeNode != null) {
086                        ((DefaultTreeModel) usbTree.getModel()).reload(parentTreeNode);
087                    } else {
088                        ((DefaultTreeModel) usbTree.getModel()).reload(root);
089                    }
090                    usbTree.setSelectionPath(selection);
091                }
092            } else {
093                root.removeAllChildren();
094                UsbBrowserPanel.this.buildTree(root);
095            }
096        }
097    };
098    private final TreeSelectionListener treeSelectionListener = (TreeSelectionEvent e) -> {
099        UsbTreeNode node = (UsbTreeNode) this.usbTree.getLastSelectedPathComponent();
100        if (node != null) {
101            deviceModel.setNode(node);
102        } else {
103            this.usbTree.setSelectionPath(e.getNewLeadSelectionPath());
104        }
105    };
106
107    /**
108     * Create new UsbBrowserPanel.
109     */
110    public UsbBrowserPanel() {
111        root = getRootNode();
112        buildTree(root);
113        if (root.getUserObject() != null) {
114            try {
115                UsbHostManager.getUsbServices().addUsbServicesListener(usbServicesListener);
116            } catch (UsbException | SecurityException ex) {
117                log.error("Unable to get root USB hub.", ex);
118            }
119        }
120        initComponents();
121        for (int i = 0; i < usbTree.getRowCount(); i++) {
122            usbTree.expandRow(i);
123        }
124    }
125
126    /*
127     * Protected method to set and retrieve the root node.
128     *
129     * This is partially in place for testing purposes, as
130     * this allows injection of a pre-defined root node by
131     * by overriding this method.
132     */
133    protected UsbTreeNode getRootNode() {
134       if(root == null ) {
135          root = new UsbTreeNode();
136       }
137       return root;
138    }
139
140    private void buildTree(UsbTreeNode root) {
141        Object userObject = root.getUserObject();
142        if (userObject != null && ((UsbDevice) userObject).isUsbHub()) {
143            UsbHub usbHub = (UsbHub) userObject;
144            
145            @SuppressWarnings("unchecked") // cast required by UsbHub API
146            List<UsbDevice> usbDevices = usbHub.getAttachedUsbDevices();
147            
148            usbDevices.forEach((UsbDevice usbDevice) -> {
149                UsbTreeNode node = new UsbTreeNode(usbDevice);
150                log.debug("Adding {} to {}, depth: {}", node, root, node.getLevel());
151                buildTree(node);
152                root.add(node);
153            });
154        }
155        // prevent NPE if called in constructor
156        if (usbTree != null) {
157            TreePath selection = usbTree.getSelectionPath();
158            ((DefaultTreeModel) usbTree.getModel()).reload(root);
159            usbTree.setSelectionPath(selection);
160        }
161    }
162
163    /*
164     * recursively search all children of root for usb device
165     */
166    private UsbTreeNode findNodeForDevice(UsbTreeNode root, UsbDevice usbDevice) {
167        if (!root.isLeaf()) {
168            for (int idx = 0; idx < root.getChildCount(); idx++) {
169                TreeNode treeNode = root.getChildAt(idx);
170                if (treeNode instanceof UsbTreeNode) {
171                    UsbTreeNode usbTreeNode = (UsbTreeNode) treeNode;
172                    UsbDevice tryUsbDevice = usbTreeNode.getUsbDevice();
173                    log.debug("trying device: {}", tryUsbDevice);
174                    if ((tryUsbDevice != null) && (tryUsbDevice == usbDevice)) {
175                        return usbTreeNode;
176                    }
177                    UsbTreeNode result = findNodeForDevice(usbTreeNode, usbDevice);
178                    if (result != null) {
179                        return result;
180                    }
181                }
182            }
183        }
184        return null;
185    }
186
187    public void dispose() {
188        try {
189            UsbHostManager.getUsbServices().removeUsbServicesListener(usbServicesListener);
190        } catch (UsbException | SecurityException ex) {
191            // silently ignore, since it was logged when this panel was constructed
192        }
193    }
194
195    /**
196     * This method is called from within the constructor to initialize the form.
197     * WARNING: Do NOT modify this code. The content of this method is always
198     * regenerated by the Form Editor.
199     */
200    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
201    private void initComponents() {
202
203        jSplitPane1 = new javax.swing.JSplitPane();
204        jScrollPane1 = new javax.swing.JScrollPane(this.usbTree);
205        usbTree = new javax.swing.JTree(this.root);
206        jScrollPane2 = new javax.swing.JScrollPane();
207        detailsTable = new javax.swing.JTable();
208
209        jSplitPane1.setBorder(null);
210        jSplitPane1.setResizeWeight(0.5);
211
212        jScrollPane1.setBorder(null);
213
214        usbTree.setCellRenderer(new UsbTreeCellRenderer());
215        usbTree.setRootVisible(false);
216        usbTree.setShowsRootHandles(true);
217        usbTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
218        usbTree.addTreeSelectionListener(this.treeSelectionListener);
219        jScrollPane1.setViewportView(usbTree);
220
221        jSplitPane1.setLeftComponent(jScrollPane1);
222
223        detailsTable.setModel(this.deviceModel);
224        detailsTable.setCellSelectionEnabled(true);
225        jScrollPane2.setViewportView(detailsTable);
226
227        jSplitPane1.setRightComponent(jScrollPane2);
228
229        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
230        this.setLayout(layout);
231        layout.setHorizontalGroup(
232            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
233            .addComponent(jSplitPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 372, Short.MAX_VALUE)
234        );
235        layout.setVerticalGroup(
236            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
237            .addComponent(jSplitPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 260, Short.MAX_VALUE)
238        );
239    }// </editor-fold>//GEN-END:initComponents
240
241
242    // Variables declaration - do not modify//GEN-BEGIN:variables
243    private javax.swing.JTable detailsTable;
244    private javax.swing.JScrollPane jScrollPane1;
245    private javax.swing.JScrollPane jScrollPane2;
246    private javax.swing.JSplitPane jSplitPane1;
247    private javax.swing.JTree usbTree;
248    // End of variables declaration//GEN-END:variables
249
250    protected static class UsbTreeNode extends DefaultMutableTreeNode {
251
252        public UsbTreeNode() {
253            this(null);
254        }
255
256        public UsbTreeNode(UsbDevice usbDevice) {
257            super();
258
259            if (usbDevice == null) {
260                try {
261                    userObject = UsbHostManager.getUsbServices().getRootUsbHub();
262                    log.debug("Using root usbDevice {}", userObject);
263                } catch (UsbException | SecurityException ex) {
264                    log.error("Unable to get root USB hub.", ex);
265                    userObject = null;
266                }
267            } else {
268                log.debug("Description of {} is\n{}", usbDevice, usbDevice.getUsbDeviceDescriptor());
269                userObject = usbDevice;
270            }
271        }
272
273        public UsbDevice getUsbDevice() {
274            return (UsbDevice) userObject;
275        }
276
277        public void setUsbDevice(UsbDevice usbDevice) {
278            userObject = usbDevice;
279        }
280
281        @Override
282        public boolean isLeaf() {
283            if (userObject instanceof UsbHub) {
284                return false;
285            }
286            return super.isLeaf();
287        }
288
289        @Override
290        public String toString() {
291            if (userObject == null) {
292                return Bundle.getMessage("UnableToGetUsbRootHub");
293            } else if (userObject instanceof UsbDevice) {
294                String name = UsbUtil.getFullProductName((UsbDevice) userObject);
295                if (name != null) {
296                    return name;
297                }
298            }
299            return super.toString();
300        }
301    }   // class UsbTreeNode
302
303    private static class UsbDeviceTableModel extends AbstractTableModel {
304
305        private UsbTreeNode node = null;
306
307        @Override
308        public int getRowCount() {
309            return ((node != null) && (node.getUsbDevice() != null)) ? 11 : 1;
310        }
311
312        @Override
313        public int getColumnCount() {
314            return ((node != null) && (node.getUsbDevice() != null)) ? 2 : 1;
315        }
316
317        @Override
318        public String getColumnName(int columnIndex) {
319            return ""; // NOI18N
320        }
321
322        @Override
323        public Object getValueAt(int rowIndex, int columnIndex) {
324            if ((node == null) || (node.getUsbDevice() == null)) {
325                return Bundle.getMessage("EmptySelection");
326            }
327            switch (columnIndex) {
328                case 0:
329                    switch (rowIndex) {
330                        case 0:
331                            return Bundle.getMessage("UsbDeviceManufacturer");
332                        case 1:
333                            return Bundle.getMessage("UsbDeviceProduct");
334                        case 2:
335                            return Bundle.getMessage("UsbDeviceSerial");
336                        case 3:
337                            return Bundle.getMessage("UsbDeviceVendorId");
338                        case 4:
339                            return Bundle.getMessage("UsbDeviceProductId");
340                        case 5:
341                            return Bundle.getMessage("UsbDeviceClass");
342                        case 6:
343                            return Bundle.getMessage("UsbDeviceSubClass");
344                        case 7:
345                            return Bundle.getMessage("UsbDeviceProtocol");
346                        case 8:
347                            return Bundle.getMessage("UsbDeviceReleaseNumber");
348                        case 9:
349                            return Bundle.getMessage("UsbDeviceNumConfigurations");
350                        case 10:
351                            return Bundle.getMessage("UsbDeviceLocation");
352                        default:
353                            break;
354                    }
355                    break;
356                case -1:
357                case 1:
358                    try {
359                        switch (rowIndex) {
360                            case 0:
361                                return node.getUsbDevice().getManufacturerString();
362                            case 1:
363                                return node.getUsbDevice().getProductString();
364                            case 2:
365                                return node.getUsbDevice().getSerialNumberString();
366                            case 3:
367                                return String.format("%04x", node.getUsbDevice().getUsbDeviceDescriptor().idVendor());
368                            case 4:
369                                return String.format("%04x", node.getUsbDevice().getUsbDeviceDescriptor().idProduct());
370                            case 5:
371                                return String.format("%02X", node.getUsbDevice().getUsbDeviceDescriptor().bDeviceClass());
372                            case 6:
373                                return String.format("%02X", node.getUsbDevice().getUsbDeviceDescriptor().bDeviceSubClass());
374                            case 7:
375                                return String.format("%02X", node.getUsbDevice().getUsbDeviceDescriptor().bDeviceProtocol());
376                            case 8:
377                                return String.format("%04x", node.getUsbDevice().getUsbDeviceDescriptor().bcdDevice());
378                            case 9:
379                                return node.getUsbDevice().getUsbDeviceDescriptor().bNumConfigurations();
380                            case 10:
381                                return UsbUtil.getLocation(node.getUsbDevice());
382                            default:
383                                return null;
384                        }
385                    } catch (UsbDisconnectedException ex) {
386                        node.setUsbDevice(null);
387                    } catch (UnsupportedEncodingException | UsbException ex) {
388                        log.error("Unable to get USB device property.", ex);
389                    }
390                    break;
391                default:
392                    return null;
393            }
394            return null;
395        }
396
397        public void setNode(UsbTreeNode node) {
398            UsbTreeNode old = this.node;
399            this.node = node;
400            if (((old == null) && (node != null)) || ((old != null) && (node == null))) {
401                fireTableStructureChanged();
402            }
403            fireTableDataChanged();
404        }
405    }
406
407    private final static class UsbTreeCellRenderer extends DefaultTreeCellRenderer {
408
409        public UsbTreeCellRenderer() {
410            int width = getOpenIcon().getIconWidth();
411            int height = getOpenIcon().getIconHeight();
412            try {
413                setOpenIcon(new ImageIcon(new ImageIcon(getClass().getResource("/jmri/util/usb/topology.png"))
414                        .getImage()
415                        .getScaledInstance(width, height, Image.SCALE_SMOOTH)));
416            } catch (NullPointerException ex) {
417                log.error("Unable to get resource /jmri/util/usb/topology.png from JMRI classpath");
418            }
419            try {
420                setClosedIcon(new ImageIcon(new ImageIcon(getClass().getResource("/jmri/util/usb/topology.png"))
421                        .getImage()
422                        .getScaledInstance(width, height, Image.SCALE_SMOOTH)));
423            } catch (NullPointerException ex) {
424                log.error("Unable to get resource /jmri/util/usb/topology.png from JMRI classpath");
425            }
426            try {
427                setLeafIcon(new ImageIcon(new ImageIcon(getClass().getResource("/jmri/util/usb/usb.png"))
428                        .getImage()
429                        .getScaledInstance(width, height, Image.SCALE_SMOOTH)));
430            } catch (NullPointerException ex) {
431                log.error("Unable to get resource /jmri/util/usb/usb.png from JMRI classpath");
432            }
433        }
434
435    }
436}