001package jmri.jmrit.display.switchboardEditor; 002 003import java.awt.*; 004import java.awt.event.*; 005import java.awt.font.FontRenderContext; 006import java.awt.geom.AffineTransform; 007import java.awt.geom.Point2D; 008import java.awt.image.BufferedImage; 009import java.awt.image.RescaleOp; 010import java.io.File; 011import java.io.IOException; 012 013import javax.annotation.CheckForNull; 014import javax.annotation.Nonnull; 015import javax.imageio.ImageIO; 016import javax.swing.*; 017 018import jmri.InstanceManager; 019import jmri.JmriException; 020import jmri.Light; 021import jmri.NamedBean; 022import jmri.NamedBeanHandle; 023import jmri.Sensor; 024import jmri.Turnout; 025import jmri.jmrit.beantable.AddNewDevicePanel; 026import jmri.util.JmriJFrame; 027import jmri.util.SystemType; 028import jmri.util.swing.JmriJOptionPane; 029 030/** 031 * Class for a switchboard interface object. 032 * <p> 033 * Contains a JButton or JPanel to control existing turnouts, sensors and 034 * lights. 035 * Separated from SwitchboardEditor.java in 4.12.3 036 * 037 * @author Egbert Broerse Copyright (c) 2017, 2018, 2020, 2021 038 */ 039public class BeanSwitch extends JPanel implements java.beans.PropertyChangeListener, ActionListener { 040 041 private final JButton beanButton = new JButton(); 042 private IconSwitch iconSwitch; 043 private final int _shape; 044 private int square = 75; // outside dimension of graphic, normally < 2*radius 045 private int radius = 50; // max distance in px from center of switch canvas, unit used for relative scaling 046 private double popScale = 1.0d; 047 private SwitchBoardLabelDisplays showUserName = SwitchBoardLabelDisplays.BOTH_NAMES; 048 private Color activeColor = Color.RED; // for testing a separate BeanSwitch 049 private Color inactiveColor = Color.GREEN; 050 Color textColor = Color.BLACK; 051 protected String switchLabel; 052 protected String switchTooltip; 053 protected boolean _text; 054 protected boolean _icon = false; 055 protected boolean _control = false; 056 protected int _showingState = 0; 057 protected String _stateSign; 058 protected String _color; 059 protected String stateClosed = Bundle.getMessage("StateClosedShort"); 060 protected String stateThrown = Bundle.getMessage("StateThrownShort"); 061 062 private final SwitchboardEditor _editor; 063 private char beanTypeChar = 'T'; // initialize now to allow testing 064 private String switchTypeName = "Turnout"; 065 private String manuPrefix = "I"; 066 private final String _switchSysName; 067 private String _switchDisplayName; 068 boolean showToolTip = true; 069 boolean allControlling = true; 070 boolean panelEditable = false; 071 // the associated Bean object 072 private final NamedBean _bname; 073 private NamedBeanHandle<?> namedBean = null; // can be Turnout, Sensor or Light 074 protected jmri.NamedBeanHandleManager nbhm = jmri.InstanceManager.getDefault(jmri.NamedBeanHandleManager.class); 075 private String _uName = "unconnected"; 076 private String _uLabel = ""; // for display, empty if userName == null or showUserName != BOTH_NAMES 077 078 /** 079 * Ctor. 080 * 081 * @param index ordinal of this switch on Switchboard. 082 * @param bean layout object to connect to. 083 * @param switchName descriptive name corresponding with system name to 084 * display in switch tooltip, i.e. LT1. 085 * @param shapeChoice Button, Slider, Key (all drawn on screen) or Icon (sets of graphic files). 086 * @param editor main switchboard editor. 087 */ 088 public BeanSwitch(int index, @CheckForNull NamedBean bean, @Nonnull String switchName, int shapeChoice, @CheckForNull SwitchboardEditor editor) { 089 log.debug("Name = [{}]", switchName); 090 _switchSysName = switchName; 091 _switchDisplayName = switchName; // updated later on if a user name is available 092 _editor = editor; 093 _bname = bean; 094 _shape = shapeChoice; 095 //if (_switchSysName.length() < 3) { // causes unexpected effects? 096 // log.error("Switch name {} too short for a valid system name", switchName); 097 // return; 098 //} 099 sysNameTextBox.setText(switchName); // setting name here allows test of AddNew() 100 boolean hideUnconnected = false; 101 Color backgroundColor = Color.LIGHT_GRAY; 102 if (editor != null) { 103 // get connection 104 manuPrefix = editor.getSwitchManu(); // connection/manufacturer prefix i.e. default) M for MERG 105 switchTypeName = _editor.getSwitchTypeName(); 106 // get display settings 107 hideUnconnected = editor.hideUnconnected(); 108 allControlling = editor.allControlling(); 109 panelEditable = editor.isEditable(); 110 showToolTip = editor.showToolTip(); 111 showUserName = editor.nameDisplay(); 112 radius = editor.getTileSize()/2; // max WxH of canvas inside cell, used as relative unit to draw 113 square = editor.getIconScale(); 114 // get colors 115 textColor = editor.getDefaultTextColorAsColor(); 116 backgroundColor = editor.getDefaultBackgroundColor(); 117 activeColor = editor.getActiveColorAsColor(); 118 inactiveColor = editor.getInactiveColorAsColor(); 119 popScale = _editor.getPaintScale(); 120 } 121 if (bean != null) { 122 _uName = bean.getUserName(); 123 log.debug("Switch userName from bean: {}", _uName); 124 if (_uName == null) { 125 _uName = Bundle.getMessage("NoUserName"); 126 } else if (showUserName == SwitchBoardLabelDisplays.BOTH_NAMES) { // (menu option setting) 127 _uLabel = _uName; 128 } else if (showUserName == SwitchBoardLabelDisplays.USER_NAME) { 129 _switchDisplayName = _uName; // replace system name 130 } 131 } 132 133 switchTooltip = switchName + " (" + _uName + ")"; 134 this.setLayout(new BorderLayout()); // makes JButtons expand to the whole grid cell 135 136 beanTypeChar = _switchSysName.charAt(manuPrefix.length()); // bean type, i.e. L, usually at char(1) 137 // check for space char which might be caused by connection name > 2 chars and/or space in name 138 if (beanTypeChar != 'T' && beanTypeChar != 'S' && beanTypeChar != 'L') { // add if more bean types are supported 139 log.error("invalid char in Switchboard Button \"{}\". Check connection name.", _switchSysName); 140 JmriJOptionPane.showMessageDialog(editor, Bundle.getMessage("ErrorSwitchAddFailed"), 141 Bundle.getMessage("WarningTitle"), 142 JmriJOptionPane.ERROR_MESSAGE); 143 return; 144 } 145 146 log.debug("BeanSwitch graphic tilesize/2 r={} scale={}", radius, square); 147 148 // look for bean to connect to by name 149 log.debug("beanconnect = {}, beantype = {}", manuPrefix, beanTypeChar); 150 try { 151 if (bean != null) { 152 namedBean = nbhm.getNamedBeanHandle(switchName, bean); 153 } 154 } catch (IllegalArgumentException e) { 155 log.error("invalid bean name= \"{}\" in Switchboard Button", switchName); 156 } 157 158 _text = true; // not actually used, web server supports in-browser drawn switches check in 159 _icon = true; // panel.js assigns only text OR icon for a single class such as BeanSwitch 160 // attach shape specific code to this beanSwitch 161 switch (_shape) { 162 case SwitchboardEditor.SLIDER: // slider shape 163 iconSwitch = new IconSwitch(_shape, beanTypeChar); // draw as Graphic2D 164 iconSwitch.setPreferredSize(new Dimension(2*radius, 2*radius)); // tweak layout 165 iconSwitch.positionLabel(0, 5*radius/-8, Component.CENTER_ALIGNMENT, getLabelFontSize(radius, _switchDisplayName)); 166 iconSwitch.positionSubLabel(0, radius/-5, Component.CENTER_ALIGNMENT, getSubLabelFontSize(radius, _uName)); // smaller (system name) 167 this.add(iconSwitch); 168 break; 169 case SwitchboardEditor.KEY: // Maerklin style keyboard 170 iconSwitch = new IconSwitch(_shape, beanTypeChar); // draw as Graphic2D 171 iconSwitch.setPreferredSize(new Dimension(2*radius, 2*radius)); // tweak layout 172 iconSwitch.positionLabel(0, 0, Component.CENTER_ALIGNMENT, getLabelFontSize(radius, _switchDisplayName)); 173 iconSwitch.positionSubLabel(0, 3*radius/10, Component.CENTER_ALIGNMENT, getSubLabelFontSize(radius, _uName)); // smaller (system name) 174 // provide x, y offset, depending on image size and free space 175 this.add(iconSwitch); 176 break; 177 case SwitchboardEditor.SYMBOL: 178 // turnout/sensor/light symbol using image files (selecting image by letter in switch name/label) 179 iconSwitch = new IconSwitch( 180 rootPath + beanTypeChar + "-on-s.png", 181 rootPath + beanTypeChar + "-off-s.png", backgroundColor); 182 iconSwitch.setPreferredSize(new Dimension(2*radius, 2*radius)); 183 switch (beanTypeChar) { 184 case 'T' : 185 iconSwitch.positionLabel(0, 5*radius/-8, Component.CENTER_ALIGNMENT, getLabelFontSize(radius, _switchDisplayName)); 186 iconSwitch.positionSubLabel(0, radius/-4, Component.CENTER_ALIGNMENT, getSubLabelFontSize(radius, _uName)); 187 break; 188 case 'S' : 189 case 'L' : 190 default : 191 iconSwitch.positionLabel(0, 5*radius/-8, Component.CENTER_ALIGNMENT, getLabelFontSize(radius, _switchDisplayName)); 192 iconSwitch.positionSubLabel(0, radius/-3, Component.CENTER_ALIGNMENT, getSubLabelFontSize(radius, _uName)); 193 } 194 // common (in)activecolor etc defined in SwitchboardEditor, retrieved by Servlet 195 this.setBorder(BorderFactory.createLineBorder(backgroundColor, 3)); 196 this.add(iconSwitch); 197 break; 198 case SwitchboardEditor.BUTTON: // 0 = "Button" shape 199 default: 200 _icon = false; 201 beanButton.setText(getSwitchButtonLabel(_switchDisplayName + ": ?")); // initial text to display 202 beanButton.setToolTipText(getSwitchButtonToolTip(switchLabel)); 203 beanButton.setForeground(textColor); 204 beanButton.setOpaque(true); // to show color from the start 205 this.setBorder(BorderFactory.createLineBorder(backgroundColor, 2)); 206 beanButton.addComponentListener(new ComponentAdapter() { 207 @Override 208 public void componentResized(ComponentEvent e) { 209 if ((showUserName == SwitchBoardLabelDisplays.BOTH_NAMES) && (beanButton.getHeight() < 50)) { 210 beanButton.setVerticalAlignment(JLabel.TOP); 211 } else { 212 beanButton.setVerticalAlignment(JLabel.CENTER); //default 213 } 214 } 215 }); 216 beanButton.addMouseListener(new MouseAdapter() { // pass on mouseEvents 217 @Override 218 public void mouseClicked(MouseEvent e) { 219 redispatchToParent(e); 220 } 221 @Override 222 public void mouseReleased(MouseEvent e) { 223 redispatchToParent(e); 224 } 225 @Override 226 public void mousePressed(MouseEvent e) { 227 redispatchToParent(e); 228 } 229 }); 230 beanButton.setMargin(new Insets(4, 1, 2, 1)); 231 this.add(beanButton); 232 break; 233 } 234 // common configuration of graphic switches 235 addMouseListener(new MouseAdapter() { // handled by JPanel 236 @Override 237 public void mouseClicked(MouseEvent me) { 238 operate(me, switchName); 239 } 240 241 @Override 242 public void mouseReleased(MouseEvent me) { // for Windows 243 if (me.isPopupTrigger()) { 244 showPopUp(me); // display the popup 245 } 246 } 247 248 @Override 249 public void mousePressed(MouseEvent me) { // for macOS, Linux 250 if (me.isPopupTrigger()) { 251 log.debug("what's clicking?"); 252 showPopUp(me); // display the popup 253 } 254 } 255 }); 256 if (showToolTip) { 257 setToolTipText(switchTooltip); 258 } 259 if (iconSwitch != null) { 260 iconSwitch.setBackground(backgroundColor); 261 iconSwitch.setLabels(switchLabel, _uLabel); 262 } 263 // connect to object or dim switch 264 if (bean == null) { 265 if (!hideUnconnected) { 266 // to dim unconnected symbols TODO make graphics see through, now icons just become bleak 267 //float dim = 100f; 268 switch (_shape) { 269 case SwitchboardEditor.BUTTON: 270 beanButton.setEnabled(false); 271 break; 272 case SwitchboardEditor.SLIDER: 273 case SwitchboardEditor.KEY: 274 case SwitchboardEditor.SYMBOL: 275 default: 276 // iconSwitch.setOpacity(dim); // activate for graphic painted switches 277 } 278 displayState(0); // show unconnected as unknown/greyed 279 } 280 } else { 281 _control = true; 282 switch (beanTypeChar) { 283 case 'T': 284 getTurnout().addPropertyChangeListener(this, _switchSysName, "Switchboard Editor Turnout Switch"); 285 if (getTurnout().canInvert()) { 286 this.setInverted(getTurnout().getInverted()); // only add and set when supported by object/connection 287 } 288 break; 289 case 'S': 290 getSensor().addPropertyChangeListener(this, _switchSysName, "Switchboard Editor Sensor Switch"); 291 if (getSensor().canInvert()) { 292 this.setInverted(getSensor().getInverted()); // only add and set when supported by object/connection 293 } 294 break; 295 default: // light 296 getLight().addPropertyChangeListener(this, _switchSysName, "Switchboard Editor Light Switch"); 297 // Lights do not support Invert 298 } 299 displayState(bean.getState()); 300 } 301 log.debug("Created switch {}", index); 302 } 303 304 static final AffineTransform affinetransform = new AffineTransform(); 305 static final FontRenderContext frc = new FontRenderContext(affinetransform,true,true); 306 307 int getLabelFontSize(int radius, String text) { 308 int fontSize = Math.max(12, radius/4); 309 // see if that fits using font metrics 310 if (text != null) { 311 Font font = new Font(Font.SANS_SERIF, Font.BOLD, fontSize); 312 int textwidth = (int)(font.getStringBounds(text, frc).getWidth()); 313 fontSize = Math.min(fontSize, fontSize*2*radius*9/textwidth/10); // integer arithmetic: fit in 90% of radius*2 314 log.trace("calculate fontsize {} from radius {} and textwidth {} for string \"{}\"", fontSize, radius, textwidth, text); 315 } 316 return Math.max(fontSize, 5); // but go no smaller than 6 point 317 } 318 319 int getSubLabelFontSize(int radius, String text) { 320 int fontSize = Math.max(9, radius/5); 321 // see if text fits using font metrics, if not correct it with a smaller font size 322 if (text != null) { 323 Font font = new Font(Font.SANS_SERIF, Font.BOLD, fontSize); 324 int textwidth = (int)(font.getStringBounds(text, frc).getWidth()); 325 fontSize = Math.min(fontSize, fontSize*2*radius*9/textwidth/10); // integer arithmetic: fit in 90% of radius*2 326 log.trace("calculate fontsize {} from radius {} and textwidth {} for string \"{}\"", fontSize, radius, textwidth, text); 327 } 328 return Math.max(fontSize, 5); // but go no smaller than 6 point 329 } 330 331 public NamedBean getNamedBean() { 332 return _bname; 333 } 334 335 /** 336 * Store an object as NamedBeanHandle, using _label as the display 337 * name. 338 * 339 * @param bean the object (either a Turnout, Sensor or Light) to attach 340 * to this switch 341 */ 342 public void setNamedBean(@Nonnull NamedBean bean) { 343 try { 344 namedBean = nbhm.getNamedBeanHandle(_switchSysName, bean); 345 } catch (IllegalArgumentException e) { 346 log.error("invalid bean name= \"{}\" in Switchboard Button \"{}\"", _switchSysName, _switchDisplayName); 347 } 348 _uName = bean.getUserName(); 349 if (_uName == null) { 350 _uName = Bundle.getMessage("NoUserName"); 351 } else { 352 if (showUserName == SwitchBoardLabelDisplays.BOTH_NAMES) { 353 _uLabel = _uName; 354 } else if (showUserName == SwitchBoardLabelDisplays.USER_NAME) { 355 switchLabel = _uName; 356 } 357 } 358 _control = true; 359 } 360 361 public Turnout getTurnout() { 362 if (namedBean == null) { 363 return null; 364 } 365 return (Turnout) namedBean.getBean(); 366 } 367 368 public Sensor getSensor() { 369 if (namedBean == null) { 370 return null; 371 } 372 return (Sensor) namedBean.getBean(); 373 } 374 375 public Light getLight() { 376 if (namedBean == null) { 377 return null; 378 } 379 return (Light) namedBean.getBean(); 380 } 381 382 /** 383 * Get the user selected switch shape (e.g. 3 for Slider) 384 * 385 * @return the index of the selected item in Shape comboBox 386 */ 387 public int getShape() { 388 return _shape; 389 } 390 391 /** 392 * Get text to display on this switch on Switchboard and in Web Server panel when attached 393 * object is Active. 394 * 395 * @return text to show on active state (differs per type of object) 396 */ 397 public String getActiveText() { 398 // fetch bean specific abbreviation 399 if (beanTypeChar == 'T') { 400 _stateSign = stateClosed; // + 401 } else { 402 // Light, Sensor 403 _stateSign = "+"; // 1 char abbreviation for StateOff not clear 404 } 405 return _switchDisplayName + ": " + _stateSign; 406 } 407 408 /** 409 * Get text to display on this switch on Switchboard and in Web Server panel when attached 410 * object is Inactive. 411 * 412 * @return text to show on inactive state (differs per type of objects) 413 */ 414 public String getInactiveText() { 415 // fetch bean specific abbreviation 416 if (beanTypeChar == 'T') { 417 _stateSign = stateThrown; // + 418 } else { 419 // Light, Sensor 420 _stateSign = "-"; // 1 char abbreviation for StateOff not clear 421 } 422 return _switchDisplayName + ": " + _stateSign; 423 } 424 425 /** 426 * Get text to display on this switch in Web Server panel when attached 427 * object is Unknown (initial state displayed). 428 * 429 * @return text to show on unknown state (used on all types of objects) 430 */ 431 public String getUnknownText() { 432 return _switchDisplayName + ": ?"; 433 } 434 435 public String getInconsistentText() { 436 return _switchDisplayName + ": X"; 437 } 438 439 /** 440 * Get text to display as switch tooltip in Web Server panel. 441 * Used in jmri.jmrit.display.switchboardEditor.configureXml.BeanSwitchXml#store(Object) 442 * 443 * @return switch tooltip text 444 */ 445 public String getToolTip() { 446 return switchTooltip; 447 } 448 449 // ******************* Display *************************** 450 451 @Override 452 public void actionPerformed(ActionEvent e) { 453 //updateBean(); 454 } 455 456 /** 457 * Get the label of this switch. 458 * 459 * @return display name not including current state 460 */ 461 public String getNameString() { 462 return _switchDisplayName; 463 } 464 465 public String getUserNameString() { 466 return _uLabel; 467 } 468 469 private String getSwitchButtonLabel(String label) { 470 if ((showUserName == SwitchBoardLabelDisplays.SYSTEM_NAME) || (_uLabel.equals(""))) { 471 String subLabel = label.substring(0, (Math.min(label.length(), 35))); // reasonable max. to display 2 lines on tile 472 return "<html><center>" + subLabel + "</center></html>"; // lines of text 473 } else if (showUserName == SwitchBoardLabelDisplays.USER_NAME) { 474 String subLabel = label.substring(0, (Math.min(label.length(), 35))); // reasonable max. to display 2 lines on tile 475 return "<html><center>" + subLabel + "</center></html>"; // lines of text 476 } else { // BOTH_NAMES case 477 String subLabel = _uLabel.substring(0, (Math.min(_uLabel.length(), 35))); // reasonable max. to display 2 lines on tile 478 return "<html><center>" + label + "</center><center><i>" + subLabel + "</i></center></html>"; // lines of text 479 } 480 } 481 482 private String getSwitchButtonToolTip(String label) { 483 if ((showUserName == SwitchBoardLabelDisplays.SYSTEM_NAME) || (_uLabel.equals(""))) { 484 return label; 485 } else if (showUserName == SwitchBoardLabelDisplays.USER_NAME) { 486 return label; 487 } else { // BOTH_NAMES case 488 return _uLabel+" "+label; 489 } 490 } 491 492 493 /** 494 * Drive the current state of the display from the state of the 495 * connected bean. 496 * 497 * @param newState integer representing the new state e.g. Turnout.CLOSED 498 */ 499 public void displayState(int newState) { 500 String switchLabel; 501 Color switchColor; 502 if (getNamedBean() == null) { 503 switchLabel = _switchDisplayName; // unconnected, doesn't show state using : and ? 504 switchColor = Color.GRAY; 505 log.debug("Switch label {} state {}, disconnected", switchLabel, newState); 506 } else { 507 if (newState == _showingState) { 508 return; // prevent redrawing on repeated identical commands 509 } 510 // display abbreviated name of state instead of state index, fine for unconnected switches too 511 switch (newState) { 512 case 1: 513 switchLabel = getUnknownText(); 514 switchColor = Color.GRAY; 515 break; 516 case 2: 517 switchLabel = getActiveText(); 518 switchColor = activeColor; 519 break; 520 case 4: 521 switchLabel = getInactiveText(); 522 switchColor = inactiveColor; 523 break; 524 default: 525 switchLabel = getInconsistentText(); 526 switchColor = Color.WHITE; 527 //log.warn("SwitchState INCONSISTENT"); // normal for unconnected switchboard 528 log.debug("Switch label {} state: {}, connected", switchLabel, newState); 529 } 530 } 531 if (isText() && !isIcon()) { // to allow text buttons on web switchboard. 532 log.debug("Label = {}, setText", getSwitchButtonLabel(switchLabel)); 533 beanButton.setText(getSwitchButtonLabel(switchLabel)); 534 beanButton.setToolTipText(getSwitchButtonToolTip(switchLabel)); 535 beanButton.setBackground(switchColor); // only the color is visible on macOS 536 // TODO get access to bg color of JButton? 537 beanButton.setOpaque(true); 538 } else if (isIcon() && (iconSwitch != null)) { 539 iconSwitch.showSwitchIcon(newState); 540 iconSwitch.setLabels(switchLabel, _uLabel); 541 } 542 _showingState = newState; 543 } 544 545 /** 546 * Switch presentation is graphic image based. 547 * 548 * @see #displayState(int) 549 * @return true when switch shape other than 'Button' is selected 550 */ 551 public final boolean isIcon() { 552 return _icon; 553 } 554 555 /** 556 * Switch presentation is text based. 557 * 558 * @see #displayState(int) 559 * @return true when switch shape 'Button' is selected (and also for the 560 * other, graphic switch types until SwitchboardServlet directly 561 * supports their graphic icons) 562 */ 563 public final boolean isText() { 564 return _text; 565 } 566 567 /** 568 * Update switch as state of bean changes. 569 * 570 * @param e the PropertyChangeEvent heard 571 */ 572 @Override 573 public void propertyChange(java.beans.PropertyChangeEvent e) { 574 if (log.isDebugEnabled()) { 575 log.debug("property change: {} {} is now: {}", _switchSysName, e.getPropertyName(), e.getNewValue()); 576 } 577 if (e.getPropertyName().equals("KnownState")) { 578 int now = ((Integer) e.getNewValue()); 579 displayState(now); 580 log.debug("Item state changed"); 581 } 582 if (e.getPropertyName().equals("UserName")) { 583 // update tooltip 584 String newUserName; 585 if (showToolTip) { 586 newUserName = ((String) e.getNewValue()); 587 _uLabel = (newUserName == null ? "" : newUserName); // store for display on icon 588 if (newUserName == null || newUserName.equals("")) { 589 newUserName = Bundle.getMessage("NoUserName"); // longer for tooltip 590 } 591 setToolTipText(_switchSysName + " (" + newUserName + ")"); 592 log.debug("User Name changed to {}", newUserName); 593 } 594 } 595 } 596 597 void cleanup() { 598 if (namedBean != null) { 599 switch (beanTypeChar) { 600 case 'T': 601 getTurnout().removePropertyChangeListener(this); 602 break; 603 case 'S': 604 getSensor().removePropertyChangeListener(this); 605 break; 606 default: // light 607 getLight().removePropertyChangeListener(this); 608 } 609 } 610 namedBean = null; 611 } 612 613 JPopupMenu switchPopup; 614 JMenuItem connectNewMenu = new JMenuItem(Bundle.getMessage("ConnectNewMenu", "...")); 615 616 /** 617 * Show pop-up on a switch with its unique attributes including the 618 * (un)connected bean. 619 * 620 * @param e unused because we now our own location 621 * @return true when pop up displayed 622 */ 623 public boolean showPopUp(MouseEvent e) { 624 if (switchPopup != null) { 625 switchPopup.removeAll(); 626 } else { 627 switchPopup = new JPopupMenu(); 628 } 629 630 switchPopup.add(getNameString()); 631 632 if (panelEditable && allControlling) { 633 if (namedBean != null) { 634 addEditUserName(switchPopup); 635 switch (beanTypeChar) { 636 case 'T': 637 if (getTurnout().canInvert()) { // check whether supported by this turnout 638 addInvert(switchPopup); 639 } 640 // tristate and momentary (see TurnoutIcon) can't be set per switch 641 break; 642 case 'S': 643 if (getSensor().canInvert()) { // check whether supported by this sensor 644 addInvert(switchPopup); 645 } 646 break; 647 default: 648 // invert is not supported by Lights, so skip 649 } 650 } else { 651 // show option to attach a new bean 652 switchPopup.add(connectNewMenu); 653 connectNewMenu.addActionListener((java.awt.event.ActionEvent e1) -> connectNew()); 654 } 655 } 656 // display the popup 657 switchPopup.show(this, this.getWidth()/3 + (int) ((popScale - 1.0) * this.getX()), 658 this.getHeight()/3 + (int) ((popScale - 1.0) * this.getY())); 659 660 return true; 661 } 662 663 javax.swing.JMenuItem editItem = null; 664 665 void addEditUserName(JPopupMenu popup) { 666 editItem = new javax.swing.JMenuItem(Bundle.getMessage("EditNameTitle", "...")); 667 popup.add(editItem); 668 editItem.addActionListener((java.awt.event.ActionEvent e) -> renameBeanDialog()); 669 } 670 671 javax.swing.JCheckBoxMenuItem invertItem = null; 672 673 void addInvert(JPopupMenu popup) { 674 invertItem = new javax.swing.JCheckBoxMenuItem(Bundle.getMessage("MenuInvertItem", _switchSysName)); 675 invertItem.setSelected(getInverted()); 676 popup.add(invertItem); 677 invertItem.addActionListener((java.awt.event.ActionEvent e) -> setBeanInverted(invertItem.isSelected())); 678 } 679 680 /** 681 * Edit user name on a switch. 682 */ 683 public void renameBeanDialog() { 684 String oldName = _uName; 685 String newUserName = (String)JmriJOptionPane.showInputDialog(this, Bundle.getMessage("EnterNewName", _switchSysName), Bundle.getMessage("EditNameTitle", ""), 686 JmriJOptionPane.QUESTION_MESSAGE, null, null, oldName); 687 688 if (newUserName == null) { 689 return; 690 } 691 if (newUserName.equals(Bundle.getMessage("NoUserName")) || newUserName.isEmpty()) { // user cancelled 692 log.debug("new user name was empty"); 693 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("WarningEmptyUserName"), Bundle.getMessage("WarningTitle"), JmriJOptionPane.ERROR_MESSAGE); 694 return; 695 } 696 renameBean(newUserName, oldName); 697 } 698 699 /** 700 * Edit user name on a switch. 701 * 702 * @param newUserName string to use as user name replacement 703 * @param oldName current user name (used to prevent useless change) 704 */ 705 protected void renameBean(String newUserName, String oldName) { 706 NamedBean nb; 707 if (newUserName.equals(oldName)) { // name was not changed by user 708 return; 709 } else { // check if name is already in use 710 switch (beanTypeChar) { 711 case 'T': 712 nb = jmri.InstanceManager.turnoutManagerInstance().getTurnout(newUserName); 713 break; 714 case 'S': 715 nb = jmri.InstanceManager.sensorManagerInstance().getSensor(newUserName); 716 break; 717 case 'L': 718 nb = jmri.InstanceManager.lightManagerInstance().getLight(newUserName); 719 break; 720 default: 721 log.error("Check userName: cannot parse bean name. userName = {}", newUserName); 722 return; 723 } 724 if (nb != null) { 725 log.error("User name is not unique {}", newUserName); 726 String msg = Bundle.getMessage("WarningUserName", newUserName); 727 JmriJOptionPane.showMessageDialog(this, msg, 728 Bundle.getMessage("WarningTitle"), 729 JmriJOptionPane.ERROR_MESSAGE); 730 return; 731 } 732 } 733 _bname.setUserName(newUserName); 734 if (oldName == null || oldName.equals("")) { 735 if (!nbhm.inUse(_switchSysName, _bname)) { 736 return; // no problem, so stop 737 } 738 String msg = Bundle.getMessage("UpdateToUserName", switchTypeName, newUserName, _switchSysName); 739 int optionPane = JmriJOptionPane.showConfirmDialog(this, 740 msg, Bundle.getMessage("UpdateToUserNameTitle"), 741 JmriJOptionPane.YES_NO_OPTION); 742 if (optionPane == JmriJOptionPane.YES_OPTION) { 743 //This will update the bean reference from the systemName to the userName 744 try { 745 nbhm.updateBeanFromSystemToUser(_bname); 746 } catch (JmriException ex) { 747 // We should never get an exception here as we already check that the username is not valid 748 } 749 } 750 751 } else { 752 nbhm.renameBean(oldName, newUserName, _bname); // will pick up name change in label 753 } 754 if (_editor != null) { 755 _editor.updatePressed(); // but we redraw whole switchboard 756 } 757 } 758 759 private boolean inverted = false; 760 761 public void setInverted(boolean set) { 762 inverted = set; 763 } 764 765 public boolean getInverted() { 766 return inverted; 767 } 768 769 /** 770 * Invert attached object on the layout, if supported by its connection. 771 * 772 * @param set new inverted state, true for inverted, false for normal. 773 */ 774 public void setBeanInverted(boolean set) { 775 switch (beanTypeChar) { 776 case 'T': 777 if (getTurnout() != null && getTurnout().canInvert()) { // if supported 778 this.setInverted(set); 779 getTurnout().setInverted(set); 780 } 781 break; 782 case 'S': 783 if (getSensor() != null && getSensor().canInvert()) { // if supported 784 this.setInverted(set); 785 getSensor().setInverted(set); 786 } 787 break; 788 case 'L': 789 // Lights cannot be inverted, so never called 790 return; 791 default: 792 log.error("Invert item: cannot parse bean name. userName = {}", _switchSysName); 793 } 794 } 795 796 /** 797 * Process mouseClick on this switch, passing in name for debug. 798 * 799 * @param e the event heard 800 * @param name ID of this button (identical to name of suggested bean 801 * object) 802 */ 803 public void operate(MouseEvent e, String name) { 804 log.debug("Button {} clicked", name); 805 if (namedBean == null || e == null || e.isMetaDown()) { 806 return; 807 } 808 alternateOnClick(); 809 } 810 811 /** 812 * Process mouseClick on this switch. 813 * Similar to {@link #operate(MouseEvent, String)}. 814 * 815 * @param e the event heard 816 */ 817 public void doMouseClicked(java.awt.event.MouseEvent e) { 818 log.debug("Switch clicked"); 819 if (namedBean == null || e == null || e.isMetaDown()) { 820 return; 821 } 822 alternateOnClick(); 823 } 824 825 /** 826 * Change the state of attached Turnout, Light or Sensor on the layout 827 * unless menu option Panel Items Control Layout is set to off. 828 */ 829 void alternateOnClick() { 830 if (allControlling) { 831 switch (beanTypeChar) { 832 case 'T': // Turnout 833 log.debug("T clicked"); 834 if (getTurnout().getKnownState() == jmri.Turnout.CLOSED) // if clear known state, set to opposite 835 { 836 getTurnout().setCommandedState(jmri.Turnout.THROWN); 837 } else if (getTurnout().getKnownState() == jmri.Turnout.THROWN) { 838 getTurnout().setCommandedState(jmri.Turnout.CLOSED); 839 } else if (getTurnout().getCommandedState() == jmri.Turnout.CLOSED) { 840 getTurnout().setCommandedState(jmri.Turnout.THROWN); // otherwise, set to opposite of current commanded state if known 841 } else { 842 getTurnout().setCommandedState(jmri.Turnout.CLOSED); // just force Closed 843 } 844 break; 845 case 'L': // Light 846 log.debug("L clicked"); 847 if (getLight().getState() == jmri.Light.OFF) { 848 getLight().setState(jmri.Light.ON); 849 } else { 850 getLight().setState(jmri.Light.OFF); 851 } 852 break; 853 case 'S': // Sensor 854 log.debug("S clicked"); 855 try { 856 if (getSensor().getKnownState() == jmri.Sensor.INACTIVE) { 857 getSensor().setKnownState(jmri.Sensor.ACTIVE); 858 } else { 859 getSensor().setKnownState(jmri.Sensor.INACTIVE); 860 } 861 } catch (jmri.JmriException reason) { 862 log.warn("Exception flipping sensor", (Object) reason); 863 } 864 break; 865 default: 866 log.error("invalid char in Switchboard Button \"{}\". State not set.", _switchSysName); 867 } 868 } 869 } 870 871 /** 872 * Only for lights. Used for All Off/All On. 873 * Skips unconnected switch icons. 874 * 875 * @param state On = 1, Off = 0 876 */ 877 public void switchLight(int state) { 878 if (namedBean != null) { 879 getLight().setState(state); 880 } 881 } 882 883 public void setBackgroundColor(Color bgcolor) { 884 this.setBackground(bgcolor); 885 } 886 887 JmriJFrame addFrame = null; 888 JTextField sysNameTextBox = new JTextField(12); 889 JTextField userName = new JTextField(15); 890 891 /** 892 * Create new bean and connect it to this switch. Use type letter from 893 * switch label (T, S or L). 894 */ 895 protected void connectNew() { 896 log.debug("Request new bean"); 897 userName.setText(""); // this method is only available on unconnected switches, so no useful content to fill in yet 898 // provide etc. 899 if (addFrame == null) { 900 addFrame = new JmriJFrame(Bundle.getMessage("ConnectNewMenu", ""), false, true); 901 addFrame.addHelpMenu("package.jmri.jmrit.display.switchboardEditor.SwitchboardEditor", true); 902 addFrame.getContentPane().setLayout(new BoxLayout(addFrame.getContentPane(), BoxLayout.Y_AXIS)); 903 904 ActionListener okListener = this::okAddPressed; 905 ActionListener cancelListener = this::cancelAddPressed; 906 AddNewDevicePanel switchConnect = new AddNewDevicePanel(sysNameTextBox, userName, "ButtonOK", okListener, cancelListener); 907 switchConnect.setSystemNameFieldIneditable(); // prevent user interference with switch label (proposed system name) 908 switchConnect.setOK(); // activate OK button on Add new device pane 909 addFrame.add(switchConnect); 910 } 911 addFrame.pack(); 912 addFrame.setLocationRelativeTo(this); 913 addFrame.setVisible(true); 914 } 915 916 protected void cancelAddPressed(ActionEvent e) { 917 if (addFrame != null) { 918 addFrame.setVisible(false); 919 addFrame.dispose(); 920 addFrame = null; 921 } 922 } 923 924 protected void okAddPressed(ActionEvent e) { 925 NamedBean nb; 926 String user = userName.getText(); 927 if (user.trim().equals("")) { 928 user = null; 929 } 930 // systemName can't be changed, fixed 931 if (addFrame != null) { 932 addFrame.setVisible(false); 933 addFrame.dispose(); 934 addFrame = null; 935 } 936 switch (_switchSysName.charAt(manuPrefix.length())) { 937 case 'T': 938 Turnout t; 939 try { 940 // add turnout to JMRI (w/appropriate manager) 941 t = InstanceManager.turnoutManagerInstance().provideTurnout(_switchSysName); 942 t.setUserName(user); 943 } catch (IllegalArgumentException ex) { 944 // user input no good 945 handleCreateException(_switchSysName); 946 return; // without creating 947 } 948 nb = jmri.InstanceManager.turnoutManagerInstance().getTurnout(_switchSysName); 949 break; 950 case 'S': 951 Sensor s; 952 try { 953 // add Sensor to JMRI (w/appropriate manager) 954 s = InstanceManager.sensorManagerInstance().provideSensor(_switchSysName); 955 s.setUserName(user); 956 } catch (IllegalArgumentException ex) { 957 // user input no good 958 handleCreateException(_switchSysName); 959 return; // without creating 960 } 961 nb = jmri.InstanceManager.sensorManagerInstance().getSensor(_switchSysName); 962 break; 963 case 'L': 964 Light l; 965 try { 966 // add Light to JMRI (w/appropriate manager) 967 l = InstanceManager.lightManagerInstance().provideLight(_switchSysName); 968 l.setUserName(user); 969 } catch (IllegalArgumentException ex) { 970 // user input no good 971 handleCreateException(_switchSysName); 972 return; // without creating 973 } 974 nb = jmri.InstanceManager.lightManagerInstance().getLight(_switchSysName); 975 break; 976 default: 977 log.error("connectNew - okAddPressed: cannot parse bean name. sName = {}", _switchSysName); 978 return; 979 } 980 if (nb == null) { 981 log.warn("failed to connect switch to item {}", _switchSysName); 982 } else { 983 // set switch on Switchboard to display current state of just connected bean 984 log.debug("sName state: {}", nb.getState()); 985 try { 986 if (_editor.getSwitch(_switchSysName) == null) { 987 log.warn("failed to update switch to state of {}", _switchSysName); 988 } else { 989 _editor.updatePressed(); 990 } 991 } catch (NullPointerException npe) { 992 handleCreateException(_switchSysName); 993 // exit without updating 994 } 995 } 996 } 997 998 /** 999 * Check the switch label currently displayed. 1000 * Used in test. 1001 * 1002 * @return line 1 of the label of this switch 1003 */ 1004 protected String getIconLabel() { 1005 switch (_shape) { 1006 case SwitchboardEditor.BUTTON: // button 1007 String lbl = beanButton.getText(); 1008 if (!lbl.startsWith("<")) { 1009 return lbl; 1010 } else { // 2 line label, "<html><center>" + label + "</center>..." 1011 return lbl.substring(14, lbl.indexOf("</center>")); 1012 } 1013 case SwitchboardEditor.SLIDER: 1014 case SwitchboardEditor.KEY: 1015 case SwitchboardEditor.SYMBOL: 1016 return iconSwitch.getIconLabel(); 1017 default: 1018 return ""; 1019 } 1020 } 1021 1022 void handleCreateException(String sysName) { 1023 JmriJOptionPane.showMessageDialog(addFrame, 1024 java.text.MessageFormat.format( 1025 Bundle.getMessage("ErrorSwitchAddFailed"), sysName), 1026 Bundle.getMessage("ErrorTitle"), 1027 JmriJOptionPane.ERROR_MESSAGE); 1028 } 1029 1030 String rootPath = "resources/icons/misc/switchboard/"; 1031 1032 /** 1033 * Class to display individual bean state switches on a JMRI Switchboard 1034 * using 2DGraphic drawing code or alternating 2 image files. 1035 */ 1036 public class IconSwitch extends JPanel { 1037 1038 private BufferedImage image; 1039 private BufferedImage image1; 1040 private BufferedImage image2; 1041 private String tag = "tag"; 1042 private String subTag = ""; 1043 1044 private int labelX = 16; 1045 private int labelY = 53; 1046 private int textSize = 12; 1047 private float textAlign = 0.0f; 1048 1049 private int subLabelX = 16; 1050 private int subLabelY = 53; 1051 private int subTextSize = 12; 1052 private float subTextAlign = 0.0f; 1053 1054 private float ropOffset = 0f; 1055 private int r = 10; // radius of circle fitting inside tile rect in px drawing units 1056 private int _shape = SwitchboardEditor.BUTTON; 1057 private int _state = 0; 1058 private RescaleOp rop; 1059 1060 /** 1061 * Create an icon from 2 alternating png images. shape is assumed SwitchboardEditor.SYMBOL 1062 * 1063 * @param filepath1 the ON image 1064 * @param filepath2 the OFF image 1065 * @param back the background color set on the Switchboard, used to fill in empty parts of rescaled image 1066 */ 1067 public IconSwitch(String filepath1, String filepath2, Color back) { 1068 // load image files 1069 try { 1070 image1 = ImageIO.read(new File(filepath1)); 1071 image2 = ImageIO.read(new File(filepath2)); 1072 if ((square != 100) && (square >= 25) && (square <= 150)) { 1073 image1 = resizeImage(image1, square, back); 1074 image2 = resizeImage(image2, square, back); 1075 } 1076 image = image2; // start off as showing inactive/closed 1077 } catch (IOException ex) { 1078 log.error("error reading image from {}-{}", filepath1, filepath2, ex); 1079 } 1080 _shape = SwitchboardEditor.SYMBOL; 1081 if (radius > 10) r = radius; 1082 log.debug("radius={} size={}", r, getWidth()); 1083 } 1084 1085 /** 1086 * Ctor to draw graphic fully in Graphics. 1087 * 1088 * @param shape int to specify switch shape {@link SwitchboardEditor} constants 1089 * @param type beanType to draw (optionally ignored depending on shape, eg. for slider) 1090 */ 1091 public IconSwitch(int shape, int type) { 1092 if ((shape == SwitchboardEditor.BUTTON) || (shape == SwitchboardEditor.SYMBOL)) { 1093 return; // when SYMBOL is migrated, leave in place for 0 = BUTTON (drawn as JButtons, not graphics) 1094 } 1095 _shape = shape; 1096 if (radius > 10) r = radius; 1097 log.debug("DrawnIcon type={}", type); 1098 } 1099 1100 public void setOpacity(float offset) { 1101 ropOffset = offset; 1102 float ropScale = 1.0f; 1103 rop = new RescaleOp(ropScale, ropOffset, null); 1104 } 1105 1106 protected void showSwitchIcon(int stateIndex) { 1107 log.debug("showSwitchIcon {}", stateIndex); 1108 if ((_shape == SwitchboardEditor.SLIDER) || (_shape == SwitchboardEditor.KEY)) { 1109 //redraw (colors are already set above 1110 _state = stateIndex; 1111 } else { 1112 if (image1 != null && image2 != null) { 1113 switch (stateIndex) { 1114 case 2: 1115 image = image1; // on/Thrown/Active 1116 break; 1117 case 1: 1118 default: 1119 image = image2; // off, also for connected & unknown 1120 break; 1121 } 1122 this.repaint(); 1123 } 1124 } 1125 } 1126 1127 /** 1128 * Set or change label text on switch. 1129 * 1130 * @param sName string to display (system name) 1131 * @param uName secondary string to display (user name) 1132 */ 1133 protected void setLabels(String sName, String uName) { 1134 tag = sName; 1135 subTag = uName; 1136 this.repaint(); 1137 } 1138 1139 private String getIconLabel() { 1140 return tag; 1141 } 1142 1143 /** 1144 * Position (sub)label on switch. 1145 * 1146 * @param x horizontal offset from top left corner, positive to the 1147 * right 1148 * @param y vertical offset from top left corner, positive down 1149 * @param align one of: JComponent.LEFT_ALIGNMENT (0.0f), CENTER_ALIGNMENT (0.5f), 1150 * RIGHT_ALIGNMENT (1.0f) 1151 * @param fontsize size in points for label text display 1152 */ 1153 protected void positionLabel(int x, int y, float align, int fontsize) { 1154 labelX = x; 1155 labelY = y; 1156 textAlign = align; 1157 textSize = fontsize; 1158 } 1159 1160 protected void positionSubLabel(int x, int y, float align, int fontsize) { 1161 subLabelX = x; 1162 subLabelY = y; 1163 subTextAlign = align; 1164 subTextSize = fontsize; 1165 } 1166 1167 @Override 1168 protected void paintComponent(Graphics g) { 1169 super.paintComponent(g); 1170 Graphics2D g2d = (Graphics2D) g; 1171 // set antialiasing hint for macOS and Windows 1172 // note: antialiasing has performance problems on some variants of Linux (Raspberry pi) 1173 if (SystemType.isMacOSX() || SystemType.isWindows()) { 1174 g2d.setRenderingHint(RenderingHints.KEY_RENDERING, 1175 RenderingHints.VALUE_RENDER_QUALITY); 1176 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 1177 RenderingHints.VALUE_ANTIALIAS_ON); 1178 g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, 1179 RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); 1180 g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, 1181 RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 1182 } 1183 // now the image 1184 g.translate(r, r); // set origin to center 1185 if (_shape == SwitchboardEditor.SLIDER) { // slider 1186 // Draw symbol on the beanswitch widget canvas 1187 // see panel.js for vector drawing: var $drawWidgetSymbol = function(id, state), ctx is same as g2d 1188 // clear for alternating text and 'moving' items not covered by new paint 1189 if (_state == 4) { 1190 g.setColor(inactiveColor); // simple change in color 1191 } else if (_state == 2) { 1192 g.setColor(activeColor); 1193 } else { 1194 g.setColor(Color.GRAY); 1195 } 1196 // slider, same shape for all beanTypes (S, T, L) 1197 // the sliderspace 1198 g2d.fillRoundRect(-r/2, 0, r, r/2, r/2, r/2); 1199 g.setColor((_state == 2 || _state == 4) ? Color.BLACK : Color.GRAY); 1200 g2d.drawRoundRect(-r/2, 0, r, r/2, r/2, r/2); 1201 // the knob 1202 int knobX = (_state == 2 ? 0 : -r/2); 1203 g.setColor(Color.WHITE); 1204 g2d.fillOval(knobX, 0, r/2, r/2); 1205 g.setColor(Color.BLACK); 1206 g2d.drawOval(knobX, 0, r/2, r/2); 1207 //g2d.drawRect(-r, -r, 2*r, 2*r); // debug tile size outline 1208 } else if (_shape == SwitchboardEditor.KEY) { 1209 // key, same shape for all beanTypes (S, T, L) 1210 // red = upper rounded rect 1211 g.setColor(_state == 2 ? activeColor : SwitchboardEditor.darkActiveColor); // simple change in color 1212 g2d.fillRoundRect(-3*r/8, -2*r/3, 3*r/4, r/3, r/6, r/6); 1213 // green = lower rounded rect 1214 g.setColor(_state == 4 ? inactiveColor : SwitchboardEditor.darkInactiveColor); // simple change in color 1215 g2d.fillRoundRect(-3*r/8, r/3, 3*r/4, r/3, r/6, r/6); 1216 // add round LED at top (only part defined as floats) 1217 Point2D center = new Point2D.Float(0.05f*r, -7.0f*r/8.0f); 1218 float radius = r/6.0f; 1219 float[] dist = {0.0f, 0.8f}; 1220 Color[] colors = {Color.WHITE, (_state == 2 ? activeColor : Color.GRAY)}; 1221 RadialGradientPaint pnt = new RadialGradientPaint(center, radius, dist, colors); 1222 g2d.setPaint(pnt); 1223 g2d.fillOval(-r/8, -r, r/4, r/4); 1224 // with black outline 1225 g.setColor(Color.BLACK); 1226 g2d.drawOval(-r/8, -r, r/4, r/4); 1227 //g2d.drawRect(-r, -r, 2*r, 2*r); // debug tile size outline 1228 } else { 1229 // use image file 1230 g2d.drawImage(image, rop, image.getWidth()/-2, image.getHeight()/-2); // center bitmap 1231 //g2d.drawRect(-r, -r, 2*r, 2*r); // debug tile size outline 1232 } 1233 g.setFont(getFont()); 1234 if (ropOffset > 0f) { 1235 g.setColor(Color.GRAY); // dimmed 1236 } else { 1237 g.setColor(textColor); 1238 } 1239 1240 g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, textSize)); 1241 1242 if (Math.abs(textAlign - Component.CENTER_ALIGNMENT) < .0001) { 1243 FontMetrics metrics = g.getFontMetrics(); // figure out where the center of the string is 1244 labelX = metrics.stringWidth(tag)/-2; 1245 } 1246 g.drawString(tag, labelX, labelY); // draw name on top of button image (vertical, horizontal offset from top left) 1247 1248 if (showUserName == SwitchBoardLabelDisplays.BOTH_NAMES) { 1249 g.setFont(new Font(Font.SANS_SERIF, Font.ITALIC, subTextSize)); 1250 if (Math.abs(subTextAlign - Component.CENTER_ALIGNMENT) < .0001) { 1251 FontMetrics metrics = g.getFontMetrics(); // figure out where the center of the string is 1252 subLabelX = metrics.stringWidth(subTag)/-2; 1253 } 1254 g.drawString(subTag, subLabelX, subLabelY); // draw user name at bottom 1255 } else { 1256 } 1257 } 1258 } 1259 1260 private void redispatchToParent(MouseEvent e){ 1261 Component source = (Component) e.getSource(); 1262 MouseEvent parentEvent = SwingUtilities.convertMouseEvent(source, e, source.getParent()); 1263 source.getParent().dispatchEvent(parentEvent); 1264 } 1265 1266 /** 1267 * Get a resized copy of the image. 1268 * 1269 * @param image the image to rescale 1270 * @param scale scale percentage as int (will be divided by 100 in operation) 1271 * @param background background color to paint on resized image, prevents null value (black) 1272 * @return a reduced/enlarged pixel image 1273 */ 1274 public static BufferedImage resizeImage(final Image image, int scale, Color background) { 1275 int newWidth = scale*(image.getWidth(null))/100; 1276 int newHeight = scale*image.getHeight(null)/100; 1277 final BufferedImage bimg = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB); 1278 final Graphics2D g2d = bimg.createGraphics(); 1279 g2d.setColor(background); 1280 log.debug("BGCOLOR={}", background); 1281 g2d.fillRect(0, 0, newWidth, newHeight); 1282 //below three lines are for RenderingHints for better image quality at cost of higher processing time 1283 g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); 1284 g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); 1285 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 1286 g2d.drawImage(image, 0, 0, newWidth, newHeight, null); 1287 g2d.dispose(); 1288 return bimg; 1289 } 1290 1291 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(BeanSwitch.class); 1292 1293}