/* * Created on 19 nov. 2004 * 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; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.custom.CTabItem; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.gudy.azureus2.core3.disk.DiskManagerFileInfo; import org.gudy.azureus2.core3.download.DownloadManager; import org.gudy.azureus2.core3.download.DownloadManagerPeerListener; import org.gudy.azureus2.core3.internat.MessageText; import org.gudy.azureus2.core3.peer.PEPeer; import org.gudy.azureus2.core3.peer.PEPeerManager; import org.gudy.azureus2.core3.peer.PEPeerStats; import org.gudy.azureus2.core3.peer.impl.PEPeerTransport; import org.gudy.azureus2.core3.peer.util.PeerUtils; import org.gudy.azureus2.core3.util.AEMonitor; import org.gudy.azureus2.core3.util.Base32; import org.gudy.azureus2.core3.util.DisplayFormatters; import org.gudy.azureus2.plugins.ui.UIPluginViewToolBarListener; import org.gudy.azureus2.ui.swt.ImageRepository; import org.gudy.azureus2.ui.swt.Messages; import org.gudy.azureus2.ui.swt.components.graphics.PieUtils; import org.gudy.azureus2.ui.swt.mainwindow.Colors; import org.gudy.azureus2.ui.swt.plugins.UISWTView; import org.gudy.azureus2.ui.swt.plugins.UISWTViewEvent; import org.gudy.azureus2.ui.swt.plugins.UISWTViewEventListener; import org.gudy.azureus2.ui.swt.pluginsimpl.UISWTViewCore; import org.gudy.azureus2.ui.swt.pluginsimpl.UISWTViewCoreEventListener; import org.gudy.azureus2.ui.swt.pluginsimpl.UISWTViewEventListenerHolder; import com.aelitis.azureus.core.networkmanager.admin.NetworkAdmin; import com.aelitis.azureus.ui.UIFunctionsManager; import com.aelitis.azureus.ui.common.ToolBarItem; import com.aelitis.azureus.ui.mdi.MdiEntry; import com.aelitis.azureus.ui.selectedcontent.SelectedContent; import com.aelitis.azureus.ui.selectedcontent.SelectedContentManager; /** * This is the "Swarm" View * * @author Olivier Chalouhi * */ public class PeersGraphicView implements DownloadManagerPeerListener, UISWTViewCoreEventListener, UIPluginViewToolBarListener { public static String MSGID_PREFIX = "PeersGraphicView"; private DownloadManager manager = null; private static final int NB_ANGLES = 1000; private double[] angles; //private double[] deltaPerimeters; private double perimeter; private double[] rs; private double[] deltaXXs; private double[] deltaXYs; private double[] deltaYXs; private double[] deltaYYs; private Point oldSize; private List<PEPeer> peers; private AEMonitor peers_mon = new AEMonitor( "PeersGraphicView:peers" );; private PeerComparator peerComparator; private Map<PEPeer,int[]> peer_hit_map = new HashMap<PEPeer, int[]>(); private int me_hit_x; private int me_hit_y; private Image my_flag; //UI Stuff private Display display; private Composite panel; private UISWTView swtView; private static final int PEER_SIZE = 18; //private static final int PACKET_SIZE = 10; private static final int OWN_SIZE_DEFAULT = 75; private static final int OWN_SIZE_MIN = 30; private static final int OWN_SIZE_MAX = 75; private static int OWN_SIZE = OWN_SIZE_DEFAULT; //Comparator Class //Note: this comparator imposes orderings that are inconsistent with equals. class PeerComparator implements Comparator<PEPeer> { public int compare(PEPeer peer0, PEPeer peer1) { int percent0 = peer0.getPercentDoneInThousandNotation(); int percent1 = peer1.getPercentDoneInThousandNotation(); int result = percent0 - percent1; if ( result == 0 ){ long l = peer0.getTimeSinceConnectionEstablished() - peer1.getTimeSinceConnectionEstablished(); if ( l < 0 ){ result = -1; }else if ( l > 0 ){ result = 1; } } return( result ); } } public PeersGraphicView() { angles = new double[NB_ANGLES]; //deltaPerimeters = new double[NB_ANGLES]; rs = new double[NB_ANGLES]; deltaXXs = new double[NB_ANGLES]; deltaXYs = new double[NB_ANGLES]; deltaYXs = new double[NB_ANGLES]; deltaYYs = new double[NB_ANGLES]; for(int i = 0 ; i < NB_ANGLES ; i++) { angles[i] = 2 * i * Math.PI / NB_ANGLES - Math.PI; deltaXXs[i] = Math.cos(angles[i]); deltaXYs[i] = Math.sin(angles[i]); deltaYXs[i] = Math.cos(angles[i]+Math.PI / 2); deltaYYs[i] = Math.sin(angles[i]+Math.PI / 2); } this.peers = new ArrayList<PEPeer>(); this.peerComparator = new PeerComparator(); } private boolean comp_focused; private Object focus_pending_ds; private void setFocused( boolean foc ) { if ( foc ){ comp_focused = true; dataSourceChanged( focus_pending_ds ); }else{ focus_pending_ds = manager; dataSourceChanged( null ); comp_focused = false; } } private void dataSourceChanged(Object newDataSource) { if ( !comp_focused ){ focus_pending_ds = newDataSource; return; } // defer this util here so that a blocking call to get the IP doesn't hang UI construction if ( my_flag == null ){ InetAddress ia = NetworkAdmin.getSingleton().getDefaultPublicAddress(); if ( ia != null ){ my_flag = ImageRepository.getCountryFlag( ia, false ); } } DownloadManager old_manager = manager; if (newDataSource == null){ manager = null; }else if (newDataSource instanceof Object[]){ Object temp = ((Object[])newDataSource)[0]; if ( temp instanceof DownloadManager ){ manager = (DownloadManager)temp; }else if ( temp instanceof DiskManagerFileInfo){ manager = ((DiskManagerFileInfo)temp).getDownloadManager(); }else{ return; } }else{ if ( newDataSource instanceof DownloadManager ){ manager = (DownloadManager)newDataSource; }else if ( newDataSource instanceof DiskManagerFileInfo){ manager = ((DiskManagerFileInfo)newDataSource).getDownloadManager(); }else{ return; } } if ( old_manager == manager ){ return; } if (old_manager != null){ old_manager.removePeerListener(this); } try { peers_mon.enter(); peers.clear(); } finally { peers_mon.exit(); } if (manager != null){ manager.addPeerListener(this); } } private void delete() { if (manager != null){ manager.removePeerListener(this); } peer_hit_map.clear(); } private Composite getComposite() { return panel; } private String getData() { return "PeersGraphicView.title.full"; } private void initialize(Composite composite) { display = composite.getDisplay(); panel = new Canvas(composite,SWT.NO_BACKGROUND); panel.addListener(SWT.MouseHover, new Listener() { public void handleEvent(Event event) { if ( manager == null ){ return; } int x = event.x; int y = event.y; String tt; if ( x >= me_hit_x && x <= me_hit_x+OWN_SIZE && y >= me_hit_y && y <= me_hit_y+OWN_SIZE ){ tt = DisplayFormatters.formatDownloadStatus( manager ) + ", " + DisplayFormatters.formatPercentFromThousands(manager.getStats().getCompleted()); }else{ PEPeer target = null; for( Map.Entry<PEPeer,int[]> entry: peer_hit_map.entrySet()){ int[] loc = entry.getValue(); int loc_x = loc[0]; int loc_y = loc[1]; if ( x >= loc_x && x <= loc_x+PEER_SIZE && y >= loc_y && y <= loc_y+PEER_SIZE ){ target = entry.getKey(); break; } } if ( target == null ){ tt = null; }else{ PEPeerStats stats = target.getStats(); String[] details = PeerUtils.getCountryDetails( target ); String dstr = (details==null||details.length<2)?"":(" - " + details[0] + "/" + details[1]); tt = target.getIp() + dstr + ", " + DisplayFormatters.formatPercentFromThousands(target.getPercentDoneInThousandNotation()) + "\r\n" + "Up=" + DisplayFormatters.formatByteCountToKiBEtcPerSec( stats.getDataSendRate() + stats.getProtocolSendRate()) + ", " + "Down=" + DisplayFormatters.formatByteCountToKiBEtcPerSec( stats.getDataReceiveRate() + stats.getProtocolReceiveRate()); } } panel.setToolTipText( tt ); } }); panel.addMouseListener( new MouseAdapter() { public void mouseDoubleClick( MouseEvent event ) { int x = event.x; int y = event.y; for( Map.Entry<PEPeer,int[]> entry: peer_hit_map.entrySet()){ int[] loc = entry.getValue(); int loc_x = loc[0]; int loc_y = loc[1]; if ( x >= loc_x && x <= loc_x+PEER_SIZE && y >= loc_y && y <= loc_y+PEER_SIZE ){ PEPeer target = entry.getKey(); // ugly code to locate any associated 'PeersView' that we can locate the peer in try{ String dm_id = "DMDetails_" + Base32.encode( manager.getTorrent().getHash()); MdiEntry mdi_entry = UIFunctionsManager.getUIFunctions().getMDI().getEntry( dm_id ); if ( mdi_entry != null ){ mdi_entry.setDatasource(new Object[] { manager, target } ); } Composite comp = panel.getParent(); while( comp != null ){ if ( comp instanceof CTabFolder ){ CTabFolder tf = (CTabFolder)comp; CTabItem[] items = tf.getItems(); for ( CTabItem item: items ){ UISWTViewCore view = (UISWTViewCore)item.getData("IView"); UISWTViewEventListener listener = view.getEventListener(); if ( listener instanceof UISWTViewEventListenerHolder ){ listener = ((UISWTViewEventListenerHolder)listener).getDelegatedEventListener( view ); } if ( listener instanceof PeersView ){ tf.setSelection( item ); Event ev = new Event(); ev.item = item; // manual setSelection doesn't file selection event - derp tf.notifyListeners( SWT.Selection, ev ); ((PeersView)listener).selectPeer( target ); return; } } } comp = comp.getParent(); } }catch( Throwable e ){ } break; } } } }); } private void refresh() { doRefresh(); } private void doRefresh() { //Comment the following line to enable the view //if(true) return; PEPeer[] sortedPeers; try { peers_mon.enter(); List<PEPeerTransport> connectedPeers = new ArrayList<PEPeerTransport>(); for (PEPeer peer : peers) { if (peer instanceof PEPeerTransport) { PEPeerTransport peerTransport = (PEPeerTransport) peer; if(peerTransport.getConnectionState() == PEPeerTransport.CONNECTION_FULLY_ESTABLISHED) connectedPeers.add(peerTransport); } } sortedPeers = connectedPeers.toArray(new PEPeer[connectedPeers.size()]); } finally { peers_mon.exit(); } if(sortedPeers == null) return; for (int i=0;i<3;i++){ try{ Arrays.sort(sortedPeers,peerComparator); break; }catch( IllegalArgumentException e ){ // can happen as peer data can change during sort and result in 'comparison method violates its general contract' error } } render(sortedPeers); } private void render(PEPeer[] sortedPeers) { peer_hit_map.clear(); if(panel == null || panel.isDisposed()){ return; } if ( manager == null ){ GC gcPanel = new GC(panel); gcPanel.fillRectangle( panel.getBounds()); gcPanel.dispose(); return; } Point panelSize = panel.getSize(); int min_dim = Math.min( panelSize.x, panelSize.y ); if ( min_dim <= 100 ){ OWN_SIZE = OWN_SIZE_MIN; }else if ( min_dim >= 400 ){ OWN_SIZE = OWN_SIZE_DEFAULT; }else{ int s_diff = OWN_SIZE_MAX - OWN_SIZE_MIN; float rat = (min_dim - 100.0f)/(400-100); OWN_SIZE = OWN_SIZE_MIN + (int)(s_diff * rat ); } int x0 = panelSize.x / 2; int y0 = panelSize.y / 2; int a = x0 - 20; int b = y0 - 20; if(a < 10 || b < 10) return; if(oldSize == null || !oldSize.equals(panelSize)) { oldSize = panelSize; perimeter = 0; for(int i = 0 ; i < NB_ANGLES ; i++) { rs[i] = Math.sqrt(1/(deltaYXs[i] * deltaYXs[i] / (a*a) + deltaYYs[i] * deltaYYs[i] / (b * b))); perimeter += rs[i]; } } Image buffer = new Image(display,panelSize.x,panelSize.y); GC gcBuffer = new GC(buffer); gcBuffer.setBackground(Colors.white); gcBuffer.setForeground(Colors.blue); gcBuffer.fillRectangle(0,0,panelSize.x,panelSize.y); try { gcBuffer.setTextAntialias(SWT.ON); gcBuffer.setAntialias(SWT.ON); } catch(Exception e) { } gcBuffer.setBackground(Colors.blues[Colors.BLUES_MIDLIGHT]); int nbPeers = sortedPeers.length; int iAngle = 0; double currentPerimeter = 0; //double angle; double r; for(int i = 0 ; i < nbPeers ; i++) { PEPeer peer = sortedPeers[i]; do { //angle = angles[iAngle]; r = rs[iAngle]; currentPerimeter += r; if(iAngle + 1 < NB_ANGLES) iAngle++; } while( currentPerimeter < i * perimeter / nbPeers); //angle = (4 * i - nbPeers) * Math.PI / (2 * nbPeers) - Math.PI / 2; int[] triangle = new int[6]; int percent_received = peer.getPercentDoneOfCurrentIncomingRequest(); int percent_sent = peer.getPercentDoneOfCurrentOutgoingRequest(); // set up base line state boolean drawLine = false; // unchoked if ( !peer.isChokingMe() || percent_received >= 0 ){ gcBuffer.setForeground(Colors.blues[1] ); drawLine = true; } // unchoking if ( !peer.isChokedByMe() || percent_sent >= 0 ){ gcBuffer.setForeground(Colors.blues[3]); drawLine = true; } // receiving from choked peer (fast request in) if ( !peer.isChokingMe() && peer.isUnchokeOverride() && peer.isInteresting()){ gcBuffer.setForeground(Colors.green); drawLine = true; } // sending to choked peer (fast request out) if ( peer.isChokedByMe() && percent_sent >= 0 ){ gcBuffer.setForeground(Colors.green); drawLine = true; } if ( drawLine ){ int x1 = x0 + (int) ( r * deltaYXs[iAngle] ); int y1 = y0 + (int) ( r * deltaYYs[iAngle] ); gcBuffer.drawLine(x0,y0,x1,y1); } if(percent_received >= 0) { gcBuffer.setBackground(Colors.blues[Colors.BLUES_MIDDARK]); double r1 = r - r * percent_received / 100; triangle[0] = (int) (x0 + (r1-10) * deltaYXs[iAngle] + 0.5); triangle[1] = (int) (y0 + (r1-10) * deltaYYs[iAngle] + 0.5); triangle[2] = (int) (x0 + deltaXXs[iAngle] * 4 + (r1) * deltaYXs[iAngle] + 0.5); triangle[3] = (int) (y0 + deltaXYs[iAngle] * 4 + (r1) * deltaYYs[iAngle] + 0.5); triangle[4] = (int) (x0 - deltaXXs[iAngle] * 4 + (r1) * deltaYXs[iAngle] + 0.5); triangle[5] = (int) (y0 - deltaXYs[iAngle] * 4 + (r1) * deltaYYs[iAngle] + 0.5); gcBuffer.fillPolygon(triangle); } if(percent_sent >= 0) { gcBuffer.setBackground(Colors.blues[Colors.BLUES_MIDLIGHT]); double r1 = r * percent_sent / 100; triangle[0] = (int) (x0 + r1 * deltaYXs[iAngle] + 0.5); triangle[1] = (int) (y0 + r1 * deltaYYs[iAngle] + 0.5); triangle[2] = (int) (x0 + deltaXXs[iAngle] * 4 + (r1-10) * deltaYXs[iAngle] + 0.5); triangle[3] = (int) (y0 + deltaXYs[iAngle] * 4 + (r1-10) * deltaYYs[iAngle] + 0.5); triangle[4] = (int) (x0 - deltaXXs[iAngle] * 4 + (r1-10) * deltaYXs[iAngle] + 0.5); triangle[5] = (int) (y0 - deltaXYs[iAngle] * 4 + (r1-10) * deltaYYs[iAngle] + 0.5); gcBuffer.fillPolygon(triangle); } int x1 = x0 + (int) (r * deltaYXs[iAngle]); int y1 = y0 + (int) (r * deltaYYs[iAngle]); gcBuffer.setBackground(Colors.blues[Colors.BLUES_MIDDARK]); if(peer.isSnubbed()) { gcBuffer.setBackground(Colors.grey); } /*int PS = (int) (PEER_SIZE); if (deltaXY == 0) { PS = (int) (PEER_SIZE * 2); } else { if (deltaYY > 0) { PS = (int) (PEER_SIZE / deltaXY); } }*/ //PieUtils.drawPie(gcBuffer,(x1 - PS / 2),y1 - PS / 2,PS,PS,peer.getPercentDoneInThousandNotation() / 10); int peer_x = x1 - PEER_SIZE / 2; int peer_y = y1 - PEER_SIZE / 2; peer_hit_map.put( peer, new int[]{ peer_x, peer_y }); Image flag = ImageRepository.getCountryFlag( peer, false ); if ( flag != null ){ PieUtils.drawPie(gcBuffer, flag, peer_x, peer_y,PEER_SIZE,PEER_SIZE,peer.getPercentDoneInThousandNotation() / 10, true ); }else{ PieUtils.drawPie(gcBuffer, peer_x, peer_y,PEER_SIZE,PEER_SIZE,peer.getPercentDoneInThousandNotation() / 10); } //gcBuffer.drawText(peer.getIp() , x1 + 8 , y1 , true); } gcBuffer.setBackground(Colors.blues[Colors.BLUES_MIDDARK]); me_hit_x = x0 - OWN_SIZE / 2; me_hit_y = y0 - OWN_SIZE / 2; PieUtils.drawPie(gcBuffer, me_hit_x, me_hit_y,OWN_SIZE,OWN_SIZE,manager.getStats().getCompleted() / 10); if ( my_flag != null ){ PieUtils.drawPie(gcBuffer, my_flag, me_hit_x, me_hit_y,OWN_SIZE,OWN_SIZE,manager.getStats().getCompleted() / 10, false ); } gcBuffer.dispose(); GC gcPanel = new GC(panel); gcPanel.drawImage(buffer,0,0); gcPanel.dispose(); buffer.dispose(); } public void peerManagerWillBeAdded( PEPeerManager peer_manager ){} public void peerManagerAdded(PEPeerManager manager) {} public void peerManagerRemoved(PEPeerManager manager) {} public void peerAdded(PEPeer peer) { try { peers_mon.enter(); peers.add(peer); } finally { peers_mon.exit(); } } public void peerRemoved(PEPeer peer) { try { peers_mon.enter(); peers.remove(peer); } finally { peers_mon.exit(); } } public boolean eventOccurred(UISWTViewEvent event) { switch (event.getType()) { case UISWTViewEvent.TYPE_CREATE: swtView = event.getView(); swtView.setTitle(MessageText.getString(getData())); swtView.setToolBarListener(this); break; case UISWTViewEvent.TYPE_DESTROY: delete(); break; case UISWTViewEvent.TYPE_INITIALIZE: initialize((Composite)event.getData()); break; case UISWTViewEvent.TYPE_LANGUAGEUPDATE: Messages.updateLanguageForControl(getComposite()); swtView.setTitle(MessageText.getString(getData())); break; case UISWTViewEvent.TYPE_DATASOURCE_CHANGED: dataSourceChanged(event.getData()); break; case UISWTViewEvent.TYPE_FOCUSGAINED: String id = "DMDetails_Swarm"; setFocused( true ); // do this before next code as it can pick up the corrent 'manager' ref if (manager != null) { if (manager.getTorrent() != null) { id += "." + manager.getInternalName(); } else { id += ":" + manager.getSize(); } } SelectedContentManager.changeCurrentlySelectedContent(id, new SelectedContent[] { new SelectedContent(manager) }); break; case UISWTViewEvent.TYPE_FOCUSLOST: setFocused( false ); break; case UISWTViewEvent.TYPE_REFRESH: refresh(); break; } return true; } public boolean toolBarItemActivated(ToolBarItem item, long activationType, Object datasource) { return( ViewUtils.toolBarItemActivated(manager, item, activationType, datasource)); } public void refreshToolBarItems(Map<String, Long> list) { ViewUtils.refreshToolBarItems(manager, list); } }