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}