KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > net > fenyo > gnetwatch > GUI > BasicComponent


1
2 /*
3  * GNetWatch
4  * Copyright 2006, 2007 Alexandre Fenyo
5  * gnetwatch@fenyo.net
6  *
7  * This file is part of GNetWatch.
8  *
9  * GNetWatch is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * GNetWatch is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with GNetWatch; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22  */

23
24 package net.fenyo.gnetwatch.GUI;
25
26 import java.awt.Color JavaDoc;
27 import java.awt.Component JavaDoc;
28 import java.awt.Dimension JavaDoc;
29 import java.awt.Graphics JavaDoc;
30 import java.awt.Graphics2D JavaDoc;
31 import java.awt.Image JavaDoc;
32 import java.awt.RenderingHints JavaDoc;
33 import java.awt.event.ComponentEvent JavaDoc;
34 import java.awt.event.ComponentListener JavaDoc;
35 import java.awt.event.WindowEvent JavaDoc;
36 import java.awt.event.WindowListener JavaDoc;
37 import java.awt.font.TextLayout JavaDoc;
38 import java.awt.geom.Rectangle2D JavaDoc;
39 import java.text.DateFormat JavaDoc;
40 import java.util.Date JavaDoc;
41
42 import net.fenyo.gnetwatch.data.EventGeneric;
43 import net.fenyo.gnetwatch.targets.Target;
44
45 import org.apache.commons.logging.Log;
46 import org.apache.commons.logging.LogFactory;
47
48 /**
49  * This class implements an AWT component capable of drawing time series with the Java2D API.
50  * @author Alexandre Fenyo
51  * @version $Id: BasicComponent.java,v 1.23 2007/03/03 00:38:19 fenyo Exp $
52  */

53
54 // any thread << sync_value_per_vinterval << sync_update << events
55
// repaint thread << sync_value_per_vinterval << events
56
// repaint thread << sync_value_per_vinterval << sync_update << registered_components
57

