code: /**
   * Copyright (c) 2004-2005, Regents of the University of California
   * All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * Redistributions of source code must retain the above copyright notice,
   * this list of conditions and the following disclaimer.
   *
   * Redistributions in binary form must reproduce the above copyright
   * notice, this list of conditions and the following disclaimer in the
   * documentation and/or other materials provided with the distribution.
   *
   * Neither the name of the University of California, Los Angeles nor the
   * names of its contributors may be used to endorse or promote products
   * derived from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  package avrora.gui;
  
  import cck.text.StringUtil;
  import cck.util.Util;
  import java.awt.*;
  import java.util.LinkedList;
  
  /**
   * The <code>TimeScale</code> class handles the conversion of time scales in displaying timing windows
   * within the GUI simulation. It has an internal notion of the scale and the start time. It has methods
   * to render a scale bar and to convert a time scale value (in cycles) to an X coordinate in the drawing
   * rectangle.
   *
   * @author Ben L. Titzer
   */
  public class TimeScale {
      final int height;
      long startTime;
      final Color backgroundColor;
      final Color borderColor;
      final Color tickColor;
      final Color fontColor;
  
      static final long hz;
      static final double nsPerCycle;
      static final int SCROLL_SIZE = 35;
      static final int MIN_TICK_WIDTH = 40;
      static ZoomLevel[] zooms;
      static int startZoom;
      static final double ONE_BILLION = 1000000000;
      static final String[] units = { "ns", "us", "ms", "s" };
  
      int zoom;
  
      TimeScale() {
          height = 20;
          backgroundColor = Color.GRAY;
          borderColor = Color.WHITE;
          tickColor = Color.DARK_GRAY;
          fontColor = Color.RED;
          zoom = startZoom;
      }
  
      static {
          hz = 7372800;
          nsPerCycle = ONE_BILLION / hz;
  
          double scaleup = 1.25895;
          double s100 = (double)hz / 100;
          zooms = new ZoomLevel[0];
  
          LinkedList<ZoomLevel> lout = new LinkedList<ZoomLevel>();
          for ( double scale = s100; scale > 1; scale /= scaleup ) {
              lout.add(newZoomLevel(scale));
          }
          ZoomLevel[] zout = (ZoomLevel[])lout.toArray(zooms);
  
          LinkedList<ZoomLevel> lin = new LinkedList<ZoomLevel>();
          for ( double scale = 1; scale > 0.02; scale /= scaleup ) {
              lin.add(newZoomLevel(scale));
          }
          ZoomLevel[] zin = (ZoomLevel[])lin.toArray(zooms);
  
          zooms = new ZoomLevel[lout.size()+lin.size()];
          System.arraycopy(zout, 0, zooms, 0, zout.length);
          System.arraycopy(zin, 0, zooms, zout.length, zin.length);
  
          startZoom = zout.length;
      }
  
      static ZoomLevel newZoomLevel(double scale) {
          long nsecs = 1;
          double cycles = getCycles(1);
          double max = 2*hz;
          for ( int cntr = 0; cycles < max; cntr++ ) {
              double tickWidth = cycles / scale;
              if ( tickWidth > MIN_TICK_WIDTH ) {
                  int dec = (300 - cntr) % 3;
                  return new ZoomLevel(scale, dec, nsecs, units[(cntr+2)/3]);
              }
              cycles *= 10;
              nsecs *= 10;
          }
          throw Util.failure("Zoom level not supported: "+scale);
      }
  
      public int getMaxZoom() {
          return zooms.length - 1;
      }
  
      static class ZoomLevel {
          final double scale; // scales in cycles per pixel
          final int dec;      // decimal positions within the unit
          final long nsecs;    // 10^pos
          final String units; // string unit name (e.g. "ms")
  
          final double majorTickWidth;
          final double minorTickWidth;
  
          ZoomLevel(double s, int d, long ns, String un) {
              scale = s;
              dec = d;
              nsecs = ns;
              units = un;
  
              majorTickWidth = getCycles(nsecs) / scale;
              minorTickWidth = majorTickWidth / 10;
          }
      }
  
      public void drawScale(Dimension dim, Graphics g) {
          g.setColor(backgroundColor);
          int y = dim.height - height;
          int my = (int)(y + height * 0.6);
          int medy = (int)(y + height * 0.4);
          g.fillRect(0, y, dim.width, dim.height);
  
          g.setFont(g.getFont().deriveFont(9.0f));
  
          ZoomLevel zl = getZoomLevel();
          long startNsecs = (long)getNS(startTime);
          long ns = startNsecs - startNsecs % zl.nsecs;
          double startPos = (getCycles(ns) - startTime) / zl.scale;
          int count = (int)((ns / zl.nsecs) % 1000);
  
          // show the actual clock ticks
          double cycWidth = 1 / zl.scale;
          if ( cycWidth >= 2 ) {
              g.setColor(this.borderColor);
              for ( double pos = 0; pos < dim.width; pos += cycWidth ) {
                  int xpos = (int)pos;
                  g.drawLine(xpos, y, xpos, y+4);
              }
          }
  
          // show the timing scale
          g.setColor(tickColor);
          double max = dim.width + zl.majorTickWidth;
          for ( double pos = startPos; pos < max; pos += zl.majorTickWidth ) {
              // draw the sub-ticks for this label
              for ( int mt = 1; mt < 10; mt++ ) {
                  int mx = (int)(pos + zl.minorTickWidth*mt);
                  if ( mt == 5 )
                      g.drawLine(mx, medy, mx, dim.height);
                  else
                      g.drawLine(mx, my, mx, dim.height);
              }
              // draw the string label for this tick
              int xpos = (int)pos;
              // draw the line from top to bottom
              g.drawLine(xpos, 0, xpos, dim.height);
              // draw the font label
              drawTickLabel(zl, count, g, xpos, y);
              count++;
              if ( count == 1000 ) count = 0;
          }
  
          // draw the border
          g.setColor(borderColor);
          g.drawLine(0, y, dim.width, y);
  
      }
  
      private double getNS(long cycles) {
          return cycles * nsPerCycle;
      }
  
      private static double getCycles(long nsecs) {
          return nsecs / nsPerCycle;
      }
  
      private void drawTickLabel(ZoomLevel zl, int tick, Graphics g, int cntr, int y) {
          String str = StringUtil.toDecimal(tick, zl.dec);
          FontMetrics m = g.getFontMetrics();
          String stru = str+" "+getZoomLevel().units;
          int width = m.stringWidth(stru);
          g.setColor(fontColor);
          g.drawString(stru, cntr - width + 5, y - 2);
          g.setColor(tickColor);
      }
  
      public int getZoom() {
          return zoom;
      }
  
      private ZoomLevel getZoomLevel() {
          return zooms[zoom];
      }
  
      public int getX(long time) {
          if ( time < startTime ) return -1;
          return (int)((time - startTime) / getZoomLevel().scale);
      }
  
      public void setZoom(int nzoom) {
          if ( nzoom >= zooms.length ) nzoom = zooms.length-1;
          if ( nzoom < 0 ) nzoom = 0;
          zoom = nzoom;
      }
  
      public void zoomin() {
          if ( zoom < zooms.length-1 ) zoom++;
      }
  
      public void zoomout() {
          if ( zoom > 0 ) zoom--;
      }
  
      public int getExtent(int width, long maxtime) {
          return width / SCROLL_SIZE;
      }
  
      public int getScrollBarSize(long maxtime) {
          return size(maxtime);
      }
  
      public void setPosition(int np) {
          startTime = (long)(np * SCROLL_SIZE * getZoomLevel().scale);
      }
  
      public int getPosition() {
          return size(startTime);
      }
  
      private int size(long cycles) {
          return (int)(cycles / getZoomLevel().scale / SCROLL_SIZE);
      }
  
      public long getStartTime() {
          return startTime;
      }
  
      public double getScale() {
          return getZoomLevel().scale;
      }
  }
  