Package net.fenyo.gnetwatch.GUI

Source Code of net.fenyo.gnetwatch.GUI.BasicComponent

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

package net.fenyo.gnetwatch.GUI;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.*;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.text.DateFormat;
import java.util.Date;

import net.fenyo.gnetwatch.data.EventBytesReceived;
import net.fenyo.gnetwatch.data.EventGeneric;
import net.fenyo.gnetwatch.targets.Target;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* This class implements an AWT component capable of drawing time series with the Java2D API.
* @author Alexandre Fenyo
* @version $Id: BasicComponent.java,v 1.39 2008/08/06 16:51:42 fenyo Exp $
*/

public abstract class BasicComponent extends Component
  implements ComponentListener, WindowListener, KeyListener,
             MouseMotionListener, MouseListener {
  private static Log log = LogFactory.getLog(BasicComponent.class);

  private boolean manual_mode = false;
  private long manual_now = 0;
  private long manual_delay_per_interval = 0;
  private int lastPixelsOffsetValue = 0;

  final private Target target;

  private static Object sync_update = new Object();

  protected java.util.List<EventGeneric> events = null;

  private Image backing_store = null;
  protected Graphics2D backing_g = null;
  protected Dimension dimension = null;

  private final DateFormat date_format = DateFormat.getDateTimeInstance();

  private int fps = 0;
  private long last_paint = System.currentTimeMillis();
  private long last_paint_100ms = System.currentTimeMillis();
  private int last_fps_100ms = 0;

  // horizontal interval
  protected final int pixels_per_interval = 80; // 80 pix/interval ; multiple de 10

  // vertical intervals
  protected int value_per_vinterval = 200;
  private final Object sync_value_per_vinterval = new Object();
  protected final int pixels_per_vinterval = 80; // 80 pix/vinterval ; multiple de 10

  private final static int std_margin = 30;
  private final static int std_separator = 3;

  protected final static int axis_margin_bottom = std_margin;
  protected final static int axis_margin_top = std_margin;
  protected int axis_margin_left = std_margin;
  protected final static int axis_margin_right = std_margin;

  private int drag_x_start = 0;
  private long drag_now_start = 0;

  /**
   * Constructor.
   * @param target target this graphic component works on.
   */
  // GUI thread
  public BasicComponent(final Target target) {
    this.target = target;
  }

  public boolean isManualMode() {
    return manual_mode;
  }

  /**
   * Returns the horizontal scale.
   * @param none.
   * @return long horizontal scale.
   */
  public long getDelayPerInterval() {
    return 5000; // 5 secs/interval
  }

  protected long _getDelayPerInterval() {
    if (!manual_mode) return getDelayPerInterval();
    else return manual_delay_per_interval;
  }

  /**
   * Sets the list of events to display.
   * @param events events to display.
   * @return void.
   */
  protected void setEvents(final java.util.List<EventGeneric> events) {
    this.events = events;
  }

  /**
   * Returns the dimensions of this component.
   * @param none.
   * @return Dimension dimensions.
   */
  protected Dimension getDimension() {
    return dimension;
  }

  /**
   * Returns the associated target.
   * @param none.
   * @return Target target.
   */
  protected Target getTarget() {
    return target;
  }

  /**
   * Initialize the component and ask AWT to receive events.
   *
   */
  // GUI thread
  // lock survey: synchro << sync_tree << HERE
  public void init() {
    setPreferredSize(new Dimension(700, 400));
    addComponentListener(this);
    setFocusable(true);
    addKeyListener(this);
    addMouseMotionListener(this);
    addMouseListener(this);
  }

  /**
   * Called when the component is hidden.
   * @param e event.
   * @return void.
   */
  public void componentHidden(final ComponentEvent e) {
  }

  /**
   * Called when the component is moved.
   * @param e event.
   * @return void.
   */
  public void componentMoved(final ComponentEvent e) {
  }

  /**
   * When the component is resized, creates a new backing store,
   * reset margins and fetch events that can be displayed.
   * @param e event.
   * @return void.
   */
  // AWT thread
  // lock survey: THIS
  public void componentResized(final ComponentEvent e) {
    synchronized (sync_value_per_vinterval) {
      newBackingElts();
      setMargin();
    }
    updateValues();
  }

  /**
   * Called when the component appears first.
   * @param e event.
   * @return void.
   */
  // AWT thread
  public void componentShown(final ComponentEvent e) {
    componentResized(e);
  }

  /**
   * Called whenthe window is activated.
   * @param e event.
   * @return void.
   */
  public void windowActivated(final WindowEvent e) {
  }

  /**
   * Called whenthe window is closed.
   * @param e event.
   * @return void.
   */
  public void windowClosed(final WindowEvent e) {
  }

  /**
   * Called when the window is closing.
   * @param e event.
   * @return void.
   */
  // AWT thread
  public abstract void windowClosing(final WindowEvent e);

  /**
   * Called whenthe window is deactivated.
   * @param e event.
   * @return void.
   */
  public void windowDeactivated(final WindowEvent e) {
  }

  /**
   * Called whenthe window is deiconified.
   * @param e event.
   * @return void.
   */
  public void windowDeiconified(final WindowEvent e) {
  }

  /**
   * Called whenthe window is iconified.
   * @param e event.
   * @return void.
   */
  public void windowIconified(final WindowEvent e) {
  }

  /**
   * Called whenthe window is opened.
   * @param e event.
   * @return void.
   */
  public void windowOpened(final WindowEvent e) {
  }

  /**
   * Computes new margins.
   * @param none.
   * @return void.
   */
  // AWT thread
  // lock survey: sync_update << sync_value_per_vinterval << HERE
  //              synchro << sync_tree << sync_update << sync_value_per_vinterval << events << HERE
  //              sync_value_per_vinterval << HERE
  private void setMargin() {
    /*
    final String left_values_str = "" + (int) (value_per_vinterval *
        (1 + (dimension.height - axis_margin_top - axis_margin_bottom) / pixels_per_vinterval));
*/
    final String left_values_str = "1000M";
    final TextLayout layout = new TextLayout(left_values_str, backing_g.getFont(), backing_g.getFontRenderContext());
    final Rectangle2D bounds = layout.getBounds();
    axis_margin_left = (int) bounds.getWidth() + 5 + 10;
  }

  /**
   * Creates a backing store.
   * @param none.
   * @return void.
   */
  private void newBackingElts() {
    dimension = getSize();
    backing_store = createImage(dimension.width, dimension.height);
    backing_g = (Graphics2D) backing_store.getGraphics();
    backing_g.setBackground(Color.BLACK);
    backing_g.setColor(Color.WHITE);
    backing_g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
        RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    backing_g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
  }

  /**
   * Removes events that can not be displayed.
   * @param none.
   * @return void.
   *
   */
  // lock survey: synchro << sync_tree << sync_update << sync_value_per_vinterval << events << HERE 
  private void removeOldEvents() {
    final Date begin = new Date(System.currentTimeMillis() - _getDelayPerInterval() *
        (dimension.width - axis_margin_left - axis_margin_right) / pixels_per_interval);
        while (events.size() >= 2 && (events.get(1).getDate().before(begin) || events.get(1).getDate().equals(begin)))
          events.remove(0);
  }

  protected int getIntValue(final EventGeneric event) {
    return event.getIntValue();
  }

  /**
   * Updates the vertical scale.
   * @param none.
   * @return void.
   */
  // Queue & AWT thread
  // lock survey: sync_update << sync_value_per_vinterval << HERE
  // lock survey: synchro << sync_tree << sync_update << sync_value_per_vinterval << events << HERE 
  protected void updateVerticalScale() {
    if (manual_mode) return;

    final int previous_value_per_vinterval = value_per_vinterval;

    int max_value = 0;
    for (final EventGeneric event : events) {
      final int value = getIntValue(event);
      if (value > max_value) max_value = value;
    }

    int nintervals = (dimension.height - axis_margin_top - axis_margin_bottom) / pixels_per_vinterval - 1;
    if (nintervals < 1) nintervals = 1;
    value_per_vinterval = max_value / nintervals;

    // possible values for value_per_vinterval: 1, 2, 5, 10, 20, 25, 50, 100, 200, etc.
    final int base = (int) Math.pow(10, new Integer(value_per_vinterval).toString().length() - 1);
    if (value_per_vinterval != base)
      if (value_per_vinterval <= 2 * base) value_per_vinterval = 2 * base;
      else if (base >= 10 && value_per_vinterval <= 25 * base / 10) value_per_vinterval = 25 * base / 10;
      else if (value_per_vinterval <= 5 * base) value_per_vinterval = 5 * base;
      else value_per_vinterval = 10 * base;

    if (value_per_vinterval == 0) value_per_vinterval = 1;

    if (previous_value_per_vinterval != value_per_vinterval) setMargin();
  }

  /**
   * Takes a new event into account.
   * @param event new event.
   * @return void.
   */
  // Queue thread
  // lock survey: synchro << sync_tree << HERE 
  public void updateValues(final EventGeneric event) {
    synchronized (sync_update) { // hypoth�se pour autres pbs synchronized : sync_value_per_vinterval cr�e un pb de verrou
    synchronized (sync_value_per_vinterval) {
        synchronized (events) {
          // lock survey: synchro << sync_tree << sync_update << sync_value_per_vinterval << events << HERE --XX 
          if (manual_mode && events.size() > 0) {
            if (event.getDate().after(new Date(manual_now)) && events.get(events.size() - 1).getDate().after(new Date(manual_now))) return;

            final long begin = manual_now - _getDelayPerInterval() *
            (getDimension().width - axis_margin_left - axis_margin_right) / pixels_per_interval;
            if (event.getDate().before(new Date(begin))) return;
          }

          for (int i = events.size() - 1; i >= 0; i--)
            if (events.get(i).getDate().before(event.getDate())) {
              if (i + 1 < events.size()) events.add(i + 1, event);
              else events.add(event);
              if (!manual_mode) removeOldEvents();
              updateVerticalScale();
              return;
            }

          events.add(0, event);
          if (!manual_mode) removeOldEvents();
          updateVerticalScale();
        }
      }
    }
  }

  protected abstract Class getEventClass();

  /**
   * Fetches events that can be displayed.
   * @param none.
   * @return void.
   */
  // AWT thread
  // lock survey: HERE
  protected void updateValues() {
    synchronized (getTarget().getGUI().getSynchro()) {
      synchronized (getTarget().getGUI().sync_tree) {
        synchronized (sync_update) {
          synchronized (sync_value_per_vinterval) {
            getTarget().registerComponent(this, getEventClass());
            final long end;
            if (!manual_mode) end = System.currentTimeMillis();
            else end = manual_now;
            final long begin = end - _getDelayPerInterval() *
            (getDimension().width - axis_margin_left - axis_margin_right) / pixels_per_interval;
            setEvents(getTarget().getEvents(new Date(begin), new Date(end), getEventClass()));
          }
          updateVerticalScale();
          repaint();
        }
      }
    }
  }

  /**
   * Computes the "frames per second" indicator.
   * @param none.
   * @return void.
   */
  // AWT thread
  private void updateFPS() {
    final long current_time = System.currentTimeMillis();
    fps = 100 / (int) (current_time - last_paint + 1) + (9 * fps) / 10;
    last_paint = current_time;
  }

  /**
   * Displays the number of frames per second.
   * @param fps frames per second to display.
   * @return void.
   */
  // AWT thread
  private void paintFPS(final int fps) {
    backing_g.setColor(Color.GRAY);
    if (!manual_mode) {
      // � traduire
      backing_g.drawString(fps + " frames/s - press any key or mouse button to enter manual scaling mode", 1, 13);
      backing_g.setColor(Color.YELLOW);
      backing_g.drawString("AUTO", 60, 50);
    } else {
      // � traduire
      backing_g.drawString("keys: '<' horiz. scale down  '>' horiz. scale up  '-' vert. scale down  '+' vert. scale up  'n' current date  'a' automatic scaling mode", 1, 13);
      backing_g.setColor(Color.YELLOW);
      backing_g.drawString("MANUAL", 60, 50);
    }
  }

  /**
   * Formats a time string to be displayed.
   * @param time time.
   * @return String time to be displayed.
   */
  // AWT thread
  private String formatTime(final long time) {
    String str = date_format.format(new Date(time));
    return str.substring(str.lastIndexOf(' ') + 1);
  }

  private String formatDate(final long time) {
    String str = date_format.format(new Date(time));
    return str.substring(0, str.lastIndexOf(' '));
  }

  public boolean pixelsOffsetChanged() {
    final int new_val = (pixels_per_interval * (int) (System.currentTimeMillis() % _getDelayPerInterval())) / (int) _getDelayPerInterval();
    if (lastPixelsOffsetValue == new_val) return false;
    lastPixelsOffsetValue = new_val;
    return true;
  }

  /**
   * Paints axis.
   * @param none.
   * @return long current time displayed at the axis bottom.
   */
  // AWT thread
  private long paintAxis() {
    backing_g.setColor(new Color(50, 50, 50));
    backing_g.fillRect(axis_margin_left, axis_margin_top,
        dimension.width - axis_margin_left - axis_margin_right + 1,
        dimension.height - axis_margin_top - axis_margin_bottom + 1);

    backing_g.setColor(Color.YELLOW);
    backing_g.drawLine(axis_margin_left, dimension.height - axis_margin_bottom,
        dimension.width - axis_margin_right, dimension.height - axis_margin_bottom);
    backing_g.drawLine(axis_margin_left, axis_margin_top,
        axis_margin_left, dimension.height - axis_margin_bottom);

    backing_g.setColor(Color.YELLOW.darker());
    backing_g.drawLine(axis_margin_left + 1, axis_margin_top,
        dimension.width - axis_margin_right,
        axis_margin_top);
    backing_g.drawLine(dimension.width - axis_margin_right,
        axis_margin_top,
        dimension.width - axis_margin_right,
        dimension.height - axis_margin_bottom - 1);

    int vinterval_pos = dimension.height - axis_margin_bottom - pixels_per_vinterval;
    backing_g.setColor(Color.YELLOW.darker().darker().darker());
    while (vinterval_pos + 9 * (pixels_per_vinterval / 10) > axis_margin_top) {
      int cpt = 10;
      while (--cpt > 0)
        if (vinterval_pos + cpt * (pixels_per_vinterval / 10) > axis_margin_top)
          backing_g.drawLine(axis_margin_left + 1, vinterval_pos + cpt * (pixels_per_vinterval / 10),
              dimension.width - axis_margin_right - 1, vinterval_pos + cpt * (pixels_per_vinterval / 10));
      vinterval_pos -= pixels_per_vinterval;
    }

    final long now;
    if (manual_mode) now = manual_now;
    else now = System.currentTimeMillis();

    final long time_to_display = now - now % _getDelayPerInterval();
    final int pixels_offset = (pixels_per_interval * (int) (now % _getDelayPerInterval())) / (int) _getDelayPerInterval();
    final int last_interval_pos = dimension.width - axis_margin_right - pixels_offset;

    backing_g.setClip(axis_margin_left, 0,
        dimension.width - axis_margin_left - axis_margin_right,
        dimension.height);
    int current_interval_pos = last_interval_pos + pixels_per_interval;
    long current_time_to_display = time_to_display + _getDelayPerInterval();
    boolean stop = false;
    while (stop == false) {
      backing_g.setColor(Color.YELLOW.darker());
      backing_g.drawLine(current_interval_pos, axis_margin_top,
          current_interval_pos, dimension.height - axis_margin_bottom + std_separator);

      int cpt = 10;
      backing_g.setColor(Color.YELLOW.darker().darker().darker());
      while (--cpt > 0)
        if (current_interval_pos - cpt * (pixels_per_interval / 10) > axis_margin_left)
          backing_g.drawLine(current_interval_pos - cpt * (pixels_per_interval / 10),
              axis_margin_top + 1,
              current_interval_pos - cpt * (pixels_per_interval / 10),
              dimension.height - axis_margin_bottom - 1);

      final String current_time_str = formatTime(current_time_to_display);
      final String current_date_str = formatDate(current_time_to_display);
      final TextLayout current_layout = new TextLayout(current_time_str, backing_g.getFont(), backing_g.getFontRenderContext());
      final TextLayout current_layout_date = new TextLayout(current_date_str, backing_g.getFont(), backing_g.getFontRenderContext());
      final Rectangle2D current_bounds = current_layout.getBounds();
      final Rectangle2D current_bounds_date = current_layout_date.getBounds();
      backing_g.setColor(Color.YELLOW.darker());
      backing_g.drawString(current_time_str,
          current_interval_pos - (int) (current_bounds.getWidth() / 2),
          dimension.height - axis_margin_bottom + (int) current_bounds.getHeight() + 2 * std_separator);
      backing_g.setColor(Color.YELLOW.darker().darker());
      backing_g.drawString(current_date_str,
          current_interval_pos - (int) (current_bounds_date.getWidth() / 2),
          3 + ((int) current_bounds.getHeight()) + dimension.height - axis_margin_bottom + (int) current_bounds.getHeight() + 2 * std_separator);
      if (current_interval_pos - current_bounds.getWidth() / 2 < axis_margin_left)
        stop = true;
      current_interval_pos -= pixels_per_interval;
      current_time_to_display -= _getDelayPerInterval();
    }
    backing_g.setClip(null);

    vinterval_pos = dimension.height - axis_margin_bottom - pixels_per_vinterval;
    int value = value_per_vinterval;
    while (vinterval_pos > axis_margin_top) {
      backing_g.setColor(Color.YELLOW.darker());
      backing_g.drawLine(axis_margin_left - std_separator, vinterval_pos,
          dimension.width - axis_margin_right, vinterval_pos);
      final String value_str;
      if (value >= 1000000) value_str = "" + value / 1000000 + "M";
      else if (value >= 1000) value_str = "" + value / 1000 + "k";
      else value_str = "" + value;
      final TextLayout current_layout = new TextLayout(value_str, backing_g.getFont(), backing_g.getFontRenderContext());
      final Rectangle2D current_bounds = current_layout.getBounds();
      backing_g.setColor(Color.YELLOW.darker());
      backing_g.drawString(value_str,
          axis_margin_left - (int) current_bounds.getWidth() - 2 * std_separator,
          vinterval_pos + (int) (current_bounds.getHeight() / 2));
      vinterval_pos -= pixels_per_vinterval;
      value += value_per_vinterval;
    }

    return now;
  }

  /**
   * Paints the chart.
   * @param now current time.
   * @return void.
   */
  // AWT thread
  // AWT thread << sync_value_per_vinterval << events
  public void paintChart(final long now) {
    backing_g.setClip(axis_margin_left + 1, axis_margin_top,
        dimension.width - axis_margin_left - axis_margin_right - 1,
        dimension.height - axis_margin_top - axis_margin_bottom);

    synchronized (events) {
      final int npoints = events.size();
      final int point_x[] = new int [npoints];
      final int point_y[] = new int [npoints];

      final long time_to_display = now - now % _getDelayPerInterval();
      final int pixels_offset = (pixels_per_interval * (int) (now % _getDelayPerInterval())) / (int) _getDelayPerInterval();
      final int last_interval_pos = dimension.width - axis_margin_right - pixels_offset;

      for (int i = 0; i < events.size(); i++) {
        final EventGeneric event = events.get(i);
        final long xpos = ((long) last_interval_pos) + (((long) pixels_per_interval) *
            (event.getDate().getTime() - time_to_display)) / _getDelayPerInterval();
        if (xpos < -1000)
          point_x[i] = -1000;
        else if (xpos > 1000 + dimension.width)
          point_x[i] = 1000 + dimension.width;
        else point_x[i] = (int) xpos;

        // cast to double to avoid overflow on int that lead to wrong results
        point_y[i] = (int) (dimension.height - axis_margin_bottom -
          pixels_per_vinterval * (double) getIntValue(event) / value_per_vinterval);
//        if (point_y[i] < 0) log.warn("y < 0: y=" + point_y[i]);
      }

      backing_g.setColor(Color.GREEN);
      backing_g.drawPolyline(point_x, point_y, events.size());

      backing_g.setColor(Color.RED);
      for (int i = 0; i < events.size(); i++)
        backing_g.drawRect(point_x[i] - 2, point_y[i] - 2, 4, 4);

      backing_g.setClip(null);
    }
  }

  /**
   * Repaints the component using the backing store.
   * @param g graphics context.
   * @return void.
   */
  // AWT thread
  public void paint(final Graphics g) {
    if (backing_store == null) return;
    backing_g.clearRect(0, 0, dimension.width, dimension.height);

    updateFPS();

    final long current_time = System.currentTimeMillis();
    if (current_time - last_paint_100ms >= 100) {
      last_paint_100ms = current_time;
      last_fps_100ms = fps;
    }

    synchronized (sync_value_per_vinterval) {
      final long now = paintAxis();
      paintChart(now);
    }

    paintFPS(last_fps_100ms);

    g.drawImage(backing_store, 0, 0, this);
  }

  public void keyPressed(final KeyEvent e) {}

  public void keyReleased(final KeyEvent e) {}

  public void keyTyped(final KeyEvent e) {
    if (!manual_mode) {
      // automatic mode
      manual_now = System.currentTimeMillis();
      manual_delay_per_interval = getDelayPerInterval();
      manual_mode = true;

    } else {
      // manual mode

      if (e.getKeyChar() == 'a') {
        manual_mode = false;
        updateValues();
      }

      if (e.getKeyChar() == 'n') {
        manual_now = System.currentTimeMillis();
        updateValues();
      }
     
      if (e.getKeyChar() == '<') {
        manual_delay_per_interval = manual_delay_per_interval * 2;
        updateValues();
      }
     
      if (e.getKeyChar() == '>') {
        manual_delay_per_interval = manual_delay_per_interval / 2;
        updateValues();
      }

      if (e.getKeyChar() == '+') {
        synchronized (sync_value_per_vinterval) {
          value_per_vinterval = value_per_vinterval / 2;
        }
        repaint();
      }

      if (e.getKeyChar() == '-') {
        synchronized (sync_value_per_vinterval) {
          value_per_vinterval = value_per_vinterval * 2;
        }
        repaint();
      }
    }
  }

  public void mouseClicked(MouseEvent e) {}

  public void mouseEntered(MouseEvent e) {}

  public void mouseExited(MouseEvent e) {}

  public void mousePressed(MouseEvent e) {
    if (!manual_mode) {
      manual_now = System.currentTimeMillis();
      manual_delay_per_interval = getDelayPerInterval();
      manual_mode = true;
    }

    drag_x_start = e.getX();
    drag_now_start = manual_now;
  }

  public void mouseReleased(MouseEvent e) {
    updateValues();
  }

  public void mouseDragged(final MouseEvent e) {
    if (manual_mode) {
      manual_now = drag_now_start +
      manual_delay_per_interval * (drag_x_start - e.getX()) / pixels_per_interval;
      }
    repaint();
  }

  public void mouseMoved(final MouseEvent e) {}
}
TOP

Related Classes of net.fenyo.gnetwatch.GUI.BasicComponent

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.