Package nl.lxtreme.ols.client.signaldisplay.model

Source Code of nl.lxtreme.ols.client.signaldisplay.model.SignalDiagramModel

/*
* OpenBench LogicSniffer / SUMP project
*
* This program 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.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
*
* Copyright (C) 2006-2010 Michael Poppitz, www.sump.org
* Copyright (C) 2010 J.W. Janssen, www.lxtreme.nl
*/
package nl.lxtreme.ols.client.signaldisplay.model;


import java.awt.*;
import java.beans.*;
import java.util.*;
import java.util.List;

import javax.swing.*;
import javax.swing.event.*;

import nl.lxtreme.ols.api.acquisition.*;
import nl.lxtreme.ols.api.data.*;
import nl.lxtreme.ols.api.data.Cursor;
import nl.lxtreme.ols.client.signaldisplay.*;
import nl.lxtreme.ols.client.signaldisplay.laf.*;
import nl.lxtreme.ols.client.signaldisplay.signalelement.*;
import nl.lxtreme.ols.client.signaldisplay.signalelement.SignalElementManager.SignalElementMeasurer;


/**
* The main model for the {@link SignalDiagramComponent}.
*/
public class SignalDiagramModel
{
  // INNER TYPES

  /**
   * Denotes where to draw the signal, at the top, center or bottom of the
   * channel.
   */
  public static enum SignalAlignment
  {
    TOP, CENTER, BOTTOM;
  }

  // CONSTANTS

  private static final int SNAP_CURSOR_MODE = ( 1 << 0 );
  private static final int MEASUREMENT_MODE = ( 1 << 1 );

  private static final double TIMESTAMP_FACTOR = 100.0;

  // VARIABLES

  private volatile int mode;
  private volatile int selectedChannelIndex;
  private volatile DataSet dataSet;

  private final ZoomController zoomController;
  private final SignalElementManager channelGroupManager;
  private final SignalDiagramController controller;
  private final EventListenerList eventListeners;
  private final PropertyChangeSupport propertyChangeSupport;

  // CONSTRUCTORS

  /**
   * Creates a new SignalDiagramModel instance.
   *
   * @param aController
   *          the controller to use, cannot be <code>null</code>.
   */
  public SignalDiagramModel( final SignalDiagramController aController )
  {
    this.controller = aController;

    this.zoomController = new ZoomController( aController );

    this.channelGroupManager = new SignalElementManager();

    this.eventListeners = new EventListenerList();
    this.propertyChangeSupport = new PropertyChangeSupport( this );

    this.mode = 0;

    addDataModelChangeListener( this.channelGroupManager );
  }

  // METHODS

  /**
   * Provides a binary search for arrays of long-values.
   * <p>
   * This implementation is directly copied from the JDK
   * {@link Arrays#binarySearch(long[], long)} implementation, slightly modified
   * to only perform a single comparison-action.
   * </p>
   *
   * @param aArray
   *          the array of long values to search in;
   * @param aFromIndex
   *          the from index to search from;
   * @param aToIndex
   *          the to index to search up and until;
   * @param aKey
   *          the value to search for.
   * @return the index of the given key, which is either the greatest index of
   *         the value less or equal to the given key.
   * @see Arrays#binarySearch(long[], long)
   */
  static final int binarySearch( final long[] aArray, final int aFromIndex, final int aToIndex, final long aKey )
  {
    int mid = -1;
    int low = aFromIndex;
    int high = aToIndex - 1;

    while ( low <= high )
    {
      mid = ( low + high ) >>> 1;
      final long midVal = aArray[mid];

      final int c = ( aKey < midVal ? -1 : ( aKey == midVal ? 0 : 1 ) );
      if ( c > 0 )
      {
        low = mid + 1;
      }
      else if ( c < 0 )
      {
        high = mid - 1;
      }
      else
      {
        return mid; // key found
      }
    }

    if ( mid < 0 )
    {
      return low;
    }

    // Determine the insertion point, avoid crossing the array boundaries...
    if ( mid < ( aToIndex - 1 ) )
    {
      // If the searched value is greater than the value of the found index,
      // insert it after this value, otherwise before it (= the last return)...
      if ( aKey > aArray[mid] )
      {
        return mid + 1;
      }
    }

    return mid;
  }

