/*
* Created on 22 juin 2005
* Created by Olivier Chalouhi
*
* Copyright (C) 2004, 2005, 2006 Aelitis SAS, All rights Reserved
*
* 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
*
* AELITIS, SAS au capital de 46,603.30 euros,
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*/
package org.gudy.azureus2.ui.swt.views.stats;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.*;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.util.SimpleTimer;
import org.gudy.azureus2.core3.util.SystemTime;
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;
import com.aelitis.azureus.core.AzureusCoreFactory;
import com.aelitis.azureus.core.dht.DHT;
import com.aelitis.azureus.core.dht.control.DHTControlActivity;
import com.aelitis.azureus.core.dht.control.DHTControlListener;
import com.aelitis.azureus.core.dht.control.DHTControlActivity.ActivityNode;
import com.aelitis.azureus.core.dht.control.DHTControlActivity.ActivityState;
import com.aelitis.azureus.ui.swt.utils.ColorCache;
public class
DHTOpsPanel
implements DHTControlListener
{
private static final int ALPHA_FOCUS = 255;
private static final int ALPHA_NOFOCUS = 150;
private static final int FADE_OUT = 10*1000;
Display display;
Composite parent;
Canvas canvas;
Scale scale;
private boolean unavailable;
private boolean mouseLeftDown = false;
private boolean mouseRightDown = false;
private int xDown;
private int yDown;
private Image img;
private int alpha = 255;
private boolean autoAlpha = false;
private DHT current_dht;
private Map<DHTControlActivity,ActivityDetail> activity_map = new HashMap<DHTControlActivity,ActivityDetail>();
private TimerEventPeriodic timeout_timer;
private class Scale {
int width;
int height;
float minX = -1000;
float maxX = 1000;
float minY = -1000;
float maxY = 1000;
double rotation = 0;
float saveMinX;
float saveMaxX;
float saveMinY;
float saveMaxY;
double saveRotation;
public int getX(float x,float y) {
return (int) (((x * Math.cos(rotation) + y * Math.sin(rotation))-minX)/(maxX - minX) * width);
}
public int getY(float x,float y) {
return (int) (((y * Math.cos(rotation) - x * Math.sin(rotation))-minY)/(maxY-minY) * height);
}
}
public DHTOpsPanel(Composite parent) {
this.parent = parent;
this.display = parent.getDisplay();
this.canvas = new Canvas(parent,SWT.NO_BACKGROUND);
this.scale = new Scale();
canvas.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
if (img != null && !img.isDisposed()) {
Rectangle bounds = img.getBounds();
if (bounds.width >= e.width && bounds.height >= e.height) {
if (alpha != 255) {
try {
e.gc.setAlpha(alpha);
} catch (Exception ex) {
// Ignore ERROR_NO_GRAPHICS_LIBRARY error or any others
}
}
e.gc.drawImage(img, e.x, e.y, e.width, e.height, e.x, e.y,
e.width, e.height);
}
} else {
e.gc.setBackground(display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
e.gc.fillRectangle(e.x, e.y, e.width, e.height);
e.gc.drawText(
MessageText.getString( unavailable?(DHTOpsView.MSGID_PREFIX + ".notAvailable"):"v3.MainWindow.view.wait"), 10,
10, true);
}
}
});
canvas.addMouseListener(new MouseAdapter() {
public void mouseDown(MouseEvent event) {
if(event.button == 1) mouseLeftDown = true;
if(event.button == 3) mouseRightDown = true;
xDown = event.x;
yDown = event.y;
scale.saveMinX = scale.minX;
scale.saveMaxX = scale.maxX;
scale.saveMinY = scale.minY;
scale.saveMaxY = scale.maxY;
scale.saveRotation = scale.rotation;
}
public void mouseUp(MouseEvent event) {
if(event.button == 1) mouseLeftDown = false;
if(event.button == 3) mouseRightDown = false;
refresh();
}
});
canvas.addListener(SWT.KeyDown, new Listener() {
public void handleEvent(Event event) {
}
});
canvas.addListener(SWT.MouseWheel, new Listener() {
public void handleEvent(Event event) {
// System.out.println(event.count);
scale.saveMinX = scale.minX;
scale.saveMaxX = scale.maxX;
scale.saveMinY = scale.minY;
scale.saveMaxY = scale.maxY;
int deltaY = event.count * 5;
// scaleFactor>1 means zoom in, this happens when
// deltaY<0 which happens when the mouse is moved up.
float scaleFactor = 1 - (float) deltaY / 300;
if(scaleFactor <= 0) scaleFactor = 0.01f;
// Scalefactor of e.g. 3 makes elements 3 times larger
float moveFactor = 1 - 1/scaleFactor;
float centerX = (scale.saveMinX + scale.saveMaxX)/2;
scale.minX = scale.saveMinX + moveFactor * (centerX - scale.saveMinX);
scale.maxX = scale.saveMaxX - moveFactor * (scale.saveMaxX - centerX);
float centerY = (scale.saveMinY + scale.saveMaxY)/2;
scale.minY = scale.saveMinY + moveFactor * (centerY - scale.saveMinY);
scale.maxY = scale.saveMaxY - moveFactor * (scale.saveMaxY - centerY);
refresh();
}
});
canvas.addMouseMoveListener(new MouseMoveListener() {
private long last_refresh;
public void mouseMove(MouseEvent event) {
boolean do_refresh = false;
if(mouseLeftDown && (event.stateMask & SWT.MOD4) == 0) {
int deltaX = event.x - xDown;
int deltaY = event.y - yDown;
float width = scale.width;
float height = scale.height;
float ratioX = (scale.saveMaxX - scale.saveMinX) / width;
float ratioY = (scale.saveMaxY - scale.saveMinY) / height;
float realDeltaX = deltaX * ratioX;
float realDeltaY = deltaY * ratioY;
scale.minX = scale.saveMinX - realDeltaX;
scale.maxX = scale.saveMaxX - realDeltaX;
scale.minY = scale.saveMinY - realDeltaY;
scale.maxY = scale.saveMaxY - realDeltaY;
do_refresh = true;
}
if(mouseRightDown || (mouseLeftDown && (event.stateMask & SWT.MOD4) > 0)) {
int deltaX = event.x - xDown;
scale.rotation = scale.saveRotation - (float) deltaX / 100;
int deltaY = event.y - yDown;
// scaleFactor>1 means zoom in, this happens when
// deltaY<0 which happens when the mouse is moved up.
float scaleFactor = 1 - (float) deltaY / 300;
if(scaleFactor <= 0) scaleFactor = 0.01f;
// Scalefactor of e.g. 3 makes elements 3 times larger
float moveFactor = 1 - 1/scaleFactor;
float centerX = (scale.saveMinX + scale.saveMaxX)/2;
scale.minX = scale.saveMinX + moveFactor * (centerX - scale.saveMinX);
scale.maxX = scale.saveMaxX - moveFactor * (scale.saveMaxX - centerX);
float centerY = (scale.saveMinY + scale.saveMaxY)/2;
scale.minY = scale.saveMinY + moveFactor * (centerY - scale.saveMinY);
scale.maxY = scale.saveMaxY - moveFactor * (scale.saveMaxY - centerY);
do_refresh = true;
}
if ( do_refresh ){
long now = SystemTime.getMonotonousTime();
if ( now - last_refresh >= 250 ){
last_refresh = now;
refresh();
}
}
}
});
canvas.addMouseTrackListener(new MouseTrackListener() {
public void mouseHover(MouseEvent e) {
}
public void mouseExit(MouseEvent e) {
if (autoAlpha) {
setAlpha(ALPHA_NOFOCUS);
}
}
public void mouseEnter(MouseEvent e) {
if (autoAlpha) {
setAlpha(ALPHA_FOCUS);
}
}
});
timeout_timer =
SimpleTimer.addPeriodicEvent(
"DHTOps:timer",
30*1000,
new TimerEventPerformer()
{
public void
perform(
TimerEvent event)
{
if ( canvas.isDisposed()){
timeout_timer.cancel();
return;
}
synchronized( activity_map ){
Iterator<ActivityDetail> it = activity_map.values().iterator();
while( it.hasNext()){
ActivityDetail act = it.next();
if ( act.isComplete()){
it.remove();
}
}
}
}
});
}
public void setLayoutData(Object data) {
canvas.setLayoutData(data);
}
public void
activityChanged(
DHTControlActivity activity,
int type )
{
//System.out.println( activity.getString() + "/" + type + "/" + activity.getCurrentState().getString());
synchronized( activity_map ){
ActivityDetail details = activity_map.get( activity );
if ( details == null ){
details = new ActivityDetail( activity );
activity_map.put( activity, details );
}
if ( type == DHTControlListener.CT_REMOVED ){
details.setComplete();
}
}
}
protected void
setUnavailable()
{
Utils.execSWTThread(
new Runnable()
{
public void
run()
{
unavailable = true;
if ( !canvas.isDisposed()){
canvas.redraw();
}
}
});
}
public void
refreshView(
DHT dht )
{
if ( current_dht != dht ){
if ( current_dht != null ){
current_dht.getControl().removeListener( this );
}
current_dht = dht;
synchronized( activity_map ){
activity_map.clear();
}
dht.getControl().addListener( this );
}
refresh();
}
public void
refresh()
{
if ( canvas.isDisposed()){
return;
}
Rectangle size = canvas.getBounds();
scale.width = size.width;
scale.height = size.height;
if (img != null && !img.isDisposed()){
img.dispose();
}
img = new Image(display,size);
GC gc = new GC(img);
gc.setAdvanced( true );
gc.setAntialias( SWT.ON );
gc.setTextAntialias( SWT.ON );
Color white = ColorCache.getColor(display,255,255,255);
gc.setForeground(white);
gc.setBackground(white);
gc.fillRectangle(size);
List<ActivityDetail> activities;
List<ActivityDetail> to_remove = new ArrayList<ActivityDetail>();
synchronized( activity_map ){
activities = new ArrayList<ActivityDetail>( activity_map.values());
}
long now = SystemTime.getMonotonousTime();
int max_slot = Math.max( activities.size(), 8 ); // always have at least 8 slots
for ( ActivityDetail details: activities ){
max_slot = Math.max( max_slot, details.getSlot()+1);
long comp_at = details.getCompleteTime();
if ( comp_at >= 0 && now - comp_at > FADE_OUT ){
to_remove.add( details );
}
}
boolean[] slots_in_use = new boolean[max_slot];
for ( ActivityDetail details: activities ){
int slot = details.getSlot();
if ( slot != -1 ){
slots_in_use[slot] = true;
}
}
int pos = 0;
for ( ActivityDetail details: activities ){
int slot = details.getSlot();
if ( slot == -1 ){
while( slots_in_use[pos] ){
pos++;
}
details.setSlot( pos++ );
}
}
int x_origin = scale.getX(0, 0);
int y_origin = scale.getY(0, 0);
double slice_angle = 2*Math.PI/max_slot;
for ( ActivityDetail details: activities ){
details.draw( gc, x_origin, y_origin, slice_angle );
}
gc.setForeground( ColorCache.getColor( gc.getDevice(), 0, 0, 0 ));
if ( activities.size() == 0 ){
gc.drawText( MessageText.getString( DHTOpsView.MSGID_PREFIX + ".idle" ), x_origin, y_origin );
}else{
gc.drawLine(x_origin-5, y_origin, x_origin+5, y_origin);
gc.drawLine(x_origin, y_origin-5, x_origin, y_origin+5);
}
gc.dispose();
canvas.redraw();
if ( to_remove.size() > 0 ){
synchronized( activity_map ){
for ( ActivityDetail detail: to_remove ){
activity_map.remove( detail.getActivity());
}
}
}
}
public int getAlpha() {
return alpha;
}
public void setAlpha(int alpha) {
this.alpha = alpha;
if (canvas != null && !canvas.isDisposed()) {
canvas.redraw();
}
}
public void setAutoAlpha(boolean autoAlpha) {
this.autoAlpha = autoAlpha;
if (autoAlpha) {
setAlpha(canvas.getDisplay().getCursorControl() == canvas ? ALPHA_FOCUS : ALPHA_NOFOCUS);
}
}
public void delete()
{
if(img != null && !img.isDisposed())
{
img.dispose();
}
if ( current_dht != null ){
current_dht.getControl().removeListener( this );
current_dht = null;
}
synchronized( activity_map ){
activity_map.clear();
}
}
private class
ActivityDetail
{
private DHTControlActivity activity;
private long complete_time = -1;
private int slot = -1;
private int draw_count = 0;
private String result_str = "";
private
ActivityDetail(
DHTControlActivity _act )
{
activity = _act;
}
private DHTControlActivity
getActivity()
{
return( activity );
}
private void
setComplete()
{
complete_time = SystemTime.getMonotonousTime();
}
private long
getCompleteTime()
{
return( complete_time );
}
private boolean
isComplete()
{
return( complete_time != -1 &&
SystemTime.getMonotonousTime() - complete_time > FADE_OUT );
}
private int
getSlot()
{
return( slot );
}
private void
setSlot(
int _s )
{
slot = _s;
}
private void
draw(
GC gc,
int x_origin,
int y_origin,
double slice_angle )
{
draw_count++;
setColour( gc );
double angle = slice_angle*slot;
ActivityState state_maybe_null = activity.getCurrentState();
if ( state_maybe_null != null ){
int depth = state_maybe_null.getDepth();
int level_depth = 750/depth;
ActivityNode root = state_maybe_null.getRootNode();
List<Object[]> level_nodes = new ArrayList<Object[]>();
float x_start = (float)( 50*Math.sin( angle ));
float y_start = (float)( 50*Math.cos( angle ));
level_nodes.add( new Object[]{ root, x_start, y_start });
int node_distance = 50;
while( true ){
int nodes_at_next_level = 0;
for ( Object[] entry: level_nodes ){
nodes_at_next_level += ((ActivityNode)entry[0]).getChildren().size();
}
if ( nodes_at_next_level == 0 ){
break;
}
node_distance += level_depth;
double node_slice_angle = slice_angle/nodes_at_next_level;
double current_angle = angle;
if ( nodes_at_next_level > 1 ){
current_angle = current_angle - (slice_angle/2);
current_angle += (slice_angle - node_slice_angle*(nodes_at_next_level-1))/2;
}
List<Object[]> next_level_nodes = new ArrayList<Object[]>();
for ( Object[] entry: level_nodes ){
ActivityNode node = (ActivityNode)entry[0];
float node_x = (Float)entry[1];
float node_y = (Float)entry[2];
int seg_start_x = scale.getX(node_x, node_y);
int seg_start_y = scale.getY(node_x, node_y);
List<ActivityNode> kids = node.getChildren();
for ( ActivityNode kid: kids ){
float kid_x = (float)( node_distance*Math.sin( current_angle ));
float kid_y = (float)( node_distance*Math.cos( current_angle ));
next_level_nodes.add( new Object[]{ kid, kid_x, kid_y } );
current_angle += node_slice_angle;
int seg_end_x = scale.getX(kid_x, kid_y);
int seg_end_y = scale.getY(kid_x, kid_y);
gc.drawLine(seg_start_x, seg_start_y, seg_end_x, seg_end_y );
gc.drawOval( seg_end_x, seg_end_y, 1, 1 );
}
}
level_nodes = next_level_nodes;
}
}
float x_end = (float)( 850*Math.sin( angle ));
float y_end = (float)( 850*Math.cos( angle ));
int text_x = scale.getX(x_end, y_end);
int text_y = scale.getY(x_end, y_end);
if ( complete_time >= 0 && result_str.length() == 0 ){
if ( state_maybe_null != null ){
result_str = ": " + state_maybe_null.getResult();
}
}
gc.drawText( activity.getDescription() + result_str, text_x, text_y );
//gc.drawLine(x_origin, y_origin, (int)x_end, (int)y_end );
gc.setAlpha( 255 );
}
private void
setColour(
GC gc )
{
if ( complete_time != -1 && draw_count > 1 ){
int age = (int)( SystemTime.getMonotonousTime() - complete_time );
gc.setAlpha( Math.max( 0, 200 - (255*age/FADE_OUT)));
gc.setForeground( ColorCache.getColor( gc.getDevice(), 0, 0, 0 ));
}else{
gc.setAlpha( 255 );
int type = activity.getType();
if ( type == DHTControlActivity.AT_EXTERNAL_GET ){
gc.setForeground( ColorCache.getColor( gc.getDevice(), 20, 200, 20 ));
}else if ( type == DHTControlActivity.AT_INTERNAL_GET ){
gc.setForeground( ColorCache.getColor( gc.getDevice(), 140, 160, 40 ));
}else if ( type == DHTControlActivity.AT_EXTERNAL_PUT ){
gc.setForeground( ColorCache.getColor( gc.getDevice(), 20, 20, 220 ));
}else{
gc.setForeground( ColorCache.getColor( gc.getDevice(), 40, 140, 160 ));
}
}
}
}
}