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