  /**
   * Moves an element from a "old" position to a "new" position, shifting all
   * other elements.
   * <p>
   * NOTE: the given array's contents will be mutated!
   * </p>
   *
   * @param aInput
   *          the input array to move the elements from, cannot be
   *          <code>null</code>;
   * @param aOldIdx
   *          the index of the element to move;
   * @param aNewIdx
   *          the index to move the element to.
   */
  static final void shiftElements( final int[] aInput, final int aOldIdx, final int aNewIdx )
  {
    final int length = aInput.length;

    final int moved = aInput[aOldIdx];
    // Delete element from array...
    System.arraycopy( aInput, aOldIdx + 1, aInput, aOldIdx, length - 1 - aOldIdx );
    // Make space for new element...
    System.arraycopy( aInput, aNewIdx, aInput, aNewIdx + 1, length - 1 - aNewIdx );
    // Set actual (inserted) element...
    aInput[aNewIdx] = moved;
  }

  /**
   * Adds a cursor change listener.
   *
   * @param aListener
   *          the listener to add, cannot be <code>null</code>.
   */
  public void addCursorChangeListener( final ICursorChangeListener aListener )
  {
    this.eventListeners.add( ICursorChangeListener.class, aListener );
  }

  /**
   * Adds a data model change listener.
   *
   * @param aListener
   *          the listener to add, cannot be <code>null</code>.
   */
  public void addDataModelChangeListener( final IDataModelChangeListener aListener )
  {
    this.eventListeners.add( IDataModelChangeListener.class, aListener );
  }

  /**
   * Adds a measurement listener.
   *
   * @param aListener
   *          the listener to add, cannot be <code>null</code>.
   */
  public void addMeasurementListener( final IMeasurementListener aListener )
  {
    this.eventListeners.add( IMeasurementListener.class, aListener );
  }

  /**
   * Adds a property change listener.
   *
   * @param aListener
   *          the listener to add, cannot be <code>null</code>.
   */
  public void addPropertyChangeListener( final PropertyChangeListener aListener )
  {
    this.propertyChangeSupport.addPropertyChangeListener( aListener );
  }

  /**
   * @param aChannelIdx
   * @param aTimestamp
   * @return
   */
  public final long findEdgeAfter( final int aChannelIdx, final long aTimestamp )
  {
    final long[] timestamps = getTimestamps();
    final int[] values = getValues();

    int refIdx = Arrays.binarySearch( timestamps, aTimestamp );
    if ( refIdx < 0 )
    {
      refIdx = -( refIdx + 1 ) - 1;
    }

    if ( ( refIdx < 0 ) || ( refIdx >= values.length ) )
    {
      return timestamps[0];
    }

    // find the reference time value; which is the "timestamp" under the
    // cursor...
    final int mask = ( 1 << aChannelIdx );
    final int refValue = ( values[refIdx] & mask );

    do
    {
      refIdx++;
    }
    while ( ( refIdx < ( values.length - 1 ) ) && ( ( values[refIdx] & mask ) == refValue ) );

    return timestamps[refIdx];
  }

  /**
   * @param aChannelIdx
   * @param aTimestamp
   * @return
   */
  public final long findEdgeBefore( final int aChannelIdx, final long aTimestamp )
  {
    final long[] timestamps = getTimestamps();
    final int[] values = getValues();

    int refIdx = Arrays.binarySearch( timestamps, aTimestamp );
    if ( refIdx < 0 )
    {
      refIdx = -( refIdx + 1 ) - 1;
    }

    if ( ( refIdx < 0 ) || ( refIdx >= values.length ) )
    {
      return timestamps[0];
    }

    // find the reference time value; which is the "timestamp" under the
    // cursor...
    final int mask = ( 1 << aChannelIdx );
    final int refValue = ( values[refIdx] & mask );

    do
    {
      refIdx--;
    }
    while ( ( refIdx > 0 ) && ( ( values[refIdx] & mask ) == refValue ) );

    return timestamps[Math.max( 0, refIdx )];
  }

  /**
   * Finds a UI-element based on a given screen coordinate.
   *
   * @param aPoint
   *          the coordinate to find the channel for, cannot be
   *          <code>null</code>.
   * @return the channel if found, or <code>null</code> if no channel was found.
   */
  public IUIElement findUIElement( final Point aPoint )
  {
    final IUIElement[] elements = getSignalElementManager().getUIElements( aPoint.y, 1,
        SignalElementMeasurer.LOOSE_MEASURER );
    if ( elements.length == 0 )
    {
      return null;
    }
    return elements[0];
  }

