001package jmri.jmrix.loconet.duplexgroup.swing; 002 003import java.awt.BasicStroke; 004import java.awt.Dimension; 005import java.awt.Font; 006import java.awt.event.ActionEvent; 007import java.awt.event.ActionListener; 008import javax.swing.BoxLayout; 009import javax.swing.JLabel; 010import javax.swing.JPanel; 011import javax.swing.JSeparator; 012import jmri.jmrix.loconet.LnConstants; 013import jmri.jmrix.loconet.LnTrafficController; 014import jmri.jmrix.loconet.LocoNetListener; 015import jmri.jmrix.loconet.LocoNetMessage; 016import jmri.jmrix.loconet.LocoNetSystemConnectionMemo; 017import jmri.jmrix.loconet.duplexgroup.LnDplxGrpInfoImplConstants; 018import org.slf4j.Logger; 019import org.slf4j.LoggerFactory; 020 021/** 022 * Defines a GUI and associated logic to perform energy scan operations on 023 * Duplex radio channels. Displays energy scan data in a graphical form. 024 * <p> 025 * This tool works equally well with UR92 and UR92CE devices. The UR92 and 026 * UR92CE behave identically with respect to this tool. For the purpose of 027 * clarity, only the term UR92 is used herein. 028 * 029 * @author B. Milhaupt Copyright 2010, 2011 030 */ 031public class DuplexGroupScanPanel extends jmri.jmrix.loconet.swing.LnPanel 032 implements LocoNetListener, javax.swing.event.ChangeListener { 033 034 DuplexChannelInfo dci[] = new DuplexChannelInfo[LnDplxGrpInfoImplConstants.DPLX_MAX_CH - LnDplxGrpInfoImplConstants.DPLX_MIN_CH + 1]; 035 private javax.swing.Timer tmr; 036 DuplexGroupScanPanel safe; 037 038 private final static int DEFAULT_SCAN_COUNT = 25; 039 private boolean isInitialized = false; 040 041 public DuplexGroupScanPanel() { 042 super(); 043 memo = null; 044 safe = this; 045 } 046 047 javax.swing.JButton scanLoopButton = null; 048 javax.swing.JLabel scanLoopLabel = null; 049 javax.swing.JButton clearButton = null; 050 javax.swing.JLabel grStatusValue = null; 051 boolean stopRequested; 052 Integer scanLoopDelay; 053 boolean waitingForPreviousGroupChannel; 054 int previousGroupChannel; 055// Dimension channelTextSize; 056 057 /** 058 * {@inheritDoc} 059 */ 060 @Override 061 public void initComponents() { 062 int i; 063 int j; 064 int minWindowWidth = 0; 065 JPanel p; 066 j = 0; 067 068 for (i = LnDplxGrpInfoImplConstants.DPLX_MIN_CH; i <= LnDplxGrpInfoImplConstants.DPLX_MAX_CH; ++i) { 069 dci[j] = new DuplexChannelInfo(); 070 dci[j].channel = i; 071 dci[j].numSamples = 0; 072 dci[j].maxSigValue = -1; 073 dci[j].minSigValue = 256; 074 dci[j].sumSamples = 0; 075 dci[j].avgSamples = 0; 076 dci[j].mostRecentSample = -1; 077 j++; 078 } 079 080 grStatusValue = new javax.swing.JLabel(" "); 081 clearButton = new javax.swing.JButton(Bundle.getMessage("ButtonClearScanData")); 082 scanLoopButton = new javax.swing.JButton(Bundle.getMessage("ButtonScanChannelsLoop")); 083 clearButton.setToolTipText(Bundle.getMessage("ToolTipButtonClearScanData")); 084 scanLoopButton.setToolTipText(Bundle.getMessage("ToolTipButtonScanChannelsLoop")); 085 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 086 087 p = new JPanel(); 088 graphicArea = new DuplexGroupChannelScanGuiCanvas(); 089 p.add(graphicArea); 090 add(p); 091 092 p = new JPanel(); 093 p.setLayout(new java.awt.GridLayout(4, 1)); 094 095 JLabel graphicAreaLabel1 = new JLabel(Bundle.getMessage("LabelGraphicArea1")); 096 graphicAreaLabel1.setFont(new Font("Dialog", Font.PLAIN, 10)); 097 p.add(graphicAreaLabel1); 098 099 JLabel graphicAreaLabel2 = new JLabel(Bundle.getMessage("LabelGraphicArea2")); 100 graphicAreaLabel2.setFont(new Font("Dialog", Font.PLAIN, 10)); 101 p.add(graphicAreaLabel2); 102 103 JLabel graphicAreaLabel3 = new JLabel(Bundle.getMessage("LabelGraphicArea3")); 104 graphicAreaLabel3.setFont(new Font("Dialog", Font.PLAIN, 10)); 105 p.add(graphicAreaLabel3); 106 add(p); 107 108 JLabel graphicAreaLabel4 = new JLabel(Bundle.getMessage("LabelGraphicArea4")); 109 graphicAreaLabel4.setFont(new Font("Dialog", Font.PLAIN, 10)); 110 p.add(graphicAreaLabel4); 111 add(p); 112 113 p = new JPanel(); 114 p.setLayout(new java.awt.FlowLayout()); 115 p.add(clearButton); 116 p.add(scanLoopButton); 117 stopRequested = false; 118 add(p); 119 120 p = new JPanel(); 121 p.setLayout(new java.awt.FlowLayout()); 122 add(new JSeparator()); 123 p.add(grStatusValue); 124 add(p); 125 p = new JPanel(); 126 // Apply a rigid area with a width that is wide enough to display the longest status message 127 try { 128 minWindowWidth = Integer.parseInt(Bundle.getMessage("MinimumWidthForWindow"), 10); 129 } catch (Exception e) { 130 minWindowWidth = 400; 131 } 132 133 p.add(javax.swing.Box.createRigidArea(new java.awt.Dimension(minWindowWidth, 0))); 134 add(p); 135 136 scanLoopButton.addActionListener(new java.awt.event.ActionListener() { 137 @Override 138 public void actionPerformed(java.awt.event.ActionEvent e) { 139 if (scanLoopButton.getText().equals(Bundle.getMessage("ButtonScanChannelsStop"))) { 140 scanLoopStopButtonActionPerformed(); 141 } else { 142 scanLoopButton.setText(Bundle.getMessage("ButtonScanChannelsStop")); 143 scanLoopButtonActionPerformed(); 144 } 145 } 146 }); 147 148 clearButton.addActionListener(new java.awt.event.ActionListener() { 149 @Override 150 public void actionPerformed(java.awt.event.ActionEvent e) { 151 scanLoopStopButtonActionPerformed(); 152 clearButtonActionPerformed(); 153 graphicArea.repaint(); 154 } 155 }); 156 157 // send message to get current Duplex Channel number 158 try { 159 scanLoopDelay = Integer.parseInt(Bundle.getMessage("SetupDefaultChannelDelayInMilliSec")); 160 } catch (Exception e) { 161 log.error("Bad value in prop files for SetupDefaultChannelDelayInMilliSec."); 162 scanLoopDelay = 200; 163 } 164 if (memo != null) { 165 isInitialized = true; 166 } 167 168 } 169 170 /** 171 * {@inheritDoc} 172 */ 173 @Override 174 public String getHelpTarget() { 175 return "package.jmri.jmrix.loconet.duplexgroup.DuplexGroupTabbedPanel"; // NOI18N replacement UR92 176 } // NOI18N 177 178 /** 179 * {@inheritDoc} 180 */ 181 @Override 182 public String getTitle() { 183 return Bundle.getMessage("ScanTitle"); 184 } 185 186 /** 187 * {@inheritDoc} 188 */ 189 @Override 190 public void initComponents(LocoNetSystemConnectionMemo memo) { 191 super.initComponents(memo); 192 193 // connect to the LnTrafficController 194 connect(memo.getLnTrafficController()); 195 waitingForPreviousGroupChannel = true; 196 memo.getLnTrafficController().sendLocoNetMessage(createGetGroupChannelPacketInt()); 197 if (grStatusValue != null) { 198 isInitialized = true; 199 } 200 } 201 202 public boolean isInitialized() { 203 return isInitialized; 204 } 205 206 /** 207 * Process all incoming LocoNet messages to look for Duplex Group 208 * information operations. Only pays attention to LocoNet report of Duplex 209 * Group Name/password/channel/groupID, and ignores all other LocoNet 210 * messages. 211 * <p> 212 * If tool has sent a query for Duplex group information and has not yet 213 * received a Duplex group report, the method updates the GUI with the 214 * received information. 215 * <p> 216 * If the tool is not currently waiting for a response to a query, then the 217 * method compares the received information against the information 218 * currently displayed in the GUI. If the received information does not 219 * match, a message is displayed on the status line in the GUI, else nothing 220 * is displayed in the GUI status line. 221 */ 222 @Override 223 public void message(LocoNetMessage m) { 224 if (stopRequested == true) { 225 return; 226 } 227 if (handleMessageDuplexScanReport(m)) { 228 return; 229 } 230 if (handleMessageDuplexChannelReport(m)) { 231 return; 232 } 233 return; 234 } 235 236 /** 237 * Examines incoming LocoNet messages to see if the message is a Duplex 238 * Group Channel Report. If so, captures the group number. 239 * 240 * @param m incoming LocoNetMessage 241 * @return true if message m is a Duplex Group Channel Report 242 */ 243 private boolean handleMessageDuplexChannelReport(LocoNetMessage m) { 244 if ((m.getOpCode() != LnConstants.OPC_PEER_XFER) 245 || (m.getElement(1) != LnConstants.RE_DPLX_OP_LEN) 246 || (m.getElement(2) != LnConstants.RE_DPLX_GP_CHAN_TYPE) 247 || (m.getElement(3) != LnConstants.RE_DPLX_SCAN_REPORT_B3)) { 248 return false; 249 } 250 if (waitingForPreviousGroupChannel) { 251 waitingForPreviousGroupChannel = false; 252 previousGroupChannel = m.getElement(5); // capture Group Channel Number 253 } 254 return true; 255 } 256 257 /** 258 * Interprets a received LocoNet message. If message is an IPL report of 259 * attached IPL-capable equipment, check to see if it reports a UR92 device 260 * as attached. If so, increment count of UR92 devices. Else ignore. 261 * 262 * @return true if message is an IPL device report indicating a UR92 263 * present, else return false. 264 */ 265 private boolean handleMessageDuplexScanReport(LocoNetMessage m) { 266 if ((m.getOpCode() != LnConstants.OPC_PEER_XFER) 267 || (m.getElement(1) != LnConstants.RE_DPLX_SCAN_OP_LEN) 268 || (m.getElement(2) != LnConstants.RE_DPLX_SCAN_REPORT_B2) 269 || (m.getElement(3) != LnConstants.RE_DPLX_SCAN_REPORT_B3)) { 270 return false; 271 } 272 handleChannelSignalReport(m.getElement(4), m.getElement(5), m.getElement(6)); 273 return true; 274 } 275 276 @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value="SLF4J_SIGN_ONLY_FORMAT", 277 justification="I18N of log message") 278 private void handleChannelSignalReport(int extendedVal, int channelNum, int signalValue) { 279 int index = -1; 280 int fullSignal; 281 fullSignal = signalValue + 128 * (((extendedVal & 0x2) == 2) ? 1 : 0); 282 for (int i = 0; i < dci.length; i++) { 283 if (dci[i].channel == channelNum) { 284 index = i; 285 } 286 } 287 if (index != -1) { 288 if (index == 16) { 289 log.error("{}\n", Bundle.getMessage("ErrorLogUnexpectedChannelNumber", channelNum)); 290 291 } 292 dci[index].numSamples++; 293 dci[index].mostRecentSample = fullSignal; 294 if (fullSignal > dci[index].maxSigValue) { 295 dci[index].maxSigValue = fullSignal; 296 } 297 if (fullSignal < dci[index].minSigValue) { 298 dci[index].minSigValue = fullSignal; 299 } 300 dci[index].sumSamples += fullSignal; 301 dci[index].avgSamples = dci[index].sumSamples / dci[index].numSamples; 302 303 graphicArea.repaint(); 304 305 } else { 306 log.error("{}",Bundle.getMessage("ErrorLogUnexpectedChannelNumber", channelNum)); 307 } 308 } 309 310 /** 311 * Creates a LocoNet message containing a channel-specific query for signal 312 * information from UR92 device(s). 313 * 314 * @param channelNum integer between 11 and 26, inclusive 315 * @return LocoNetMessage - query for Dulpex Channel Scan information 316 */ 317 private LocoNetMessage createDuplexScanQueryPacket(int channelNum) { 318 int i = 0; 319 LocoNetMessage m = new LocoNetMessage(LnConstants.RE_DPLX_OP_LEN); 320 321 m.setElement(i++, LnConstants.OPC_PEER_XFER); 322 m.setElement(i++, LnConstants.RE_DPLX_OP_LEN); // 20-byte message 323 m.setElement(i++, LnConstants.RE_DPLX_SCAN_QUERY_B2); // Duplex Group Scan Query type 324 m.setElement(i++, LnConstants.RE_DPLX_SCAN_QUERY_B3); // Query Operation 325 m.setElement(i++, LnConstants.RE_DPLX_SCAN_QUERY_B4); 326 m.setElement(i++, channelNum); // Duplex Channel Number 327 for (; i < (LnConstants.RE_DPLX_OP_LEN - 1); i++) { 328 m.setElement(i, 0); // always 0 for duplex group ID write 329 } 330 // LocoNet send process will compute and add checksum byte in correct location 331 return m; 332 } 333 334 /** 335 * Create a LocoNet packet to get the current Duplex group channel number. 336 * 337 * @return The packet which writes the Group Channel Number to the UR92 338 * device(s) 339 */ 340 private LocoNetMessage createGetGroupChannelPacketInt() { 341 int i; 342 343 // format packet 344 LocoNetMessage m = new LocoNetMessage(LnConstants.RE_DPLX_OP_LEN); 345 346 i = 0; 347 m.setElement(i++, LnConstants.OPC_PEER_XFER); 348 m.setElement(i++, LnConstants.RE_DPLX_OP_LEN); // 20-byte message 349 m.setElement(i++, LnConstants.RE_DPLX_GP_CHAN_TYPE); // Group Channel Operation 350 m.setElement(i++, LnConstants.RE_DPLX_OP_TYPE_QUERY); // Write Operation 351 for (; i < (LnConstants.RE_DPLX_OP_LEN - 1); i++) { 352 m.setElement(i, 0); // always 0 for duplex group channel query 353 } 354 // LocoNet send process will compute and add checksum byte in correct location 355 return m; 356 } 357 358 int channelIndexToScan; 359 int maxChannelIndexToScan; 360 int loopNum; 361 Integer whenToStop; 362 363 private void updateScanLoopCountStatus(int current, int total) { 364// String countStatus = Bundle.getMessage("StatusCurrentLoopCounter"); // much easier using Bundle.getMessage variables 365// String begin = countStatus.substring(0, countStatus.indexOf("%count")); // NOI18N 366// 367// String middle = countStatus.substring(begin.length() + 6, countStatus.indexOf("%loops")); // NOI18N 368// String end = countStatus.substring(countStatus.indexOf("%loops") + 6); // NOI18N 369// countStatus = begin + Integer.toString(current) + middle + Integer.toString(total) + end; 370 grStatusValue.setText(Bundle.getMessage("StatusCurrentLoopCounter", current, total)); 371 } 372 373 private void scanLoopButtonActionPerformed() { 374 loopNum = 1; 375 try { 376 whenToStop = Integer.parseInt(Bundle.getMessage("SetupNumberOfLoops")); 377 } catch (Exception e) { 378 whenToStop = DEFAULT_SCAN_COUNT; 379 } 380 if ((whenToStop <= 0) || (whenToStop > 1000)) { 381 grStatusValue.setText(Bundle.getMessage("ErrorBadLoopCount")); 382 return; 383 } 384 grStatusValue.setText(" "); 385 stopRequested = false; 386 387 channelIndexToScan = 0; 388 maxChannelIndexToScan = LnDplxGrpInfoImplConstants.DPLX_MAX_CH - LnDplxGrpInfoImplConstants.DPLX_MIN_CH; 389 updateScanLoopCountStatus(loopNum, whenToStop); 390 391 tmr = new javax.swing.Timer(scanLoopDelay, new ActionListener() { 392 @Override 393 public void actionPerformed(ActionEvent e) { 394 tmr.stop(); 395 if (stopRequested == true) { 396 stopRequested = false; 397 showOnlyMaxAvgValues(); 398 } else if (channelIndexToScan <= maxChannelIndexToScan) { 399 400 graphicArea.setChannelBeingScanned(dci[channelIndexToScan].channel); 401 graphicArea.repaint(); 402 memo.getLnTrafficController().sendLocoNetMessage(createDuplexScanQueryPacket(dci[channelIndexToScan].channel)); 403 tmr.setInitialDelay(scanLoopDelay); 404 tmr.setRepeats(false); 405 tmr.start(); 406 channelIndexToScan++; 407 } else if (loopNum < whenToStop) { 408 loopNum++; 409 // update displayed Loop Count 410 updateScanLoopCountStatus(loopNum, whenToStop); 411 412 channelIndexToScan = 0; 413 graphicArea.setChannelBeingScanned(dci[channelIndexToScan].channel); 414 graphicArea.repaint(); 415 416 memo.getLnTrafficController().sendLocoNetMessage(createDuplexScanQueryPacket(dci[channelIndexToScan].channel)); 417 tmr.setInitialDelay(scanLoopDelay); 418 tmr.setRepeats(false); 419 tmr.start(); 420 channelIndexToScan++; 421 } else { 422 // must be done with all channels and all loops. 423 showOnlyMaxAvgValues(); 424 scanLoopButton.setText(Bundle.getMessage("ButtonScanChannelsLoop")); 425 scanLoopStopButtonActionPerformed(); 426 grStatusValue.setText(" "); 427 graphicArea.setChannelBeingScanned(-1); 428 graphicArea.repaint(); 429 } 430 } 431 }); 432 // need to trigger first delay to get first channel to be scanned 433 tmr.setInitialDelay(scanLoopDelay); 434 tmr.setRepeats(false); 435 tmr.start(); 436 return; 437 } 438 439 private void scanLoopStopButtonActionPerformed() { 440 scanLoopButton.setText(Bundle.getMessage("ButtonScanChannelsLoop")); 441 graphicArea.setChannelBeingScanned(-1); 442 graphicArea.repaint(); 443 grStatusValue.setText(" "); 444 stopRequested = true; 445 } 446 447 private void clearButtonActionPerformed() { 448 int index; 449 int maxIndex; 450 maxIndex = LnDplxGrpInfoImplConstants.DPLX_MAX_CH - LnDplxGrpInfoImplConstants.DPLX_MIN_CH; 451 for (index = 0; index <= maxIndex; ++index) { 452 dci[index].numSamples = 0; 453 dci[index].maxSigValue = -1; 454 dci[index].minSigValue = 256; 455 dci[index].sumSamples = 0; 456 dci[index].avgSamples = 0; 457 dci[index].mostRecentSample = -1; 458 } 459 return; 460 } 461 462 public void connect(LnTrafficController t) { 463 if (t != null) { 464 // connect to the LnTrafficController if the connection is a valid LocoNet connection 465 t.addLocoNetListener(~0, this); 466 } 467 } 468 469 /** 470 * Break connection with the LnTrafficController and stop timers. 471 */ 472 @Override 473 public void dispose() { 474 javax.swing.Timer exitTmr; 475 476 stopRequested = true; 477 if (tmr != null) { 478 tmr.stop(); 479 } 480 tmr = null; 481 482 if (waitingForPreviousGroupChannel == false) { 483 exitTmr = new javax.swing.Timer(200, new ActionListener() { 484 @Override 485 public void actionPerformed(ActionEvent e) { 486 if (memo.getLnTrafficController() != null) { 487 memo.getLnTrafficController().removeLocoNetListener(~0, safe); 488 } 489 safe.dispose(); 490 } 491 }); 492 exitTmr.setInitialDelay(200); 493 exitTmr.setRepeats(false); 494 exitTmr.start(); 495 while (exitTmr.isRunning()) { 496 // wait for timer to run out before releasing LocoNet traffic controller listener 497 } 498 exitTmr.stop(); 499 } 500 if (memo.getLnTrafficController() != null) { 501 memo.getLnTrafficController().removeLocoNetListener(~0, this); 502 } 503 super.dispose(); 504 } 505 506 private final static Logger log = LoggerFactory.getLogger(DuplexGroupScanPanel.class); 507 508 @Override 509 public void stateChanged(javax.swing.event.ChangeEvent e) { 510 graphicArea.repaint(); 511 } 512 513 private void showOnlyMaxAvgValues() { 514 for (int i = 0; i < (LnDplxGrpInfoImplConstants.DPLX_MAX_CH - LnDplxGrpInfoImplConstants.DPLX_MIN_CH) + 1; ++i) { 515 dci[i].mostRecentSample = -1; 516 } 517 graphicArea.repaint(); 518 } 519 520 private DuplexGroupChannelScanGuiCanvas graphicArea; 521 522 private class DuplexGroupChannelScanGuiCanvas extends java.awt.Canvas { 523 524 private int barWidth = 7; 525 private int barSpace = barWidth + 8; 526 private int barOffset = (barSpace - barWidth) / 2; 527 private final static int channelCount = 26 - 11 + 1; 528 private final static int barGraphScale = 2; 529 private final static int maxScanValue = 255; 530 private final static int maxScaledBarValue = ((maxScanValue + 1) / barGraphScale); 531 private int baseline = maxScaledBarValue + 5; 532 533 public int requiredMinWindowWidth = (channelCount * barSpace); 534 public int requiredMinWindowHeight = (baseline + 10); 535 private static final int HORIZ_PADDING = 12; 536 private static final int VERT_PADDING = 4; 537 private int indexBeingScanned = -1; 538 private Dimension channelTextSize; 539 540 Font signalBarsFont; 541 private final java.awt.Color foregroundColor = java.awt.Color.WHITE; 542 private final java.awt.Color backgroundColor = java.awt.Color.BLACK; 543 private final java.awt.Color recommendationLineColor = java.awt.Color.YELLOW; 544 private final java.awt.Color valueBarColor = java.awt.Color.CYAN; 545 private final java.awt.Color maxLineColor = java.awt.Color.RED; 546 private final java.awt.Color averageLineColor = java.awt.Color.GREEN; 547 private final java.awt.Color lowerLimitLineColor = java.awt.Color.LIGHT_GRAY; 548 549 public DuplexGroupChannelScanGuiCanvas() { 550 super(); 551 setBackground(backgroundColor); 552 setForeground(foregroundColor); 553 // create a smaller font 554 signalBarsFont = new Font("Dialog", Font.PLAIN, 8); 555 556 int textHeight = 0; 557 int textWidth = 0; 558 559 // get metrics from the graphics 560 java.awt.FontMetrics metrics = getFontMetrics(signalBarsFont); 561 // get the height of a line of text in this font and render context 562 textHeight = metrics.getHeight(); 563 // get the advance of my text in this font and render context 564 textWidth = metrics.stringWidth("38"); // representative (but not accurate) example text string // NOI18N 565 // calculate the size of a box to hold the text with some padding. 566 channelTextSize = new Dimension(textWidth + HORIZ_PADDING, textHeight + VERT_PADDING); 567 requiredMinWindowWidth = channelCount * channelTextSize.width; 568 requiredMinWindowHeight += (2 * channelTextSize.height); 569 baseline += channelTextSize.height; 570 barSpace = channelTextSize.width; 571 barWidth = textWidth; 572 barOffset = (barSpace - barWidth) / 2; 573 textWidth = 0; 574 setSize(requiredMinWindowWidth, requiredMinWindowHeight); 575 } 576 577 /** 578 * Used by this class to specify a channel number to highlight in the 579 * GUI. An invalid channel number may be used to cause the class to 580 * clear the channel highlight. After invoking this method, a call to 581 * this class' repaint() method is required to cause the GUI update. 582 * 583 * @param channelNum integer representing a Duplex Group channel 584 * number. 585 */ 586 public void setChannelBeingScanned(int channelNum) { 587 if ((channelNum < 11) || (channelNum > 26)) { 588 indexBeingScanned = -1; 589 return; 590 } 591 indexBeingScanned = channelNum - 11; 592 } 593 594 final float dash1[] = {7.0f, 3.0f}; 595 final BasicStroke dashedStroke = new BasicStroke(1.0f, 596 BasicStroke.CAP_BUTT, 597 BasicStroke.JOIN_MITER, 598 10.0f, dash1, 0.0f); 599 600 final BasicStroke plainStroke = new BasicStroke(1.0f); 601 602 @Override 603 public void paint(java.awt.Graphics g) { 604 int channelIndex; 605 java.awt.Graphics2D g2; 606 if (g instanceof java.awt.Graphics2D) { 607 g2 = (java.awt.Graphics2D) g; 608 } else { 609 log.error("paint() cannot cast object g to Graphics2D. Aborting paint()."); 610 return; 611 } 612 for (int i = 11; i <= 26; ++i) { 613 g2.drawString(Integer.toString(i), (i - 11) * channelTextSize.width, channelTextSize.height); 614 g2.drawString(Integer.toString(i), (i - 11) * channelTextSize.width, requiredMinWindowHeight - 1); 615 } 616 617 // draw a simple line to act as a bottom line in the graphic block 618 g2.setColor(lowerLimitLineColor); 619 g2.draw(new java.awt.geom.Line2D.Float(0, baseline + 1, 620 requiredMinWindowWidth - 1, baseline + 1)); 621 622 // draw a bar, average line and max line for each channel 623 for (channelIndex = 0; channelIndex < 16; ++channelIndex) { 624 redrawSignalBar(g2, dci[channelIndex]); 625 } 626 // draw a diamond for the area showing the channel being scanned 627 redrawChannelAtIndicator(g2, indexBeingScanned); 628 629 // draw the recommended limit line 630 g2.setColor(recommendationLineColor); 631 g2.setStroke(dashedStroke); 632 g2.draw(new java.awt.geom.Line2D.Float(1, baseline - (96 / barGraphScale), 633 requiredMinWindowWidth - 1, baseline - (96 / barGraphScale))); 634 g2.setStroke(plainStroke); 635 } 636 637 private void redrawSignalBar(java.awt.Graphics2D g2, DuplexChannelInfo dci) { 638 int index = dci.channel - 11; 639 int current = dci.mostRecentSample; 640 int max = dci.maxSigValue; 641 int avg = dci.avgSamples; 642 if (avg < 0) { 643 avg = 0; 644 } 645 int numSamples = dci.numSamples; 646 647 if (current > 0) { 648 int upperX; 649 int upperY; 650 int width; 651 int height; 652 653 upperX = (barSpace * index); 654 width = barSpace; 655 656 // clear anything above the "bottoms line" 657 upperY = baseline - maxScaledBarValue; 658 height = (maxScaledBarValue - 1); 659 g2.setColor(backgroundColor); 660 g2.fillRect(upperX, upperY, 661 width, height - 1); 662 663 // draw the filled rectangle for the current value. 664 upperY = baseline - (current / barGraphScale); 665 g2.setColor(valueBarColor); 666 g2.fillRect(upperX + barOffset, upperY, 667 barWidth, (current / barGraphScale)); 668 669 } else { 670 // clear anything above the "bottoms line" 671 g2.setColor(backgroundColor); 672 g2.fillRect( 673 (barOffset + (barSpace * index)), ((baseline - maxScaledBarValue) - 1), 674 barWidth, maxScaledBarValue); 675 } 676 677 if (numSamples > 1) { 678 // draw the line for the average value. 679 g2.setColor(averageLineColor); 680 g2.draw(new java.awt.geom.Line2D.Float( 681 (barSpace * index) + 1, ((baseline - (avg / barGraphScale)) - 1), 682 (barSpace * (index + 1)) - 2, (baseline - (avg / barGraphScale)) - 1)); 683 } 684 685 // draw the line for the max value. 686 if (max >= 0) { 687 g2.setColor(maxLineColor); 688 g2.draw(new java.awt.geom.Line2D.Float( 689 (barSpace * index) + 1, ((baseline - (max / barGraphScale)) - 1), 690 (barSpace * (index + 1)) - 2, (baseline - (max / barGraphScale)) - 1)); 691 } 692 } 693 694 private void redrawChannelAtIndicator(java.awt.Graphics2D g2, int channelIndex) { 695 int upperX; 696 int upperY; 697 int width; 698 int height; 699 // clear anything below the "bottoms line" 700 upperY = baseline + 2; 701 height = requiredMinWindowHeight - upperY - channelTextSize.height - 2; 702 upperX = 0; 703 width = requiredMinWindowWidth; 704 g2.setColor(backgroundColor); 705 g2.fillRect(upperX, upperY, 706 width, height - 1); 707 708 // show the highlight only if a valid channel index (1-16) is specified 709 if ((channelIndex >= 0) && (channelIndex < 16)) { 710 // draw a diamond in black using polyline mechanisms 711 g2.setColor(foregroundColor); 712 int x2Points[] = {(channelIndex * barSpace) + (barSpace / 2), 713 (channelIndex * barSpace) + barOffset, 714 channelIndex * barSpace + (barSpace / 2), 715 (channelIndex * barSpace) + (barSpace - barOffset)}; 716 int y2Points[] = {baseline + 2, baseline + 5, baseline + 8, baseline + 5}; 717 java.awt.geom.GeneralPath polygon 718 = new java.awt.geom.GeneralPath(java.awt.geom.GeneralPath.WIND_EVEN_ODD, 719 x2Points.length); 720 721 polygon.moveTo(x2Points[0], y2Points[0]); 722 723 for (int index = 1; index < x2Points.length; index++) { 724 polygon.lineTo(x2Points[index], y2Points[index]); 725 } 726 polygon.closePath(); 727 g2.draw(polygon); 728 } 729 } 730 } 731 732 /** 733 * Implements a basic structure for tracking Duplex Radio channel energy 734 * scan information. 735 * 736 * @author B. Milhaupt Copyright 2010, 2011 737 */ 738 private static class DuplexChannelInfo { 739 740 public int channel; 741 public int numSamples; 742 public int maxSigValue; 743 public int minSigValue; 744 public int sumSamples; 745 public int avgSamples; 746 public int mostRecentSample; 747 748 public DuplexChannelInfo() { 749 channel = -1; 750 numSamples = 0; 751 maxSigValue = -1; 752 minSigValue = 1000; 753 sumSamples = 0; 754 avgSamples = 0; 755 mostRecentSample = -1; 756 } 757 } 758 759}