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