  /**
   * @param aPoint
   */
  public void fireMeasurementEvent( final MeasurementInfo aMeasurementInfo )
  {
    final IMeasurementListener[] listeners = this.eventListeners.getListeners( IMeasurementListener.class );
    for ( IMeasurementListener listener : listeners )
    {
      if ( listener.isListening() )
      {
        listener.handleMeasureEvent( aMeasurementInfo );
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public long getAbsoluteLength()
  {
    if ( !hasData() )
    {
      return 0L;
    }

    final AcquisitionResult capturedData = getCapturedData();
    return capturedData.getAbsoluteLength();
  }

  /**
   * @return
   */
  public AcquisitionResult getCapturedData()
  {
    if ( this.dataSet == null )
    {
      return null;
    }
    return this.dataSet.getCapturedData();
  }

  /**
   * {@inheritDoc}
   */
  public Cursor getCursor( final int aCursorIdx )
  {
    final Cursor[] cursors = this.dataSet.getCursors();
    if ( ( aCursorIdx < 0 ) || ( aCursorIdx > cursors.length ) )
    {
      throw new IllegalArgumentException( "Invalid cursor index!" );
    }
    return cursors[aCursorIdx];
  }

  /**
   * Returns all defined cursors.
   *
   * @return an array of defined cursors, never <code>null</code>.
   */
  public Cursor[] getDefinedCursors()
  {
    List<Cursor> result = new ArrayList<Cursor>();

    if ( hasData() )
    {
      for ( Cursor c : this.dataSet.getCursors() )
      {
        if ( c.isDefined() )
        {
          result.add( c );
        }
      }
    }

    return result.toArray( new Cursor[result.size()] );
  }

  /**
   * Returns the time interval displayed by the current view.
   *
   * @return a time interval, in seconds.
   */
  public Double getDisplayedTimeInterval()
  {
    final int width = this.controller.getSignalDiagram().getVisibleRect().width;
    if ( !hasData() )
    {
      return null;
    }
    double result;
    if ( hasTimingData() )
    {
      result = width / ( getZoomFactor() * getSampleRate() );
    }
    else
    {
      result = width / getZoomFactor();
    }
    return Double.valueOf( result );
  }

  /**
   * Calculates the horizontal block increment.
   * <p>
   * The following rules are adhered for scrolling horizontally:
   * </p>
   * <ol>
   * <li>unless the first or last sample is not shown, scroll a full block;
   * otherwise</li>
   * <li>do not scroll.</li>
   * </ol>
   *
   * @param aVisibleRect
   *          the visible rectangle of the component, never <code>null</code>;
   * @param aDirection
   *          the direction in which to scroll (&gt; 0 to scroll left, &lt; 0 to
   *          scroll right);
   * @return a horizontal block increment, determined according to the rules
   *         described.
   */
  public int getHorizontalBlockIncrement( final Rectangle aVisibleRect, final int aDirection )
  {
    final int blockIncr = 50;

    final int firstVisibleSample = locationToSampleIndex( aVisibleRect.getLocation() );
    final int lastVisibleSample = locationToSampleIndex( new Point( aVisibleRect.x + aVisibleRect.width, 0 ) );
    final int lastSampleIdx = getSampleCount();

    int inc = 0;
    if ( aDirection < 0 )
    {
      // Scroll left
      if ( firstVisibleSample >= 0 )
      {
        inc = blockIncr;
      }
    }
    else if ( aDirection > 0 )
    {
      // Scroll right
      if ( lastVisibleSample < lastSampleIdx )
      {
        inc = blockIncr;
      }
    }

    return inc;
  }

  /**
   * @return the minimum height of the signal diagram, in pixels, > 0.
   */
  public int getMinimumHeight()
  {
    return getSignalElementManager().calculateScreenHeight();
  }

  /**
   * {@inheritDoc}
   */
  public int getSampleRate()
  {
    final AcquisitionResult capturedData = getCapturedData();
    if ( capturedData == null )
    {
      return -1;
    }
    return getCapturedData().getSampleRate();
  }

  /**
   * {@inheritDoc}
   */
  public int getSampleWidth()
  {
    final AcquisitionResult capturedData = getCapturedData();
    if ( capturedData == null )
    {
      return 0;
    }
    return capturedData.getChannels();
  }

  /**
   * Returns the index of the current selected channel.
   *
   * @return the selected channel index, or -1 if no channel is selected.
   */
  public int getSelectedChannelIndex()
  {
    return this.selectedChannelIndex;
  }

  /**
   * Returns channel group manager.
   *
   * @return the channel group manager, never <code>null</code>.
   */
  public final SignalElementManager getSignalElementManager()
  {
    return this.channelGroupManager;
  }

  /**
   * Returns the hover area of the signal under the given coordinate (= mouse
   * position).
   *
   * @param aPoint
   *          the mouse coordinate to determine the signal rectangle for, cannot
   *          be <code>null</code>.
   * @return the rectangle of the signal the given coordinate contains,
   *         <code>null</code> if not found.
   */
  public final MeasurementInfo getSignalHover( final Point aPoint )
  {
    final IUIElement element = findUIElement( aPoint );
    if ( !( element instanceof SignalElement ) || !( ( SignalElement )element ).isDigitalSignal() )
    {
      // Trivial reject: no digital signal, or not above any channel...
      return null;
    }

    return getSignalHover( aPoint, ( SignalElement )element );
  }

  /**
   * @param aPoint
   * @param aSignalElement
   * @return
   */
  public MeasurementInfo getSignalHover( final Point aPoint, final SignalElement aSignalElement )
  {
    if ( !aSignalElement.isDigitalSignal() )
    {
      throw new IllegalArgumentException( "Signal element must represent a digital channel!" );
    }

    final int refIdx = locationToSampleIndex( aPoint );
    // Calculate the "absolute" time based on the mouse position, use a
    // "over sampling" factor to allow intermediary (between two time stamps)
    // time value to be shown...
    final double refTime;
    if ( hasTimingData() )
    {
      refTime = getTimestamp( aPoint );
    }
    else
    {
      refTime = refIdx;
    }

    final Channel channel = aSignalElement.getChannel();
    if ( !channel.isEnabled() )
    {
      // Trivial reject: real channel is invisible...
      return new MeasurementInfo( aSignalElement, refTime );
    }

    final long[] timestamps = getTimestamps();

    long ts = -1L;
    long tm = -1L;
    long te = -1L;
    long th = -1L;

    // find the reference time value; which is the "timestamp" under the
    // cursor...
    final int[] values = getValues();
    if ( ( refIdx >= 0 ) && ( refIdx < values.length ) )
    {
      final int mask = channel.getMask();
      final int refValue = ( values[refIdx] & mask );

      int idx = refIdx;
      do
      {
        idx--;
      }
      while ( ( idx >= 0 ) && ( ( values[idx] & mask ) == refValue ) );

      // convert the found index back to "screen" values...
      final int tm_idx = Math.max( 0, idx + 1 );
      tm = ( tm_idx == 0 ) ? 0 : timestamps[tm_idx];

      // Search for the original value again, to complete the pulse...
      do
      {
        idx--;
      }
      while ( ( idx >= 0 ) && ( ( values[idx] & mask ) != refValue ) );

      // convert the found index back to "screen" values...
      final int ts_idx = Math.max( 0, idx + 1 );
      ts = ( ts_idx == 0 ) ? 0 : timestamps[ts_idx];

      idx = refIdx;
      do
      {
        idx++;
      }
      while ( ( idx < values.length ) && ( ( values[idx] & mask ) == refValue ) );

      // convert the found index back to "screen" values...
      final int te_idx = Math.min( idx, timestamps.length - 1 );
      te = ( te_idx == 0 ) ? 0 : timestamps[te_idx];

      // Determine the width of the "high" part...
      if ( ( values[ts_idx] & mask ) != 0 )
      {
        th = Math.abs( tm - ts );
      }
      else
      {
        th = Math.abs( te - tm );
      }
    }

    MeasurementInfo result;
    if ( hasTimingData() )
    {
      result = new MeasurementInfo( aSignalElement, ts, tm, te, th, refTime, getZoomFactor(), getSampleRate() );
    }
    else
    {
      result = new MeasurementInfo( aSignalElement, ts, tm, te, refTime, getZoomFactor(), getSampleRate() );
    }

    return result;
  }

  /**
   * Returns the amount of pixels that represents one second on the timeline.
   *
   * @return the number of pixels to display for 1 second, > 0.
   */
  public Double getTimelinePixelsPerSecond()
  {
    if ( !hasData() || !hasTimingData() )
    {
      return null;
    }
    return Double.valueOf( getZoomFactor() * getSampleRate() );
  }

  /**
   * Returns the number of seconds that represents one pixel on the timeline.
   *
   * @return the number of seconds per pixel, > 0.
   * @see #getTimelinePixelsPerSecond()
   */
  public Double getTimelineSecondsPerPixel()
  {
    Double pixelsPerSecond = getTimelinePixelsPerSecond();
    if ( pixelsPerSecond == null )
    {
      return null;
    }
    return Double.valueOf( 1.0 / pixelsPerSecond.doubleValue() );
  }

  /**
   * Returns the unit of time the timeline is currently is displaying, in
   * multiples of 10 (for human readability).
   *
   * @return a timeline unit of time, > 0, can only be <code>null</code> if
   *         there is no data.
   */
  public Double getTimelineUnitOfTime()
  {
    final Double p = getTimelineSecondsPerPixel();
    if ( p == null )
    {
      return null;
    }
    return Double.valueOf( Math.pow( 10, Math.ceil( Math.log10( p.doubleValue() ) ) ) );
  }

  /**
   * Converts the X-coordinate of the given {@link Point} to a precise
   * timestamp, useful for display purposes.
   *
   * @param aAbsTimestamp
   *          the timestamp to convert to a relative timestamp, should be >= 0.
   * @return a precise timestamp, as double value.
   * @see DisplayUtils#displayTime(double)
   */
  public double getTimestamp( long aAbsTimestamp )
  {
    // Calculate the "absolute" time based on the mouse position, use a
    // "over sampling" factor to allow intermediary (between two time stamps)
    // time value to be shown...
    final double zoomFactor = getZoomFactor();
    final double scaleFactor = TIMESTAMP_FACTOR * zoomFactor;

    // Take (optional) trigger position into account...
    final Long triggerPos = getTriggerPosition();
    if ( triggerPos != null )
    {
      aAbsTimestamp -= triggerPos.longValue();
    }
    // If no sample rate is available, we use a factor of 1; which doesn't
    // make a difference in the result...
    final int sampleRate = Math.max( 1, getSampleRate() );

    return ( scaleFactor * aAbsTimestamp ) / ( scaleFactor * sampleRate );
  }

  /**
   * Converts the X-coordinate of the given {@link Point} to a precise
   * timestamp, useful for display purposes.
   *
   * @param aPoint
   *          the X,Y-coordinate to convert to a precise timestamp, cannot be
   *          <code>null</code>.
   * @return a precise timestamp, as double value.
   * @see DisplayUtils#displayTime(double)
   */
  public double getTimestamp( final Point aPoint )
  {
    // Calculate the "absolute" time based on the mouse position, use a
    // "over sampling" factor to allow intermediary (between two time stamps)
    // time value to be shown...
    final double zoomFactor = getZoomFactor();
    final double scaleFactor = TIMESTAMP_FACTOR * zoomFactor;

    // Convert mouse position to absolute timestamp...
    double x = aPoint.x / zoomFactor;
    // Take (optional) trigger position into account...
    final Long triggerPos = getTriggerPosition();
    if ( triggerPos != null )
    {
      x -= triggerPos.longValue();
    }
    // If no sample rate is available, we use a factor of 1; which doesn't
    // make a difference in the result...
    if ( !hasTimingData() )
    {
      return ( scaleFactor * x ) / scaleFactor;
    }

    return ( scaleFactor * x ) / ( scaleFactor * getSampleRate() );
  }

  /**
   * {@inheritDoc}
   */
  public int getTimestampIndex( final long aValue )
  {
    final AcquisitionResult capturedData = getCapturedData();
    if ( capturedData == null )
    {
      return 0;
    }
    return capturedData.getSampleIndex( aValue );
  }

  /**
   * {@inheritDoc}
   */
  public long[] getTimestamps()
  {
    final AcquisitionResult capturedData = getCapturedData();
    if ( capturedData == null )
    {
      return new long[0];
    }
    return capturedData.getTimestamps();
  }

  /**
   * Returns the trigger position, if available.
   *
   * @return a trigger position, as timestamp, or <code>null</code> if no
   *         trigger is used/present.
   */
  public Long getTriggerPosition()
  {
    AcquisitionResult capturedData = getCapturedData();
    if ( ( capturedData == null ) || !capturedData.hasTriggerData() )
    {
      return null;
    }
    return Long.valueOf( capturedData.getTriggerPosition() );
  }

  /**
   * {@inheritDoc}
   */
  public int[] getValues()
  {
    final AcquisitionResult capturedData = getCapturedData();
    if ( capturedData == null )
    {
      return new int[0];
    }
    return capturedData.getValues();
  }

  /**
   * Calculates the vertical block increment.
   * <p>
   * The following rules are adhered for scrolling vertically:
   * </p>
   * <ol>
   * <li>if the first shown channel is not completely visible, it will be made
   * fully visible; otherwise</li>
   * <li>scroll down to show the succeeding channel fully;</li>
   * <li>if the last channel is fully shown, and there is some room left at the
   * bottom, show the remaining space.</li>
   * </ol>
   *
   * @param aVisibleRect
   *          the visible rectangle of the component, never <code>null</code>;
   * @param aDirection
   *          the direction in which to scroll (&gt; 0 to scroll down, &lt; 0 to
   *          scroll up).
   * @return a vertical block increment, determined according to the rules
   *         described.
   */
  public int getVerticalBlockIncrement( final Dimension aViewDimensions, final Rectangle aVisibleRect,
      final int aDirection )
  {
    final SignalElementMeasurer measurer = SignalElementMeasurer.LOOSE_MEASURER;
    final SignalElementManager elemMgr = getSignalElementManager();

    IUIElement[] signalElements = elemMgr.getUIElements( aVisibleRect.y + 1, 1, measurer );
    if ( signalElements.length == 0 )
    {
      return 0;
    }

    final int spacing = UIManager.getInt( UIManagerKeys.SIGNAL_ELEMENT_SPACING );

    int inc = 0;
    int yPos = signalElements[0].getYposition();

    if ( aDirection > 0 )
    {
      // Scroll down...
      int height = signalElements[0].getHeight() + spacing;
      inc = height - ( aVisibleRect.y - yPos );
      if ( inc < 0 )
      {
        inc = -inc;
      }
    }
    else if ( aDirection < 0 )
    {
      // Scroll up...
      inc = ( aVisibleRect.y - yPos );
      if ( inc <= 0 )
      {
        // Determine the height of the element *before* the current one, as we
        // need to scroll up its height...
        signalElements = elemMgr.getUIElements( yPos - spacing, 1, measurer );
        if ( signalElements.length > 0 )
        {
          inc += signalElements[0].getHeight() + spacing;
        }
      }
    }

    return inc;
  }

  /**
   * Returns the zoom controller of this diagram.
   *
   * @return the zoom controller, never <code>null</code>.
   */
  public final ZoomController getZoomController()
  {
    return this.zoomController;
  }

  /**
   * Returns the current zoom factor.
   *
   * @return a zoom factor.
   */
  public final double getZoomFactor()
  {
    return getZoomController().getFactor();
  }

  /**
   * Returns whether or not there is captured data to display.
   *
   * @return <code>true</code> if there is any data to display,
   *         <code>false</code> otherwise.
   */
  public final boolean hasData()
  {
    return ( this.dataSet != null ) && ( getCapturedData() != null );
  }

  /**
   * Returns whether the data is a timed-capture or a state-capture.
   *
   * @return <code>true</code> if there is timing data available,
   *         <code>false</code> if not.
   */
  public boolean hasTimingData()
  {
    AcquisitionResult captureData = getCapturedData();
    return ( captureData != null ) && captureData.hasTimingData();
  }

  /**
   * @return <code>true</code> if the analog scope is by default visible,
   *         <code>false</code> if it is default hidden.
   */
  public boolean isAnalogScopeDefaultVisible()
  {
    return UIManager.getBoolean( UIManagerKeys.ANALOG_SCOPE_VISIBLE_DEFAULT );
  }

  /**
   * {@inheritDoc}
   */
  public boolean isCursorDefined( final int aCursorIdx )
  {
    Cursor[] cursors = this.dataSet.getCursors();
    if ( ( aCursorIdx < 0 ) || ( aCursorIdx > cursors.length ) )
    {
      throw new IllegalArgumentException( "Invalid cursor index!" );
    }
    return cursors[aCursorIdx].isDefined();
  }

  /**
   * Returns whether or not the cursor-mode is enabled.
   *
   * @return <code>true</code> if cursor-mode is enabled, thereby making all
   *         defined cursors visible, <code>false</code> otherwise.
   */
  public boolean isCursorMode()
  {
    return ( this.dataSet != null ) && this.dataSet.isCursorsEnabled();
  }

  /**
   * @return <code>true</code> if the group summary is by default visible,
   *         <code>false</code> if it is default hidden.
   */
  public boolean isGroupSummaryDefaultVisible()
  {
    return UIManager.getBoolean( UIManagerKeys.GROUP_SUMMARY_VISIBLE_DEFAULT );
  }

  /**
   * @return
   */
  public boolean isMeasurementMode()
  {
    return ( this.mode & MEASUREMENT_MODE ) != 0;
  }

  /**
   * @return <code>true</code> if the snap cursor mode is enabled,
   *         <code>false</code> otherwise.
   */
  public boolean isSnapCursorMode()
  {
    return ( this.mode & SNAP_CURSOR_MODE ) != 0;
  }

  /**
   * Converts the given coordinate to the corresponding sample index.
   *
   * @param aCoordinate
   *          the coordinate to convert to a sample index, cannot be
   *          <code>null</code>.
   * @return a sample index, >= 0, or -1 if no corresponding sample index could
   *         be found.
   */
  public int locationToSampleIndex( final Point aCoordinate )
  {
    final long timestamp = locationToTimestamp( aCoordinate );
    final int idx = getTimestampIndex( timestamp );
    if ( idx < 0 )
    {
      return -1;
    }
    final int sampleCount = getSampleCount() - 1;
    if ( idx > sampleCount )
    {
      return sampleCount;
    }

    return idx;
  }

  /**
   * Converts the given coordinate to the corresponding sample index.
   *
   * @param aCoordinate
   *          the coordinate to convert to a sample index, cannot be
   *          <code>null</code>.
   * @return a sample index, >= 0, or -1 if no corresponding sample index could
   *         be found.
   */
  public long locationToTimestamp( final Point aCoordinate )
  {
    final long timestamp = ( long )Math.ceil( aCoordinate.x / getZoomFactor() );
    if ( timestamp < 0 )
    {
      return -1;
    }
    return timestamp;
  }

  /**
   * @param aCursorIdx
   */
  public void removeCursor( final int aCursorIdx )
  {
    Cursor[] cursors = this.dataSet.getCursors();
    if ( ( aCursorIdx < 0 ) || ( aCursorIdx > cursors.length ) )
    {
      throw new IllegalArgumentException( "Invalid cursor index!" );
    }

    final Cursor cursor = cursors[aCursorIdx];
    if ( !cursor.isDefined() )
    {
      // Nothing to do; the cursor is not defined...
      return;
    }

    final Cursor oldCursor = cursor.clone();

    cursor.clear();

    ICursorChangeListener[] listeners = this.eventListeners.getListeners( ICursorChangeListener.class );
    for ( ICursorChangeListener listener : listeners )
    {
      listener.cursorRemoved( oldCursor );
    }
  }

  /**
   * Removes a cursor change listener.
   *
   * @param aListener
   *          the listener to remove, cannot be <code>null</code>.
   */
  public void removeCursorChangeListener( final ICursorChangeListener aListener )
  {
    this.eventListeners.remove( ICursorChangeListener.class, aListener );
  }

  /**
   * Removes a data model change listener.
   *
   * @param aListener
   *          the listener to remove, cannot be <code>null</code>.
   */
  public void removeDataModelChangeListener( final IDataModelChangeListener aListener )
  {
    this.eventListeners.remove( IDataModelChangeListener.class, aListener );
  }

  /**
   * Removes the given measurement listener from the list of listeners.
   *
   * @param aListener
   *          the listener to remove, cannot be <code>null</code>.
   */
  public void removeMeasurementListener( final IMeasurementListener aListener )
  {
    this.eventListeners.remove( IMeasurementListener.class, aListener );
  }

  /**
   * Removes a property change listener.
   *
   * @param aListener
   *          the listener to remove, cannot be <code>null</code>.
   */
  public void removePropertyChangeListener( final PropertyChangeListener aListener )
  {
    this.propertyChangeSupport.removePropertyChangeListener( aListener );
  }

  /**
   * {@inheritDoc}
   */
  public void setCursor( final int aCursorIdx, final long aTimestamp )
  {
    Cursor[] cursors = this.dataSet.getCursors();
    if ( ( aCursorIdx < 0 ) || ( aCursorIdx > cursors.length ) )
    {
      throw new IllegalArgumentException( "Invalid cursor index!" );
    }

    final Cursor cursor = cursors[aCursorIdx];
    final Cursor oldCursor = cursor.clone();

    // Update the time stamp of the cursor...
    cursor.setTimestamp( aTimestamp );

    fireCursorChangeEvent( ICursorChangeListener.PROPERTY_TIMESTAMP, oldCursor, cursor );
  }

  /**
   * Returns the color for a cursor with the given index.
   *
   * @param aCursorIdx
   *          the index of the cursor to retrieve the color for;
   * @param aColor
   *          the color to set, cannot be <code>null</code>.
   * @return a cursor color, never <code>null</code>.
   */
  public void setCursorColor( final int aCursorIdx, final Color aColor )
  {
    Cursor[] cursors = this.dataSet.getCursors();
    if ( ( aCursorIdx < 0 ) || ( aCursorIdx > cursors.length ) )
    {
      throw new IllegalArgumentException( "Invalid cursor index!" );
    }

    final Cursor cursor = cursors[aCursorIdx];
    final Cursor oldCursor = cursor.clone();

    // Update the color of the cursor...
    cursor.setColor( aColor );

    fireCursorChangeEvent( ICursorChangeListener.PROPERTY_COLOR, oldCursor, cursor );
  }

  /**
   * Returns the color for a cursor with the given index.
   *
   * @param aCursorIdx
   *          the index of the cursor to retrieve the color for;
   * @param aLabel
   *          the label to set, cannot be <code>null</code>.
   * @return a cursor color, never <code>null</code>.
   */
  public void setCursorLabel( final int aCursorIdx, final String aLabel )
  {
    Cursor[] cursors = this.dataSet.getCursors();
    if ( ( aCursorIdx < 0 ) || ( aCursorIdx > cursors.length ) )
    {
      throw new IllegalArgumentException( "Invalid cursor index!" );
    }

    final Cursor cursor = cursors[aCursorIdx];
    final Cursor oldCursor = cursor.clone();

    // Update the label of the cursor...
    cursor.setLabel( aLabel );

    fireCursorChangeEvent( ICursorChangeListener.PROPERTY_LABEL, oldCursor, cursor );
  }

  /**
   * Enables or disables the cursors.
   *
   * @param aSelected
   *          <code>true</code> to enable the cursors, <code>false</code> to
   *          disable the cursors.
   */
  public void setCursorMode( final boolean aCursorMode )
  {
    this.dataSet.setCursorsEnabled( aCursorMode );

    ICursorChangeListener[] listeners = this.eventListeners.getListeners( ICursorChangeListener.class );
    for ( ICursorChangeListener listener : listeners )
    {
      if ( aCursorMode )
      {
        listener.cursorsVisible();
      }
      else
      {
        listener.cursorsInvisible();
      }
    }
  }

  /**
   * Sets the data model for this controller.
   *
   * @param aDataSet
   *          the dataModel to set, cannot be <code>null</code>.
   */
  public void setDataModel( final DataSet aDataSet )
  {
    if ( aDataSet == null )
    {
      throw new IllegalArgumentException( "Parameter DataSet cannot be null!" );
    }

    this.dataSet = aDataSet;

    final IDataModelChangeListener[] listeners = this.eventListeners.getListeners( IDataModelChangeListener.class );
    for ( IDataModelChangeListener listener : listeners )
    {
      listener.dataModelChanged( aDataSet );
    }
  }

  /**
   * @param aEnabled
   */
  public void setMeasurementMode( final boolean aEnabled )
  {
    if ( aEnabled )
    {
      this.mode |= MEASUREMENT_MODE;
    }
    else
    {
      this.mode &= ~MEASUREMENT_MODE;
    }

    IMeasurementListener[] listeners = this.eventListeners.getListeners( IMeasurementListener.class );
    for ( IMeasurementListener listener : listeners )
    {
      if ( aEnabled )
      {
        listener.enableMeasurementMode();
      }
      else
      {
        listener.disableMeasurementMode();
      }
    }
  }

  /**
   * Sets the selected channel index to the given value.
   *
   * @param aChannelIndex
   *          the index to set, or -1 if no channel is to be selected.
   */
  public void setSelectedChannelIndex( final int aChannelIndex )
  {
    this.selectedChannelIndex = aChannelIndex;
  }

  /**
   * @param aSnapCursors
   *          the snapCursor to set
   */
  public void setSnapCursorMode( final boolean aSnapCursors )
  {
    if ( aSnapCursors )
    {
      this.mode |= SNAP_CURSOR_MODE;
    }
    else
    {
      this.mode &= ~SNAP_CURSOR_MODE;
    }
  }

  /**
   * @param aOldCursor
   * @param aCursor
   */
  private void fireCursorChangeEvent( final String aPropertyName, final Cursor aOldCursor, final Cursor aCursor )
  {
    ICursorChangeListener[] listeners = this.eventListeners.getListeners( ICursorChangeListener.class );
    for ( ICursorChangeListener listener : listeners )
    {
      if ( !aOldCursor.isDefined() )
      {
        listener.cursorAdded( aCursor );
      }
      else
      {
        listener.cursorChanged( aPropertyName, aOldCursor, aCursor );
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  private int getSampleCount()
  {
    return getValues().length;
  }
}
TOP

Related Classes of nl.lxtreme.ols.client.signaldisplay.model.SignalDiagramModel

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.