58 public abstract class BasicComponent extends Component JavaDoc implements ComponentListener JavaDoc, WindowListener JavaDoc {
59   private static Log log = LogFactory.getLog(BasicComponent.class);
60
61   final private Target target;
62
63   private static Object JavaDoc sync_update = new Object JavaDoc();
64
65   private java.util.List JavaDoc<EventGeneric> events = null;
66
67   private Image JavaDoc backing_store = null;
68   private Graphics2D JavaDoc backing_g = null;
69   private Dimension JavaDoc dimension = null;
70
71   private final DateFormat JavaDoc date_format = DateFormat.getDateTimeInstance();
72
73   private int fps = 0;
74   private long last_paint = System.currentTimeMillis();
75   private long last_paint_100ms = System.currentTimeMillis();
76   private int last_fps_100ms = 0;
77
78   // horizontal interval
79
protected final int pixels_per_interval = 80; // 80 pix/intervalle ; multiple de 10
80

81   // vertical intervals
82
private int value_per_vinterval = 200;
83   private final Object JavaDoc sync_value_per_vinterval = new Object JavaDoc();
84   private final int pixels_per_vinterval = 80; // 80 pix/vintervalle ; multiple de 10
85

86   private final static int std_margin = 30;
87   private final static int std_separator = 3;
88
89   private final static int axis_margin_bottom = std_margin;
90   private final static int axis_margin_top = std_margin;
91   protected int axis_margin_left = std_margin;
92   protected final static int axis_margin_right = std_margin;
93
94   /**
95    * Constructor.
96    * @param target target this graphic component works on.
97    */

98   // GUI thread
99
public BasicComponent(final Target target) {
100     this.target = target;
101   }
102
103   /**
104    * Returns the horizontal scale.
105    * @param none.
106    * @return long horizontal scale.
107    */

108   public long getDelayPerInterval() {
109     return 5000; // 5 secs/interval
110
}
111
112   /**
113    * Sets the list of events to display.
114    * @param events events to display.
115    * @return void.
116    */

117   protected void setEvents(final java.util.List JavaDoc<EventGeneric> events) {
118     this.events = events;
119   }
120
121   /**
122    * Returns the dimensions of this component.
123    * @param none.
124    * @return Dimension dimensions.
125    */

126   protected Dimension JavaDoc getDimension() {
127     return dimension;
128   }
129
130   /**
131    * Returns the multithreaded synchronization lock.
132    * @param none.
133    * @return Object lock.
134    */

135   protected Object JavaDoc getSyncUpdate() {
136     return sync_update;
137   }
138
139   /**
140    * Returns the associated target.
141    * @param none.
142    * @return Target target.
143    */

144   protected Target getTarget() {
145     return target;
146   }
147
148   /**
149    * Initialize the component and ask AWT to receive events.
150    *
151    */

152   // GUI thread
153
public void init() {
154     setPreferredSize(new Dimension JavaDoc(700, 400));
155     addComponentListener(this);
156   }
157
158   /**
159    * Called when the component is hidden.
160    * @param e event.
161    * @return void.
162    */

163   public void componentHidden(final ComponentEvent JavaDoc e) {
164   }
165
166   /**
167    * Called when the component is moved.
168    * @param e event.
169    * @return void.
170    */

171   public void componentMoved(final ComponentEvent JavaDoc e) {
172   }
173
174   /**
175    * When the component is resized, creates a new backing store,
176    * reset margins and fetch events that can be displayed.
177    * @param e event.
178    * @return void.
179    */

180   // AWT thread
181
// AWT thread << sync_value_per_vinterval
182
public void componentResized(final ComponentEvent JavaDoc e) {
183     synchronized (sync_value_per_vinterval) {
184       newBackingElts();
185       setMargin();
186       updateValues();
187     }
188   }
189
190   /**
191    * Called when the component appears first.
192    * @param e event.
193    * @return void.
194    */

195   // AWT thread
196
public void componentShown(final ComponentEvent JavaDoc e) {
197     componentResized(e);
198   }
199
200   /**
201    * Called whenthe window is activated.
202    * @param e event.
203    * @return void.
204    */

205   public void windowActivated(final WindowEvent JavaDoc e) {
206   }
207
208   /**
209    * Called whenthe window is closed.
210    * @param e event.
211    * @return void.
212    */

213   public void windowClosed(final WindowEvent JavaDoc e) {
214   }
215
216   /**
217    * Called when the window is closing.
218    * @param e event.
219    * @return void.
220    */

221   // AWT thread
222
public abstract void windowClosing(final WindowEvent JavaDoc e);
223
224   /**
225    * Called whenthe window is deactivated.
226    * @param e event.
227    * @return void.
228    */

229   public void windowDeactivated(final WindowEvent JavaDoc e) {
230   }
231
232   /**
233    * Called whenthe window is deiconified.
234    * @param e event.
235    * @return void.
236    */

237   public void windowDeiconified(final WindowEvent JavaDoc e) {
238   }
239
240   /**
241    * Called whenthe window is iconified.
242    * @param e event.
243    * @return void.
244    */

245   public void windowIconified(final WindowEvent JavaDoc e) {
246   }
247
248   /**
249    * Called whenthe window is opened.
250    * @param e event.
251    * @return void.
252    */

253   public void windowOpened(final WindowEvent JavaDoc e) {
254   }
255
256   /**
257    * Computes new margins.
258    * @param none.
259    * @return void.
260    */

261   // AWT thread
262
// AWT thread << sync_value_per_vinterval
263
private void setMargin() {
264     final String JavaDoc left_values_str = "" + (int) (value_per_vinterval *
265         (1 + (dimension.height - axis_margin_top - axis_margin_bottom) / pixels_per_vinterval));
266     final TextLayout JavaDoc layout = new TextLayout JavaDoc(left_values_str, backing_g.getFont(), backing_g.getFontRenderContext());
267     final Rectangle2D JavaDoc bounds = layout.getBounds();
268     axis_margin_left = (int) bounds.getWidth() + 5 + 10;
269   }
270
271   /**
272    * Creates a backing store.
273    * @param none.
274    * @return void.
275    */

276   // AWT thread
277
// AWT thread << sync_value_per_vinterval
278
private void newBackingElts() {
279     dimension = getSize();
280     backing_store = createImage(dimension.width, dimension.height);
281     backing_g = (Graphics2D JavaDoc) backing_store.getGraphics();
282     backing_g.setBackground(Color.BLACK);
283     backing_g.setColor(Color.WHITE);
284     backing_g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
285         RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
286     backing_g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
287         RenderingHints.VALUE_ANTIALIAS_ON);
288   }
289
290   /**
291    * Removes events that can not be displayed.
292    * @param none.
293    * @return void.
294    *
295    */

