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}