001/* 002 * jPOS Project [http://jpos.org] 003 * Copyright (C) 2000-2026 jPOS Software SRL 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jpos.iso.gui; 020 021import org.jpos.iso.ISOMsg; 022 023import javax.swing.*; 024import java.awt.*; 025import java.awt.event.MouseAdapter; 026import java.awt.event.MouseEvent; 027import java.awt.event.MouseListener; 028 029/** 030 * ISOMsgPanel 031 * Swing based GUI to ISOMsg 032 * @author apr@cs.com.uy 033 * @author Kris Leite (kleite at imcsoftware.com) 034 * @see org.jpos.iso.ISOMsg 035 */ 036@SuppressWarnings("deprecation") 037public class ISOMeter extends JComponent implements Runnable { 038 039 private static final long serialVersionUID = -1770533267122111538L; 040 /** Background color of the meter. 041 * @serial 042 */ 043 Color color = new Color (255, 255, 255); 044 /** Off-screen image buffer. 045 * @serial 046 */ 047 Image im; 048 /** Graphics context for the off-screen buffer. 049 * @serial 050 */ 051 Graphics img; 052 /** Large and small fonts used for text rendering. 053 * @serial 054 */ 055 Font fontBig, fontSmall; 056 /** Label shown for positive (inbound) activity. 057 * @serial 058 */ 059 String positiveText; 060 /** Label shown for negative (outbound) activity. 061 * @serial 062 */ 063 String negativeText; 064 /** Swing timer controlling periodic refresh. 065 * @serial 066 */ 067 Timer ti; 068 /** Positive counter string (managed externally to reduce int-to-String conversions). 069 * @serial 070 */ 071 String positiveCounter; 072 /** Negative counter string. 073 * @serial 074 */ 075 String negativeCounter; 076 /** Age counter for the positive text label. 077 * @serial 078 */ 079 int lastPositive; 080 /** Age counter for the negative text label. 081 * @serial 082 */ 083 int lastNegative; 084 /** Whether the channel is currently connected. 085 * @serial 086 */ 087 boolean connected; 088 /** Parent channel panel that owns this meter. 089 * @serial 090 */ 091 ISOChannelPanel parent; 092 093 final static int width = 200; 094 final static int height = 60; 095 final static int mass = height/2; 096 final static int MAX_VALUE = 1000; 097 098 /** Y-coordinate samples for the waveform plot. 099 * @serial 100 */ 101 int[] yPoints; 102 /** X-coordinate positions for the waveform plot. 103 * @serial 104 */ 105 int[] xPoints; 106 /** 107 * counter to keep the scrolling active 108 */ 109 int continueScroll; 110 /** 111 * used to determine if to scroll mark to end of graph 112 */ 113 boolean scroll = true; 114 /** 115 * Refresh panel in millseconds 116 */ 117 int refreshPanel = 50; 118 119 /** Off-screen image buffer used during repaint operations. */ 120 private Image imb; 121 /** Background thread that advances the animated meter display. */ 122 private Thread repaintThread; 123 124 /** Constructs an ISOMeter for the given channel panel. 125 * @param parent the parent ISOChannelPanel 126 */ 127 public ISOMeter(ISOChannelPanel parent) { 128 super(); 129 this.parent = parent; 130 131 fontBig = new Font ("Helvetica", Font.ITALIC, mass*3/4); 132 fontSmall = new Font ("Helvetica", Font.PLAIN, 10); 133 yPoints = new int[width]; 134 xPoints = new int[width]; 135 for (int i=0; i<width; i++) { 136 xPoints[i] = i; 137 yPoints[i] = mass; 138 } 139 positiveText = null; 140 negativeText = null; 141 positiveCounter = negativeCounter = ""; 142 connected = false; 143 144 MouseListener mouseListener = new MouseAdapter() { 145 public void mouseClicked(MouseEvent e) { 146 showLogList(); 147 } 148 }; 149 addMouseListener(mouseListener); 150 } 151 152 /** Starts the meter update thread. */ 153 public synchronized void start() { 154 if (repaintThread == null) { 155 repaintThread = new Thread (this,"ISOMeter"); 156 repaintThread.setPriority (Thread.NORM_PRIORITY-1); 157 repaintThread.start(); 158 } 159 } 160 161 /** Shows the log list panel. */ 162 public void showLogList() { 163 JFrame f = new JFrame(parent.getSymbolicName()); 164 f.getContentPane().add(createLogList()); 165 f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 166 f.validate(); 167 f.pack(); 168 f.setSize(width,width+50); 169 f.show(); 170 } 171 172 /** Creates and returns the log list component. 173 * @return the log list JComponent 174 */ 175 public JComponent createLogList() { 176 final JList logList = new JList(parent.getLog()); 177 JPanel A = new JPanel(); 178 A.setLayout(new BorderLayout()); 179 180 MouseListener mouseListener = new MouseAdapter() { 181 public void mouseClicked(MouseEvent e) { 182 ISOMsg m = (ISOMsg) logList.getSelectedValue(); 183 if (m != null) { 184 JFrame f = new JFrame( 185 parent.getSymbolicName()+":"+m.toString()); 186 ISOMsgPanel p = new ISOMsgPanel(m); 187 f.getContentPane().add(p); 188 f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 189 f.pack(); 190 f.show(); 191 } 192 } 193 }; 194 logList.addMouseListener(mouseListener); 195 196 logList.setPrototypeCellValue("9999 99999999 999999"); 197 JScrollPane scrollPane = new JScrollPane(logList); 198 A.add(scrollPane, BorderLayout.CENTER); 199 return A; 200 } 201 202 /** Sets the current meter value. 203 * @param val the value to display 204 */ 205 public void setValue(int val) { 206 int y = mass - val%1000 * height / 2000; 207 yPoints[width-1] = y; 208 continueScroll = width; 209 scroll(); 210 } 211 212 /** Sets whether the log list auto-scrolls. 213 * @param scroll if true, auto-scroll the log 214 */ 215 public void setScroll (boolean scroll) { 216 this.scroll = scroll; 217 } 218 /** Sets the refresh interval in milliseconds. 219 * @param refreshPanel the refresh interval 220 */ 221 public void setRefresh (int refreshPanel) { 222 if (refreshPanel > 0) 223 this.refreshPanel = refreshPanel; 224 } 225 /** Sets whether the channel is connected. 226 * @param connected true if connected 227 */ 228 public void setConnected(boolean connected) { 229 if (this.connected != connected) { 230 if (!scroll) 231 if (connected) 232 continueScroll = width; 233 else 234 continueScroll = 1; 235 repaint(); 236 } 237 this.connected = connected; 238 } 239 /** Sets the positive counter display string. 240 * @param s the counter string 241 */ 242 public void setPositiveCounter(String s) { 243 positiveCounter = s; 244 } 245 /** Sets the negative counter display string. 246 * @param s the counter string 247 */ 248 public void setNegativeCounter(String s){ 249 negativeCounter = s; 250 } 251 /** Sets the meter value with a text label. 252 * @param val the numeric value 253 * @param textString the display text 254 */ 255 public void setValue(int val, String textString) { 256 setValue(val); 257 if (val < 0) { 258 negativeText = textString; 259 lastNegative = 0; 260 } 261 else { 262 positiveText = textString; 263 lastPositive = 0; 264 } 265 } 266 /** Paints the meter component by rendering the off-screen buffer. 267 * @param g the graphics context 268 */ 269 public void paint (Graphics g) { 270 if (repaintThread == null) 271 start(); 272 plot(); 273 g.drawImage (im, 0, 0, null); 274 } 275 /** Returns the preferred size of this meter component. 276 * @return preferred dimensions 277 */ 278 public Dimension getPreferredSize() { 279 return new Dimension(width, height); 280 } 281 private void scroll() { 282 System.arraycopy(yPoints, 1, yPoints, 0, width - 1); 283 if (continueScroll > 0) 284 continueScroll--; 285 } 286 /** Renders the meter display. */ 287 public void plot() { 288 if (im == null) { 289 im = createImage(width, height); 290 img = im.getGraphics (); 291 img.setColor (Color.black); 292 img.fillRoundRect (0, 0, width, height, 10, 10); 293 img.clipRect (0, 0, width, height); 294 plotGrid(); 295 296 /* save a copy of the image */ 297 imb = createImage(width, height); 298 Graphics imbCopy = imb.getGraphics(); 299 imbCopy.drawImage (im, 0, 0, this); 300 } 301 img.drawImage (imb, 0, 0, this); 302 if (continueScroll > 0) 303 scroll(); 304 plotText(positiveText, lastPositive++, 3, mass-3); 305 plotText(negativeText, lastNegative++, 3, height-3); 306 plotCounters(positiveCounter, negativeCounter); 307 img.setColor (connected ? Color.green : Color.red); 308 img.drawPolyline(xPoints, yPoints, width); 309 } 310 private void plotGrid() { 311 img.setColor(Color.blue); 312 for (int i=0; i<width; i++) 313 if (i % 20 == 0) 314 img.drawLine(i,0,i,height); 315 for (int i=-1000; i<1000; i+= 200) { 316 int y = mass + i*height/2000; 317 img.drawLine(0,y,width,y); 318 } 319 } 320 private void plotText(String t, int l, int x, int y) { 321 if (t != null && l < 20) { 322 img.setColor(Color.lightGray); 323 img.setFont(fontBig); 324 img.drawString (t, x, y); 325 } 326 } 327 private void plotCounters(String p, String n) { 328 img.setColor(Color.lightGray); 329 img.setFont(fontSmall); 330 img.drawString (p, width-55, 13); 331 img.drawString (n, width-55, height-3); 332 } 333 /** Repaint loop run by the background thread; triggers repaints at the configured interval. */ 334 public void run () { 335 while (isShowing()) { 336 if (continueScroll > 0) 337 repaint(); 338 try { 339 Thread.sleep(refreshPanel); 340 } catch (InterruptedException e) { 341 // OK to ignore 342 } 343 } 344 repaintThread = null; 345 } 346 /** Updates the component by delegating to {@link #paint(Graphics)}. 347 * @param g the graphics context 348 */ 349 public void update (Graphics g) { 350 paint (g); 351 } 352}