296   // Queue thread
297
private void removeOldEvents() {
298     final Date JavaDoc begin = new Date JavaDoc(System.currentTimeMillis() - getDelayPerInterval() *
299         (dimension.width - axis_margin_left - axis_margin_right) / pixels_per_interval);
300         while (events.size() >= 2 && (events.get(1).getDate().before(begin) || events.get(1).getDate().equals(begin)))
301           events.remove(0);
302   }
303
304   /**
305    * Updates the vertical scale.
306    * @param none.
307    * @return void.
308    */

309   // Queue & AWT thread
310
protected void updateVerticalScale() {
311     final int previous_value_per_vinterval = value_per_vinterval;
312
313     int max_value = 0;
314     int i = 0;
315     for (final EventGeneric event : events) {
316       final int value = event.getIntValue(events, i++);
317       if (value > max_value) max_value = value;
318     }
319
320     int nintervals = (dimension.height - axis_margin_top - axis_margin_bottom) / pixels_per_vinterval - 1;
321     if (nintervals < 1) nintervals = 1;
322     value_per_vinterval = max_value / nintervals;
323
324     // possible values for value_per_vinterval: 1, 2, 5, 10, 20, 25, 50, 100, 200, etc.
325
final int base = (int) Math.pow(10, new Integer JavaDoc(value_per_vinterval).toString().length() - 1);
326     if (value_per_vinterval != base)
327       if (value_per_vinterval <= 2 * base) value_per_vinterval = 2 * base;
328       else if (base >= 10 && value_per_vinterval <= 25 * base / 10) value_per_vinterval = 25 * base / 10;
329       else if (value_per_vinterval <= 5 * base) value_per_vinterval = 5 * base;
330       else value_per_vinterval = 10 * base;
331
332     if (value_per_vinterval == 0) value_per_vinterval = 1;
333
334     if (previous_value_per_vinterval != value_per_vinterval) setMargin();
335   }
336
337   /**
338    * Takes a new event into account.
339    * @param event new event.
340    * @return void.
341    */

342   // Queue thread
343
// any thread ( << sync_value_per_vinterval ) << sync_value_per_vinterval << sync_update << events
344
public void updateValues(final EventGeneric event) {
345     synchronized (sync_value_per_vinterval) {
346       synchronized (sync_update) {
347         synchronized (events) {
348           for (int i = events.size() - 1; i >= 0; i--)
349             if (events.get(i).getDate().before(event.getDate())) {
350               if (i + 1 < events.size()) events.add(i + 1, event);
351               else events.add(event);
352               removeOldEvents();
353               updateVerticalScale();
354               return;
355             }
356           events.add(0, event);
357           removeOldEvents();
358           updateVerticalScale();
359         }
360       }
361     }
362   }
363
364   /**
365    * Fetches events that can be displayed.
366    * @param none.
367    * @return void.
368    */

369   // AWT thread
370
// AWT thread << sync_value_per_vinterval << sync_update << registered_components
371
protected abstract void updateValues();
372
373   /**
374    * Computes the "frames per second" indicator.
375    * @param none.
376    * @return void.
377    */

378   // AWT thread
379
private void updateFPS() {
380     final long current_time = System.currentTimeMillis();
381     fps = 100 / (int) (current_time - last_paint + 1) + (9 * fps) / 10;
382     last_paint = current_time;
383   }
384
385   /**
386    * Displays the number of frames per second.
387    * @param fps frames per second to display.
388    * @return void.
389    */

390   // AWT thread
391
private void paintFPS(final int fps) {
392     backing_g.setColor(Color.GRAY);
393     backing_g.drawString(fps + " frames/s", 1, 10);
394   }
395
396   /**
397    * Formats a time string to be displayed.
398    * @param time time.
399    * @return String time to be displayed.
400    */

