001package jmri.jmrit.swing.meter; 002 003import java.awt.event.*; 004import java.beans.PropertyChangeListener; 005import java.text.DecimalFormat; 006import java.text.DecimalFormatSymbols; 007import java.util.ArrayList; 008import java.util.HashMap; 009import java.util.List; 010import java.util.Map; 011import java.util.UUID; 012 013import javax.swing.*; 014 015import jmri.*; 016import jmri.jmrit.catalog.NamedIcon; 017import jmri.util.JmriJFrame; 018 019/** 020 * Frame providing a simple LCD-based display of track voltage. 021 * <p> 022 * Adapted from ammeter to display voltage and current. 023 * 024 * @author Ken Cameron Copyright (C) 2007 025 * @author Mark Underwood Copyright (C) 2007 026 * @author Andrew Crosland Copyright (C) 2020 027 * @author Daniel Bergqvist Copyright (C) 2020 028 * @author B. Milhaupt Copyright (C) 2020 029 */ 030public class MeterFrame extends JmriJFrame { 031 032 public enum Unit { 033 Percent(1.0), // Not a unit, but here anyway as display option 034 MicroVolt(1000*1000), 035 MilliVolt(1000), 036 Volt(1.0), 037 KiloVolt(1/1000.0), 038 MicroAmpere(1000*1000), 039 MilliAmpere(1000), 040 Ampere(1.0), 041 KiloAmpere(1/1000.0); 042 043 private final double multiply; 044 045 Unit(double m) { multiply = m; } 046 } 047 048 private static final int MAX_INTEGER_DIGITS = 7; 049 private static final int MAX_DECIMAL_DIGITS = 3; 050 051 private final UUID uuid; 052 053 private final List<Meter> voltageMeters = new ArrayList<>(); 054 private final List<Meter> currentMeters = new ArrayList<>(); 055 056 // GUI member declarations 057 private JMenuBar menuBar; 058 ArrayList<JLabel> integerDigitIcons; 059 ArrayList<JLabel> decimalDigitIcons; 060 JLabel decimal; 061 boolean decimalDot = true; 062 Map<Unit, JLabel> unitLabels = new HashMap<>(); 063 064 Map<Meter, JCheckBoxMenuItem> meter_MenuItemMap = new HashMap<>(); 065 Map<Unit, JCheckBoxMenuItem> units_MenuItemMap = new HashMap<>(); 066 Map<Integer, JCheckBoxMenuItem> integerDigits_MenuItemMap = new HashMap<>(); 067 Map<Integer, JCheckBoxMenuItem> decimalDigits_MenuItemMap = new HashMap<>(); 068 JMenuItem lastSelectedMeterMenuItem; 069 JMenuItem lastSelectedIntegerDigitsMenuItem; 070 JMenuItem lastSelectedDecimalDigitsMenuItem; 071 int numIntegerDigits = 3; 072 int numDecimalDigits = 0; 073 int lastNumDecimalDigits = -1; 074 int widthOfAllIconsToDisplay = 0; 075 int oldWidthOfAllIconsToDisplay = -1; 076 boolean frameIsInitialized = false; 077 Unit selectedUnit = Unit.Volt; 078 079 int digitIconWidth; 080 int decimalIconWidth; 081 int unitIconWidth; 082 int iconHeight; 083 084 private PropertyChangeListener propertyChangeListener; 085 086 private Meter meter; 087 088 private String initialMeterName=""; // remember the initially selected meter since it may not exist at frame creation 089 090 NamedIcon[] integerDigits = new NamedIcon[10]; 091 NamedIcon[] decimalDigits = new NamedIcon[10]; 092 NamedIcon decimalIcon; 093 NamedIcon microVoltIcon; 094 NamedIcon milliVoltIcon; 095 NamedIcon voltIcon; 096 NamedIcon kiloVoltIcon; 097 NamedIcon microAmpIcon; 098 NamedIcon milliAmpIcon; 099 NamedIcon ampIcon; 100 NamedIcon kiloAmpIcon; 101 NamedIcon percentIcon; 102 NamedIcon errorIcon; 103 104 JPanel pane1; 105 JPanel meterPane; 106 107 public MeterFrame() { 108 this(UUID.randomUUID()); 109 } 110 111 public MeterFrame(UUID uuid) { 112 super(Bundle.getMessage("VoltageMeterTitle")); 113 114 this.uuid = uuid; 115 116 MeterManager mm = InstanceManager.getNullableDefault(MeterManager.class); 117 if (mm == null) throw new RuntimeException("No meter manager exists"); 118 119 addAllMeters(); 120 121 if (!voltageMeters.isEmpty()) { 122 setMeter(voltageMeters.get(0)); 123 } else if (!currentMeters.isEmpty()) { 124 setMeter(currentMeters.get(0)); 125 } else { 126 setTitle(Bundle.getMessage("VoltageMeterTitle")); 127 } 128 129 MeterFrameManager.getInstance().register(this); 130 } 131 132 /** 133 * Get the UUID of this frame. 134 * <p> 135 * The UUID is used if two different panel files are loaded with the same 136 * meter frame. 137 * 138 * @return the UUID of this frame 139 */ 140 public UUID getUUID() { 141 return uuid; 142 } 143 144 /** 145 * Get the meter that is displayed. 146 * @return the meter 147 */ 148 public Meter getMeter() { 149 return meter; 150 } 151 152 /** 153 * Set the meter that is displayed. 154 * @param m the meter or null if no meter is to be shown 155 */ 156 public void setMeter(Meter m) { 157 if (lastSelectedMeterMenuItem != null) lastSelectedMeterMenuItem.setSelected(false); 158 159 if (meter != null) { 160 meter.disable(); 161 meter.removePropertyChangeListener(NamedBean.PROPERTY_STATE, propertyChangeListener); 162 } 163 164 meter = m; 165 166 if (meter == null) return; 167 168 meter.addPropertyChangeListener(NamedBean.PROPERTY_STATE, propertyChangeListener); 169 meter.enable(); 170 171 if (frameIsInitialized) { 172 // Initially we want to scale the icons to fit the previously saved window size 173 scaleImage(); 174 175 JCheckBoxMenuItem menuItem = meter_MenuItemMap.get(meter); 176 menuItem.setSelected(true); 177 lastSelectedMeterMenuItem = menuItem; 178 179 updateMenuUnits(); 180 181 //set Units and Digits, except for restored settings 182 if (!getInitialMeterName().equals(m.getSystemName())) { 183 initSettingsMenu(); 184 setInitialMeterName(""); //clear out saved name after first use 185 } 186 } 187 188 if (meter instanceof VoltageMeter) { 189 setTitle(Bundle.getMessage("VoltageMeterTitle2", m.getDisplayName())); 190 } else { 191 setTitle(Bundle.getMessage("CurrentMeterTitle2", m.getDisplayName())); 192 } 193 } 194 195 JMenu voltageMetersMenu = null; 196 JMenu currentMetersMenu = null; 197 198 @Override 199 public void initComponents() { 200 MeterManager mm = InstanceManager.getNullableDefault(MeterManager.class); 201 if (mm == null) { 202 return; 203 } 204 mm.addDataListener(new MeterFrame.BeanListListener(this)); 205 // Create menu bar 206 207 menuBar = new JMenuBar(); 208 voltageMetersMenu = new JMenu(Bundle.getMessage("MenuVoltageMeters")); 209 menuBar.add(voltageMetersMenu); 210 if (voltageMeters.size() == 0) { 211 voltageMetersMenu.add(new JMenuItem(Bundle.getMessage("NoMeters"))); 212 } else { 213 for (Meter m : voltageMeters) { 214 JCheckBoxMenuItem item = new JCheckBoxMenuItem(new SelectMeterAction(m.getDisplayName(), m)); 215 voltageMetersMenu.add(item); 216 meter_MenuItemMap.put(m, item); 217 } 218 } 219 220 currentMetersMenu = new JMenu(Bundle.getMessage("MenuCurrentMeters")); 221 menuBar.add(currentMetersMenu); 222 if (currentMeters.size() == 0) { 223 currentMetersMenu.add(new JMenuItem(Bundle.getMessage("NoMeters"))); 224 } else { 225 for (Meter m : currentMeters) { 226 JCheckBoxMenuItem item = new JCheckBoxMenuItem(new SelectMeterAction(m.getDisplayName(), m)); 227 currentMetersMenu.add(item); 228 meter_MenuItemMap.put(m, item); 229 } 230 } 231 232 JMenu settingsMenu = new JMenu(Bundle.getMessage("MenuMeterSettings")); 233 menuBar.add(settingsMenu); 234 235 JMenu unitsMenu = new JMenu(Bundle.getMessage("MenuMeterUnitMenu")); 236 settingsMenu.add(unitsMenu); 237 for (Unit unit : Unit.values()) { 238 final Unit u = unit; 239 JCheckBoxMenuItem item = new JCheckBoxMenuItem(new AbstractAction(Bundle.getMessage("MenuMeter_" + unit.name())) { 240 @Override 241 public void actionPerformed(ActionEvent e) { 242 units_MenuItemMap.get(selectedUnit).setSelected(false); 243 unitLabels.get(selectedUnit).setVisible(false); 244 units_MenuItemMap.get(u).setSelected(true); 245 unitLabels.get(u).setVisible(true); 246 selectedUnit = u; 247 update(); 248 } 249 }); 250 units_MenuItemMap.put(unit, item); 251 unitsMenu.add(item); 252 } 253 254 settingsMenu.addSeparator(); 255 JMenu digitsMenu = new JMenu(Bundle.getMessage("MenuMeterIntegerDigits")); 256 settingsMenu.add(digitsMenu); 257 for (int i = 1; i <= MAX_INTEGER_DIGITS; i++) { 258 final int ii = i; 259 final String label = i + ""; 260 JCheckBoxMenuItem item = new JCheckBoxMenuItem(new AbstractAction(label) { 261 @Override 262 public void actionPerformed(ActionEvent e) { 263 integerDigits_MenuItemMap.get(numIntegerDigits).setSelected(false); 264 numIntegerDigits = ii; 265 update(); 266 } 267 }); 268 integerDigits_MenuItemMap.put(ii, item); 269 digitsMenu.add(item); 270 if (ii == numIntegerDigits) 271 item.setSelected(true); 272 } 273 274 JMenu decimalMenu = new JMenu(Bundle.getMessage("MenuMeterDecimalDigits")); 275 settingsMenu.add(decimalMenu); 276 for (int i = 0; i <= MAX_DECIMAL_DIGITS; i++) { 277 final int ii = i; 278 final String label2 = i + ""; 279 JCheckBoxMenuItem item = new JCheckBoxMenuItem(new AbstractAction(label2) { 280 @Override 281 public void actionPerformed(ActionEvent e) { 282 decimalDigits_MenuItemMap.get(numDecimalDigits).setSelected(false); 283 decimalDigits_MenuItemMap.get(ii).setSelected(true); 284 numDecimalDigits = ii; 285 update(); 286 } 287 }); 288 decimalDigits_MenuItemMap.put(ii, item); 289 decimalMenu.add(item); 290 if (ii == numDecimalDigits) 291 item.setSelected(true); 292 } 293 294 setJMenuBar(menuBar); 295 296 // clear the contents 297 getContentPane().removeAll(); 298 299 pane1 = new JPanel(); 300 pane1.setLayout(new BoxLayout(pane1, BoxLayout.Y_AXIS)); 301 302 meterPane = new JPanel(); 303 meterPane.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder())); 304 305 // build the actual multimeter display. 306 meterPane.setLayout(new BoxLayout(meterPane, BoxLayout.X_AXIS)); 307 308 // decimal separator is ',' instead of '.' in some locales 309 DecimalFormat format = (DecimalFormat) DecimalFormat.getInstance(); 310 DecimalFormatSymbols symbols = format.getDecimalFormatSymbols(); 311 if (symbols.getDecimalSeparator() == ',') { 312 decimalDot = false; 313 } 314 //Load the images (these are now the larger version of the original gifs 315 for (int i = 0; i < 10; i++) { 316 integerDigits[i] = new NamedIcon("resources/icons/misc/LCD/Lcd_" + i + "b.GIF", "resources/icons/misc/LCD/Lcd_" + i + "b.GIF"); 317 } 318 for (int i = 0; i < 10; i++) { 319 decimalDigits[i] = new NamedIcon("resources/icons/misc/LCD/Lcd_" + i + "b.GIF", "resources/icons/misc/LCD/Lcd_" + i + "b.GIF"); 320 } 321 if (decimalDot) { 322 decimalIcon = new NamedIcon("resources/icons/misc/LCD/decimalb.gif", "resources/icons/misc/LCD/decimalb.gif"); 323 } else { 324 decimalIcon = new NamedIcon("resources/icons/misc/LCD/decimalc.gif", "resources/icons/misc/LCD/decimalc.gif"); 325 } 326 microVoltIcon = new NamedIcon("resources/icons/misc/LCD/uvoltb.gif", "resources/icons/misc/LCD/uvoltb.gif"); 327 milliVoltIcon = new NamedIcon("resources/icons/misc/LCD/mvoltb.gif", "resources/icons/misc/LCD/mvoltb.gif"); 328 voltIcon = new NamedIcon("resources/icons/misc/LCD/voltb.gif", "resources/icons/misc/LCD/voltb.gif"); 329 kiloVoltIcon = new NamedIcon("resources/icons/misc/LCD/kvoltb.gif", "resources/icons/misc/LCD/kvoltb.gif"); 330 microAmpIcon = new NamedIcon("resources/icons/misc/LCD/uampb.gif", "resources/icons/misc/LCD/uampb.gif"); 331 milliAmpIcon = new NamedIcon("resources/icons/misc/LCD/mampb.gif", "resources/icons/misc/LCD/mampb.gif"); 332 ampIcon = new NamedIcon("resources/icons/misc/LCD/ampb.gif", "resources/icons/misc/LCD/ampb.gif"); 333 kiloAmpIcon = new NamedIcon("resources/icons/misc/LCD/kampb.gif", "resources/icons/misc/LCD/kampb.gif"); 334 percentIcon = new NamedIcon("resources/icons/misc/LCD/percentb.gif", "resources/icons/misc/LCD/percentb.gif"); 335 errorIcon = new NamedIcon("resources/icons/misc/LCD/Lcd_Error.GIF", "resources/icons/misc/LCD/Lcd_Error.GIF"); 336 337 decimal = new JLabel(decimalIcon); 338 unitLabels.put(Unit.Percent, new JLabel(percentIcon)); 339 unitLabels.put(Unit.MicroVolt, new JLabel(microVoltIcon)); 340 unitLabels.put(Unit.MilliVolt, new JLabel(milliVoltIcon)); 341 unitLabels.put(Unit.Volt, new JLabel(voltIcon)); 342 unitLabels.put(Unit.KiloVolt, new JLabel(kiloVoltIcon)); 343 unitLabels.put(Unit.MicroAmpere, new JLabel(microAmpIcon)); 344 unitLabels.put(Unit.MilliAmpere, new JLabel(milliAmpIcon)); 345 unitLabels.put(Unit.Ampere, new JLabel(ampIcon)); 346 unitLabels.put(Unit.KiloAmpere, new JLabel(kiloAmpIcon)); 347 348 for (Unit unit : Unit.values()) unitLabels.get(unit).setVisible(false); 349 350 integerDigitIcons = new ArrayList<>(MAX_INTEGER_DIGITS); 351 for (int i = 0; i < MAX_INTEGER_DIGITS; i++) { 352 integerDigitIcons.add(i, new JLabel(integerDigits[i])); 353 meterPane.add(integerDigitIcons.get(i)); 354 } 355 356 meterPane.add(decimal); 357 358 decimalDigitIcons = new ArrayList<>(MAX_DECIMAL_DIGITS); 359 for (int i = 0; i < MAX_DECIMAL_DIGITS; i++) { 360 decimalDigitIcons.add(i, new JLabel(decimalDigits[i])); 361 meterPane.add(decimalDigitIcons.get(i)); 362 } 363 364 for (JLabel label : unitLabels.values()) meterPane.add(label); 365 366 iconHeight = integerDigits[0].getIconHeight(); 367 digitIconWidth = integerDigits[0].getIconWidth(); 368 decimalIconWidth = decimalIcon.getIconWidth(); 369 unitIconWidth = milliVoltIcon.getIconWidth(); 370 371 // Initially we want to scale the icons to fit the previously saved window size 372 scaleImage(); 373 374 if (meter != null) { 375 meter.enable(); 376 } 377 378 updateMenuUnits(); 379 initSettingsMenu(); 380 381 // Request callback to update time 382 propertyChangeListener = (java.beans.PropertyChangeEvent e) -> update(); 383 if (meter != null) { 384 meter.addPropertyChangeListener(NamedBean.PROPERTY_STATE, propertyChangeListener); 385 } 386 387 // Add component listener to handle frame resizing event 388 this.addComponentListener( 389 new ComponentAdapter() { 390 @Override 391 public void componentResized(ComponentEvent e) { 392 scaleImage(); 393 } 394 }); 395 396 pane1.add(meterPane); 397 getContentPane().add(pane1); 398 399 getContentPane().setPreferredSize(meterPane.getPreferredSize()); 400 401 pack(); 402 403 frameIsInitialized = true; 404 } 405 406 /* Set default Units, Digits and Decimals for Settings menu 407 * based on initial/selected meter configuration. */ 408 private void initSettingsMenu() { 409 410 boolean isPercent = (meter != null) && (meter.getUnit() == Meter.Unit.Percent); 411 boolean isVoltage = (meter != null) && (meter instanceof VoltageMeter) && !isPercent; 412 boolean isCurrent = (meter != null) && (meter instanceof CurrentMeter) && !isPercent; 413 414 units_MenuItemMap.get(selectedUnit).setSelected(false); 415 unitLabels.get(selectedUnit).setVisible(false); 416 417 if (isPercent) selectedUnit = Unit.Percent; 418 else if (isVoltage && (meter.getUnit() == Meter.Unit.Milli)) selectedUnit = Unit.MilliVolt; 419 else if (isVoltage) selectedUnit = Unit.Volt; 420 else if (isCurrent && (meter.getUnit() == Meter.Unit.Milli)) selectedUnit = Unit.MilliAmpere; 421 else selectedUnit = Unit.Ampere; 422 423 units_MenuItemMap.get(selectedUnit).setSelected(true); 424 unitLabels.get(selectedUnit).setVisible(true); 425 log.debug("selectedUnit set to '{}' for '{}'", selectedUnit, uuid); 426 update(); 427 428 if (meter == null) return; // skip if meter not set 429 430 double max = meter.getMax(); 431 int iDigits = (int) (Math.log10(max) + 1); 432 log.debug("integer digits set to {} for max={} for '{}'", iDigits, max, uuid); 433 setNumIntegerDigits(iDigits); 434 435 double res = meter.getResolution(); 436 int dDigits = 0; //assume no decimals 437 if (res % 1 != 0) { //not a whole number 438 dDigits = new java.math.BigDecimal(String.valueOf(res)).scale(); // get decimal places used by resolution 439 } 440 log.debug("decimal digits set to {} for resolution={} for '{}'", dDigits, res, uuid); 441 setNumDecimalDigits(dDigits); 442 } 443 444 // Scale the clock digit images to fit the size of the display window. 445 synchronized public void scaleImage() { 446 447 int frameHeight = this.getContentPane().getHeight() 448 - meterPane.getInsets().top - meterPane.getInsets().bottom; 449 int frameWidth = this.getContentPane().getWidth() 450 - meterPane.getInsets().left - meterPane.getInsets().right; 451 452 double hscale = ((double)frameHeight)/((double)iconHeight); 453 double wscale = ((double)frameWidth)/((double)widthOfAllIconsToDisplay); 454 double scale = Math.min(hscale, wscale); 455 456 for (int i = 0; i < 10; i++) { 457 integerDigits[i].scale(scale,this); 458 } 459 for (int i = 0; i < 10; i++) { 460 decimalDigits[i].scale(scale,this); 461 } 462 decimalIcon.scale(scale,this); 463 microVoltIcon.scale(scale,this); 464 milliVoltIcon.scale(scale,this); 465 voltIcon.scale(scale,this); 466 kiloVoltIcon.scale(scale,this); 467 microAmpIcon.scale(scale,this); 468 milliAmpIcon.scale(scale,this); 469 ampIcon.scale(scale, this); 470 kiloAmpIcon.scale(scale,this); 471 percentIcon.scale(scale, this); 472 errorIcon.scale(scale, this); 473 474 meterPane.revalidate(); 475 this.getContentPane().revalidate(); 476 } 477 478 private void updateMenuUnits() { 479 boolean isPercent = (meter != null) && (meter.getUnit() == Meter.Unit.Percent); 480 boolean isVoltage = (meter != null) && (meter instanceof VoltageMeter) && !isPercent; 481 boolean isCurrent = (meter != null) && (meter instanceof CurrentMeter) && !isPercent; 482 483 units_MenuItemMap.get(Unit.Percent).setVisible(isPercent); 484 485 units_MenuItemMap.get(Unit.MicroVolt).setVisible(isVoltage); 486 units_MenuItemMap.get(Unit.MilliVolt).setVisible(isVoltage); 487 units_MenuItemMap.get(Unit.Volt).setVisible(isVoltage); 488 units_MenuItemMap.get(Unit.KiloVolt).setVisible(isVoltage); 489 490 units_MenuItemMap.get(Unit.MicroAmpere).setVisible(isCurrent); 491 units_MenuItemMap.get(Unit.MilliAmpere).setVisible(isCurrent); 492 units_MenuItemMap.get(Unit.Ampere).setVisible(isCurrent); 493 units_MenuItemMap.get(Unit.KiloAmpere).setVisible(isCurrent); 494 } 495 496 private void showError() { 497 for (int i=0; i < MAX_INTEGER_DIGITS; i++) { 498 JLabel label = integerDigitIcons.get(i); 499 if (i < numIntegerDigits) { 500 label.setIcon(errorIcon); 501 label.setVisible(true); 502 } else { 503 label.setVisible(false); 504 } 505 } 506 507 decimal.setVisible(numDecimalDigits > 0); 508 509 for (int i=0; i < MAX_DECIMAL_DIGITS; i++) { 510 JLabel label = decimalDigitIcons.get(i); 511 if (i < numDecimalDigits) { 512 label.setIcon(errorIcon); 513 label.setVisible(true); 514 } else { 515 label.setVisible(false); 516 } 517 } 518 519 // Add width of integer digits 520 widthOfAllIconsToDisplay = digitIconWidth * numIntegerDigits; 521 522 // Add decimal point 523 if (numDecimalDigits > 0) widthOfAllIconsToDisplay += decimalIconWidth; 524 525 // Add width of decimal digits 526 widthOfAllIconsToDisplay += digitIconWidth * numDecimalDigits; 527 528 // Add one for the unit icon 529 widthOfAllIconsToDisplay += unitIconWidth; 530 531 if (widthOfAllIconsToDisplay != oldWidthOfAllIconsToDisplay){ 532 // clear the content pane and rebuild it. 533 scaleImage(); 534 oldWidthOfAllIconsToDisplay = widthOfAllIconsToDisplay; 535 } 536 } 537 538 /** 539 * Update the displayed value. 540 * 541 * Assumes an integer value has an extra, non-displayed decimal digit. 542 */ 543 synchronized void update() { 544 if (meter == null) { 545 showError(); 546 return; 547 } 548 549 double meterValue = meter.getKnownAnalogValue() * selectedUnit.multiply; 550 551 switch (meter.getUnit()) { 552 case Kilo: 553 meterValue *= 1000.0; 554 break; 555 556 case Milli: 557 meterValue /= 1000.0; 558 break; 559 560 case Micro: 561 meterValue /= 1000_000.0; 562 break; 563 564 case NoPrefix: 565 case Percent: 566 default: 567 // Do nothing 568 } 569 570 // We want at least one decimal digit so we cut the last digit later. 571 // The problem is that the format string %05.0f will not add the dot 572 // and we always want the dot to be able to split the string at the dot. 573 int numChars = numIntegerDigits + numDecimalDigits + 2; 574 String formatStr = String.format("%%0%d.%df", numChars, numDecimalDigits+1); 575 String valueStr = String.format(formatStr, meterValue); 576 String rgx = "\\."; 577 if (!decimalDot) { 578 rgx = ","; // decimal separator is "," in some locales 579 } 580 String[] valueParts = valueStr.split(rgx); 581 if (valueParts.length < 2) { 582 log.warn("cannot parse meter value using locale decimal separator"); 583 return; 584 } 585 // Show error if we don't have enough integer digits to show the result 586 if (valueParts[0].length() > MAX_INTEGER_DIGITS) { 587 showError(); 588 return; 589 } 590 591 for (int i = 0; i < MAX_INTEGER_DIGITS; i++) { 592 JLabel label = integerDigitIcons.get(i); 593 if (i < valueParts[0].length()) { 594 label.setIcon(integerDigits[valueParts[0].charAt(i)-'0']); 595 label.setVisible(true); 596 } else { 597 label.setVisible(false); 598 } 599 } 600 601 decimal.setVisible(numDecimalDigits > 0); 602 603 for (int i = 0; i < MAX_DECIMAL_DIGITS; i++) { 604 JLabel label = decimalDigitIcons.get(i); 605 if (i < valueParts[1].length()-1) { // the decimal part has one digit too much 606 label.setIcon(integerDigits[valueParts[1].charAt(i)-'0']); 607 label.setVisible(true); 608 } else { 609 label.setVisible(false); 610 } 611 } 612 613 614 // Add width of integer digits 615 widthOfAllIconsToDisplay = digitIconWidth * valueParts[0].length(); 616 617 // Add width of decimal point (or other delimiter as set in system locale?) 618 if (numDecimalDigits > 0) widthOfAllIconsToDisplay += decimalIconWidth; 619 620 // Add width of decimal digits 621 widthOfAllIconsToDisplay += digitIconWidth * (valueParts[1].length()-1); 622 623 // Add one for the unit icon 624 widthOfAllIconsToDisplay += unitIconWidth; 625 626 if (widthOfAllIconsToDisplay != oldWidthOfAllIconsToDisplay){ 627 // clear the content pane and rebuild it. 628 scaleImage(); 629 oldWidthOfAllIconsToDisplay = widthOfAllIconsToDisplay; 630 } 631 } 632 633 @Override 634 public void dispose() { 635 if (meter != null) { 636 meter.disable(); 637 meter.removePropertyChangeListener(propertyChangeListener); 638 } 639 MeterFrameManager.getInstance().deregister(this); 640 super.dispose(); 641 } 642 643 /** 644 * Get the number of integer digits. 645 * 646 * @return the number of integer digits 647 */ 648 public int getNumIntegerDigits() { 649 return numIntegerDigits; 650 } 651 652 /** 653 * Set the number of integer digits. 654 * 655 * @param digits the number of integer digits 656 */ 657 public void setNumIntegerDigits(int digits) { 658 integerDigits_MenuItemMap.get(numIntegerDigits).setSelected(false); 659 integerDigits_MenuItemMap.get(digits).setSelected(true); 660 numIntegerDigits = digits; 661 update(); 662 } 663 664 /** 665 * Get the number of decimal digits. 666 * 667 * @return the number of decimal digits 668 */ 669 public int getNumDecimalDigits() { 670 return numDecimalDigits; 671 } 672 673 /** 674 * Set the number of decimal digits. 675 * 676 * @param digits the number of decimal digits 677 */ 678 public void setNumDecimalDigits(int digits) { 679 decimalDigits_MenuItemMap.get(numDecimalDigits).setSelected(false); 680 decimalDigits_MenuItemMap.get(digits).setSelected(true); 681 numDecimalDigits = digits; 682 update(); 683 } 684 685 /** 686 * Get the unit. 687 * 688 * @return the unit 689 */ 690 public Unit getUnit() { 691 return selectedUnit; 692 } 693 694 /** 695 * Set the unit. 696 * 697 * @param unit the unit 698 */ 699 public void setUnit(Unit unit) { 700 units_MenuItemMap.get(selectedUnit).setSelected(false); 701 unitLabels.get(selectedUnit).setVisible(false); 702 units_MenuItemMap.get(unit).setSelected(true); 703 unitLabels.get(unit).setVisible(true); 704 selectedUnit = unit; 705 update(); 706 } 707 708 public String getInitialMeterName() { 709 return initialMeterName; 710 } 711 712 public void setInitialMeterName(String initialMeterName) { 713 this.initialMeterName = initialMeterName; 714 } 715 716 /** 717 * Update the list of available meters. 718 */ 719 private void addAllMeters() { 720 MeterManager mm = InstanceManager.getNullableDefault(MeterManager.class); 721 if (mm == null) { 722 return; 723 } 724 if (log.isTraceEnabled()) { 725 log.trace("attempting to add all meters. There are {} meters to add.", 726 mm.getNamedBeanSet().size()); 727 } 728 mm.getNamedBeanSet().forEach((m) -> { 729 String n = m.getDisplayName(); 730 if (m instanceof VoltageMeter) { 731 if (voltageMeters.contains(m)) { 732 log.trace("voltage meter '{}' is already present", n); 733 } else { 734 voltageMeters.add(m); 735 log.trace("Added voltage meter '{}'", n); 736 } 737 } else if (m instanceof CurrentMeter) { 738 if (currentMeters.contains(m)) { 739 log.trace("current meter '{}' is already present", n); 740 } else { 741 currentMeters.add(m); 742 log.trace("Added current meter '{}'", n); 743 } 744 } 745 }); 746 747 if ((menuBar != null) && (voltageMetersMenu != null)) { 748 updateMetersMenu(voltageMetersMenu, voltageMeters); 749 } 750 751 if ((menuBar != null) && (currentMetersMenu != null)) { 752 updateMetersMenu(currentMetersMenu, currentMeters); 753 } 754 } 755 756 /** 757 * Update specified menu with specified list of meters 758 * 759 * @param menu - Menu to be updated 760 * @param meters - list of Meters 761 * 762 */ 763 private void updateMetersMenu(JMenu menu, List<Meter> meters) { 764 for (Meter meter : meters) { 765 String n = meter.getDisplayName(); 766 log.trace("need to add a new checkbox for meter '{}'?", n); 767 boolean found = false; 768 769 if (menu.getItemCount() > 0) { 770 for (int i =0; (i < menu.getItemCount()) && (!found);++i) { 771 JMenuItem jim = menu.getItem(i); 772 if (jim instanceof JCheckBoxMenuItem) { 773 if (jim.getText().compareTo(meter.getDisplayName()) == 0 ) { 774 log.trace("item '{}' is already in menu", n); 775 found = true; 776 } else { 777 log.trace("item '{}' is not already in menu", n); 778 } 779 } 780 } 781 } 782 if (!found) { 783 log.trace("Adding item '{}' to menu for frame {}", n, uuid); 784 JCheckBoxMenuItem item = new JCheckBoxMenuItem(new SelectMeterAction(n, meter)); 785 menu.add(item); 786 meter_MenuItemMap.put(meter, item); 787 //if this new meter was selected when panel stored, activate it 788 if (getInitialMeterName().equals(n)) { 789 setMeter(meter); 790 } 791 } 792 } 793 // if more menu options than meters, remove any "NoMeters" menu item. 794 if (menu.getItemCount() > meters.size()) { 795 for (int i =0; (i < menu.getItemCount());++i) { 796 JMenuItem jim = menu.getItem(i); 797 if (jim.getText().compareTo(Bundle.getMessage("NoMeters")) == 0 ) { 798 menu.remove(jim); 799 log.trace("item '{}' removed from this menu for frame {}", jim.getText(), uuid); 800 break; 801 } 802 } 803 } 804 } 805 806 /** 807 * Update the menu items in the voltage meters and current meters menus. 808 */ 809 private void updateCheckboxList() { 810 log.trace("Updating the checkbox lists of meters."); 811 addAllMeters(); 812 currentMetersMenu.repaint(); 813 voltageMetersMenu.repaint(); 814 } 815 816 /** 817 * Provide hooks so that menus of available meters may be updated "on-the-fly" 818 * as new meters are created and/or old meters are disposed of. 819 */ 820 private static class BeanListListener implements jmri.Manager.ManagerDataListener<Meter> { 821 822 private BeanListListener(MeterFrame mf) { 823 this.mf = mf; 824 } 825 MeterFrame mf; 826 827 @Override 828 public void contentsChanged(Manager.ManagerDataEvent<Meter> e) { 829 log.warn("contents of the bean list changed."); 830 mf.updateCheckboxList(); 831 } 832 833 @Override 834 public void intervalRemoved(Manager.ManagerDataEvent<Meter> e) { 835 mf.updateCheckboxList(); 836 } 837 838 @Override 839 public void intervalAdded(Manager.ManagerDataEvent<Meter> e) { 840 mf.updateCheckboxList(); 841 } 842 } 843 844 /** 845 * Mechanism for acting upon selection of a meter from one of the menu items. 846 */ 847 public class SelectMeterAction extends AbstractAction { 848 849 private final Meter m; 850 851 public SelectMeterAction(String actionName, Meter meter) { 852 super(actionName); 853 this.m = meter; 854 } 855 856 @Override 857 public void actionPerformed(ActionEvent e) { 858 setMeter(m); 859 860 JMenuItem selectedItem = (JMenuItem) e.getSource(); 861 selectedItem.setSelected(true); 862 lastSelectedMeterMenuItem = selectedItem; 863 } 864 } 865 866 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MeterFrame.class); 867 868}