/* * File : SpeedGraphic.java * Created : 15 d�c. 2003} * By : Olivier * * Azureus - a Java Bittorrent client * * 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. * * 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 ( see the LICENSE file ). * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.gudy.azureus2.ui.swt.components.graphics; import java.util.HashSet; import java.util.Set; import org.eclipse.swt.SWT; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseTrackListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.*; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.gudy.azureus2.core3.config.COConfigurationManager; import org.gudy.azureus2.core3.config.ParameterListener; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.SimpleTimer; import org.gudy.azureus2.core3.util.TimerEvent; import org.gudy.azureus2.core3.util.TimerEventPerformer; import org.gudy.azureus2.core3.util.TimerEventPeriodic; import org.gudy.azureus2.ui.swt.Utils; /** * @author Olivier * */ public class MultiPlotGraphic extends ScaledGraphic implements ParameterListener { private static final int DEFAULT_ENTRIES = 2000; public static MultiPlotGraphic getInstance( ValueSource[] sources, ValueFormater formatter ) { return( new MultiPlotGraphic( new Scale(), sources, formatter )); } private ValueSource[] value_sources; private int internalLoop; private int graphicsUpdate; private Point oldSize; private Image bufferImage; private int nbValues = 0; private int maxEntries = DEFAULT_ENTRIES; private int[][] all_values; private int currentPosition = 0; private boolean update_outstanding = false; private TimerEventPeriodic update_event; private MultiPlotGraphic( Scale scale, ValueSource[] sources, ValueFormater formater) { super( scale,formater ); value_sources = sources; init( null ); COConfigurationManager.addAndFireParameterListeners( new String[]{ "Graphics Update", "Stats Graph Dividers" }, this ); } private void init( int[][] history ) { nbValues = 0; maxEntries = DEFAULT_ENTRIES; all_values = new int[value_sources.length][maxEntries]; currentPosition = 0; if ( history != null ){ if ( history.length != value_sources.length ){ Debug.out( "Incompatible history records, ignored" ); }else{ if ( history.length > 0 ){ int history_entries = history[0].length; int offset = Math.max( history_entries - maxEntries, 0 ); for ( int i=offset; i<history_entries;i++){ for ( int j=0;j<history.length;j++){ all_values[j][nbValues] = history[j][i]; } nbValues++; } } currentPosition = nbValues; } } update_outstanding = true; } public void initialize( Canvas canvas ) { initialize( canvas, true ); } public void initialize( Canvas canvas, boolean is_active ) { super.initialize(canvas); drawCanvas.addPaintListener(new PaintListener() { public void paintControl(PaintEvent e) { if (bufferImage != null && !bufferImage.isDisposed()) { Rectangle bounds = bufferImage.getBounds(); if (bounds.width >= e.width && bounds.height >= e.height) { e.gc.drawImage(bufferImage, e.x, e.y, e.width, e.height, e.x, e.y, e.width, e.height); } } } }); drawCanvas.addListener(SWT.Resize, new Listener() { public void handleEvent(Event event) { drawChart(true); } }); setActive( is_active ); } public void setActive( boolean active ) { if ( active ){ if ( update_event != null ){ return; } update_event = SimpleTimer.addPeriodicEvent( "MPG:updater", 1000, new TimerEventPerformer() { public void perform( TimerEvent event ) { if ( drawCanvas.isDisposed()){ if ( update_event != null ){ update_event.cancel(); update_event = null; } }else{ int[] new_values = new int[value_sources.length]; for ( int i=0;i<new_values.length;i++){ new_values[i] = value_sources[i].getValue(); } addIntsValue( new_values ); } } }); }else{ if ( update_event != null ){ update_event.cancel(); update_event = null; } } } public void reset( int[][] history ) { init( history ); Utils.execSWTThread( new Runnable() { public void run() { refresh( true ); } }); } private void addIntsValue( int[] new_values) { try{ this_mon.enter(); if ( all_values.length < new_values.length ){ int[][] new_all_values = new int[new_values.length][]; for (int i=0;i<all_values.length;i++){ new_all_values[i] = all_values[i]; } for (int i=all_values.length;i<new_all_values.length; i++ ){ new_all_values[i] = new int[maxEntries]; } all_values = new_all_values; } for (int i=0;i<new_values.length;i++){ all_values[i][currentPosition] = new_values[i]; } currentPosition++; if(nbValues < maxEntries){ nbValues++; } currentPosition %= maxEntries; }finally{ this_mon.exit(); } if ( update_outstanding ){ update_outstanding = false; Utils.execSWTThread( new Runnable() { public void run() { refresh( true ); } }); } } public void refresh( boolean force ) { if ( drawCanvas == null || drawCanvas.isDisposed()){ return; } Rectangle bounds = drawCanvas.getClientArea(); if ( bounds.height < 30 || bounds.width < 100 || bounds.width > 10000 || bounds.height > 10000){ return; } // inflate # of values only if necessary if ( bounds.width > maxEntries ){ try{ this_mon.enter(); while( maxEntries < bounds.width ){ maxEntries += 1000; } for( int i=0;i<all_values.length;i++ ){ int[] newValues = new int[maxEntries]; System.arraycopy(all_values[i], 0, newValues, 0, all_values[i].length); all_values[i] = newValues; } }finally{ this_mon.exit(); } } boolean sizeChanged = (oldSize == null || oldSize.x != bounds.width || oldSize.y != bounds.height); oldSize = new Point(bounds.width,bounds.height); internalLoop++; if( internalLoop > graphicsUpdate ){ internalLoop = 0; } if ( internalLoop == 0 || sizeChanged || force ){ drawChart( sizeChanged ); // to get the scale to redraw correctly we need to force a second drawing as it // is the result of the initial draw that sets the scale... if ( force ){ drawChart( true ); } } drawCanvas.redraw(); drawCanvas.update(); } protected void drawChart( boolean sizeChanged) { if ( drawCanvas == null || drawCanvas.isDisposed() || !drawCanvas.isVisible()){ return; } GC gcImage = null; try{ this_mon.enter(); drawScale( sizeChanged ); if ( bufferScale == null || bufferScale.isDisposed()){ return; } Rectangle bounds = drawCanvas.getClientArea(); if ( bounds.isEmpty()){ return; } //If bufferedImage is not null, dispose it if (bufferImage != null && !bufferImage.isDisposed()){ bufferImage.dispose(); } bufferImage = new Image(drawCanvas.getDisplay(), bounds); gcImage = new GC(bufferImage); gcImage.drawImage(bufferScale, 0, 0); gcImage.setAntialias( SWT.ON ); gcImage.setTextAntialias( SWT.ON ); Set<ValueSource> invisible_sources = new HashSet<ValueSource>(); for ( int i=0;i<value_sources.length;i++){ ValueSource source = value_sources[i]; if (( source.getStyle() & ValueSource.STYLE_INVISIBLE ) != 0 ){ invisible_sources.add( source ); } } int[] oldTargetValues = new int[all_values.length]; int[] maxs = new int[all_values.length]; for (int x = 0; x < bounds.width - 71; x++){ int position = currentPosition - x - 1; if ( position < 0 ){ position += maxEntries; if ( position < 0 ){ position = 0; } } for (int chartIdx = 0; chartIdx < all_values.length; chartIdx++){ ValueSource source = value_sources[chartIdx]; if ( invisible_sources.contains( source )){ continue; } int value = all_values[chartIdx][position]; if (value > maxs[chartIdx]){ maxs[chartIdx] = value; } } } Set<ValueSource> bold_sources = new HashSet<ValueSource>(); Set<ValueSource> dotted_sources = new HashSet<ValueSource>(); int max = 0; for ( int i=0;i<maxs.length;i++){ ValueSource source = value_sources[i]; if ( invisible_sources.contains( source )){ continue; } if (( source.getStyle() & ValueSource.STYLE_BOLD ) != 0 ){ bold_sources.add( source ); } if (( source.getStyle() & ValueSource.STYLE_DOTTED ) != 0 ){ dotted_sources.add( source ); } if ( !source.isTrimmable()){ max = Math.max( max, maxs[i] ); } } int max_primary = max; for ( int i=0;i<maxs.length;i++){ ValueSource source = value_sources[i]; if ( invisible_sources.contains( source )){ continue; } if ( source.isTrimmable()){ // trim secondary indicators so we don't loose the more important info int m = maxs[i]; if ( max < m ){ if ( m <= 2 * max_primary ){ max = m; }else{ max = 2 * max_primary; break; } } } } if ( max > 5*1024 ){ max = (( max + 1023 )/1024)*1024; } scale.setMax( max ); int[] prev_x = new int[value_sources.length]; int[] prev_y = new int[value_sources.length]; int bounds_width_adj = bounds.width - 71; int cycles = bold_sources.size()==0?2:3; for (int x = 0; x < bounds_width_adj; x++){ int position = currentPosition - x - 1; if (position < 0){ position += maxEntries; if ( position < 0 ){ position = 0; } } int xDraw = bounds_width_adj - x; for ( int order=0;order<cycles;order++){ for (int chartIdx = 0; chartIdx < all_values.length; chartIdx++){ ValueSource source = value_sources[chartIdx]; if ( invisible_sources.contains( source )){ continue; } boolean is_bold = bold_sources.contains( source ); if ( is_bold && order != 2 ){ continue; } boolean is_dotted = dotted_sources.contains( source ); if ( ( source.isTrimmable() == (order==0 ) && order < 2 ) || ( is_bold && order == 2 )){ Color line_color = source.getLineColor(); int targetValue = all_values[chartIdx][position]; int oldTargetValue = oldTargetValues[chartIdx]; if ( x > 0 ){ int trimmed; if ( is_dotted ){ trimmed = 2; }else{ trimmed = 0; if (targetValue > max){ targetValue = max; trimmed++; } if (oldTargetValue > max){ oldTargetValue = max; trimmed++; } } boolean force_draw = ( trimmed == 2 && position % 4 == 0 ) || xDraw == 1; int h1 = bounds.height - scale.getScaledValue(targetValue) - 2; if ( x == 1 ){ int h2 = bounds.height - scale.getScaledValue(oldTargetValue) - 2; prev_x[chartIdx] = xDraw+1; prev_y[chartIdx] = h2; } if ( trimmed < 2 || force_draw ){ if ( h1 != prev_y[chartIdx] || force_draw ){ gcImage.setAlpha( source.getAlpha()); gcImage.setLineWidth( trimmed==2?3:is_bold?4:2 ); gcImage.setForeground( line_color ); gcImage.drawLine(xDraw+1, prev_y[chartIdx], prev_x[chartIdx], prev_y[chartIdx]); gcImage.drawLine(xDraw, h1, xDraw + 1, prev_y[chartIdx]); prev_x[chartIdx] = xDraw; prev_y[chartIdx] = h1; } }else{ prev_x[chartIdx] = xDraw; prev_y[chartIdx] = h1; } } oldTargetValues[chartIdx] = all_values[chartIdx][position]; } } } } if ( nbValues > 0 ){ for ( int order=0;order<cycles;order++){ for ( int chartIdx = 0; chartIdx < all_values.length; chartIdx++){ ValueSource source = value_sources[chartIdx]; if ( invisible_sources.contains( source )){ continue; } boolean is_bold = bold_sources.contains( source ); if ( is_bold && order != 2 ){ continue; } if ( ( source.isTrimmable() == (order==0 ) && order < 2 ) || ( is_bold && order == 2 )){ int style = source.getStyle(); if (( style & ValueSource.STYLE_HIDE_LABEL ) == 0 ){ int average_val = computeAverage( chartIdx, currentPosition - 6 ); int average_mod = average_val; if ( average_mod > max ){ average_mod = max; } int height = bounds.height - scale.getScaledValue( average_mod) - 2; gcImage.setAlpha( 255 ); gcImage.setForeground( source.getLineColor()); gcImage.drawText(formater.format( average_val ), bounds.width - 65, height - 12, false); Color bg = gcImage.getBackground(); if (( style & ValueSource.STYLE_DOWN ) != 0 ){ int x = bounds.width - 72; int y = height - 12; gcImage.setBackground( source.getLineColor()); gcImage.fillPolygon(new int[] { x, y, x+7, y, x+3, y+7 }); gcImage.setBackground( bg ); }else if (( style & ValueSource.STYLE_UP ) != 0 ){ int x = bounds.width - 72; int y = height - 12; gcImage.setBackground( source.getLineColor()); gcImage.fillPolygon(new int[] { x, y+7, x+7, y+7, x+3, y }); gcImage.setBackground( bg ); } } } } } } }catch( Throwable e ){ Debug.out( e ); }finally{ if ( gcImage != null ){ gcImage.dispose(); } this_mon.exit(); } } private int computeAverage( int line_index, int position ) { long sum = 0; for(int i = -5 ; i < 6 ; i++) { int pos = position + i; pos %= maxEntries; if (pos < 0) pos += maxEntries; sum += all_values[line_index][pos]; } return(int)(sum / 11); } public void parameterChanged( String parameter ) { graphicsUpdate = COConfigurationManager.getIntParameter("Graphics Update"); boolean update_dividers = COConfigurationManager.getBooleanParameter("Stats Graph Dividers"); int update_divider_width = update_dividers ? 60 : 0; setUpdateDividerWidth( update_divider_width ); } public void dispose() { super.dispose(); if ( bufferImage != null && ! bufferImage.isDisposed()){ bufferImage.dispose(); } if ( update_event != null ){ update_event.cancel(); update_event = null; } COConfigurationManager.removeParameterListener("Graphics Update",this); COConfigurationManager.removeParameterListener("Stats Graph Dividers" ,this); } }