401   // AWT thread
402
private String JavaDoc formatTime(final long time) {
403     String JavaDoc str = date_format.format(new Date JavaDoc(time));
404     return str.substring(str.lastIndexOf(' ') + 1);
405   }
406
407   /**
408    * Paints axis.
409    * @param none.
410    * @return long current time displayed at the axis bottom.
411    */

412   // AWT thread
413
private long paintAxis() {
414     backing_g.setColor(new Color JavaDoc(50, 50, 50));
415     backing_g.fillRect(axis_margin_left, axis_margin_top,
416         dimension.width - axis_margin_left - axis_margin_right + 1,
417         dimension.height - axis_margin_top - axis_margin_bottom + 1);
418
419     backing_g.setColor(Color.YELLOW);
420     backing_g.drawLine(axis_margin_left, dimension.height - axis_margin_bottom,
421         dimension.width - axis_margin_right, dimension.height - axis_margin_bottom);
422     backing_g.drawLine(axis_margin_left, axis_margin_top,
423         axis_margin_left, dimension.height - axis_margin_bottom);
424
425     backing_g.setColor(Color.YELLOW.darker());
426     backing_g.drawLine(axis_margin_left + 1, axis_margin_top,
427         dimension.width - axis_margin_right,
428         axis_margin_top);
429     backing_g.drawLine(dimension.width - axis_margin_right,
430         axis_margin_top,
431         dimension.width - axis_margin_right,
432         dimension.height - axis_margin_bottom - 1);
433
434     int vinterval_pos = dimension.height - axis_margin_bottom - pixels_per_vinterval;
435     backing_g.setColor(Color.YELLOW.darker().darker().darker());
436     while (vinterval_pos + 9 * (pixels_per_vinterval / 10) > axis_margin_top) {
437       int cpt = 10;
438       while (--cpt > 0)
439         if (vinterval_pos + cpt * (pixels_per_vinterval / 10) > axis_margin_top)
440           backing_g.drawLine(axis_margin_left + 1, vinterval_pos + cpt * (pixels_per_vinterval / 10),
441               dimension.width - axis_margin_right - 1, vinterval_pos + cpt * (pixels_per_vinterval / 10));
442       vinterval_pos -= pixels_per_vinterval;
443     }
444
445     final long now = System.currentTimeMillis();
446
447     final long time_to_display = now - now % getDelayPerInterval();
448     final int pixels_offset = (pixels_per_interval * (int) (now % getDelayPerInterval())) / (int) getDelayPerInterval();
449     final int last_interval_pos = dimension.width - axis_margin_right - pixels_offset;
450
451     backing_g.setClip(axis_margin_left, 0,
452         dimension.width - axis_margin_left - axis_margin_right,
453         dimension.height);
454     int current_interval_pos = last_interval_pos + pixels_per_interval;
455     long current_time_to_display = time_to_display + getDelayPerInterval();
456     boolean stop = false;
457     while (stop == false) {
458       backing_g.setColor(Color.YELLOW.darker());
459       backing_g.drawLine(current_interval_pos, axis_margin_top,
460           current_interval_pos, dimension.height - axis_margin_bottom + std_separator);
461
462       int cpt = 10;
463       backing_g.setColor(Color.YELLOW.darker().darker().darker());
464       while (--cpt > 0)
465         if (current_interval_pos - cpt * (pixels_per_interval / 10) > axis_margin_left)
466           backing_g.drawLine(current_interval_pos - cpt * (pixels_per_interval / 10),
467               axis_margin_top + 1,
468               current_interval_pos - cpt * (pixels_per_interval / 10),
469               dimension.height - axis_margin_bottom - 1);
470
471       final String JavaDoc current_time_str = formatTime(current_time_to_display);
472       final TextLayout JavaDoc current_layout = new TextLayout JavaDoc(current_time_str, backing_g.getFont(), backing_g.getFontRenderContext());
473       final Rectangle2D JavaDoc current_bounds = current_layout.getBounds();
474       backing_g.setColor(Color.YELLOW.darker());
475       backing_g.drawString(current_time_str,
476           current_interval_pos - (int) (current_bounds.getWidth() / 2),
477           dimension.height - axis_margin_bottom + (int) current_bounds.getHeight() + 2 * std_separator);
478       if (current_interval_pos - current_bounds.getWidth() / 2 < axis_margin_left)
479         stop = true;
480       current_interval_pos -= pixels_per_interval;
481       current_time_to_display -= getDelayPerInterval();
482     }
483     backing_g.setClip(null);
484
485     vinterval_pos = dimension.height - axis_margin_bottom - pixels_per_vinterval;
486     int value = value_per_vinterval;
487     while (vinterval_pos > axis_margin_top) {
488       backing_g.setColor(Color.YELLOW.darker());
489       backing_g.drawLine(axis_margin_left - std_separator, vinterval_pos,
490           dimension.width - axis_margin_right, vinterval_pos);
491       final String JavaDoc value_str = "" + value;
492       final TextLayout JavaDoc current_layout = new TextLayout JavaDoc(value_str, backing_g.getFont(), backing_g.getFontRenderContext());
493       final Rectangle2D JavaDoc current_bounds = current_layout.getBounds();
494       backing_g.setColor(Color.YELLOW.darker());
495       backing_g.drawString(value_str,
496           axis_margin_left - (int) current_bounds.getWidth() - 2 * std_separator,
497           vinterval_pos + (int) (current_bounds.getHeight() / 2));
498       vinterval_pos -= pixels_per_vinterval;
499       value += value_per_vinterval;
500     }
501
502     return now;
503   }
504
505   /**
506    * Paints the chart.
507    * @param now current time.
508    * @return void.
509    */

510   // AWT thread
511
// AWT thread << sync_value_per_vinterval << events
512
public void paintChart(final long now) {
513     backing_g.setClip(axis_margin_left + 1, axis_margin_top,
514         dimension.width - axis_margin_left - axis_margin_right - 1,
515         dimension.height - axis_margin_top - axis_margin_bottom);
516
517     synchronized (events) {
518       final int npoints = events.size();
519       final int point_x[] = new int [npoints];
520       final int point_y[] = new int [npoints];
521
522       final long time_to_display = now - now % getDelayPerInterval();
523       final int pixels_offset = (pixels_per_interval * (int) (now % getDelayPerInterval())) / (int) getDelayPerInterval();
524       final int last_interval_pos = dimension.width - axis_margin_right - pixels_offset;
525
526       for (int i = 0; i < events.size(); i++) {
527         final EventGeneric event = events.get(i);
528         point_x[i] = last_interval_pos + (int) ((pixels_per_interval *
529           (int) (event.getDate().getTime() - time_to_display)) / getDelayPerInterval());
530
531         // cast to double to avoid overflow on int that lead to wrong results
532
point_y[i] = (int) (dimension.height - axis_margin_bottom -
533           pixels_per_vinterval * (double) event.getIntValue(events, i) / value_per_vinterval);
534         if (point_y[i] < 0) log.warn("y < 0: y=" + point_y[i]);
535       }
536
537       backing_g.setColor(Color.GREEN);
538       backing_g.drawPolyline(point_x, point_y, events.size());
539
540       backing_g.setColor(Color.RED);
541       for (int i = 0; i < events.size(); i++)
542         backing_g.drawRect(point_x[i] - 2, point_y[i] - 2, 4, 4);
543
544       backing_g.setClip(null);
545     }
546   }
547
548   /**
549    * Repaints the component using the backing store.
550    * @param g graphics context.
551    * @return void.
552    */

553   // AWT thread
554
public void paint(final Graphics JavaDoc g) {
555     if (backing_store == null) return;
556     backing_g.clearRect(0, 0, dimension.width, dimension.height);
557
558     updateFPS();
559
560     final long current_time = System.currentTimeMillis();
561     if (current_time - last_paint_100ms >= 100) {
562       last_paint_100ms = current_time;
563       last_fps_100ms = fps;
564     }
565     paintFPS(last_fps_100ms);
566
567     synchronized (sync_value_per_vinterval) {
568       final long now = paintAxis();
569       paintChart(now);
570     }
571
572     g.drawImage(backing_store, 0, 0, this);
573   }
574 }
575
Popular Tags