/**
* Copyright (C) 2007 Aelitis, 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* AELITIS, SAS au capital de 63.529,40 euros
* 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
*
*/
package com.aelitis.azureus.ui.swt.views;
import java.util.*;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.*;
import org.gudy.azureus2.core3.disk.DiskManager;
import org.gudy.azureus2.core3.disk.DiskManagerPiece;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.peer.PEPeer;
import org.gudy.azureus2.core3.peer.PEPeerManager;
import org.gudy.azureus2.core3.peer.PEPiece;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.util.AERunnable;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.ui.swt.Messages;
import org.gudy.azureus2.ui.swt.Utils;
import org.gudy.azureus2.ui.swt.plugins.UISWTView;
import org.gudy.azureus2.ui.swt.plugins.UISWTViewEvent;
import org.gudy.azureus2.ui.swt.pluginsimpl.UISWTViewCoreEventListener;
import com.aelitis.azureus.ui.swt.skin.SWTSkinFactory;
import com.aelitis.azureus.ui.swt.skin.SWTSkinProperties;
/**
* @author TuxPaper
* @created Apr 9, 2007
*
*/
public class PieceGraphView
implements UISWTViewCoreEventListener
{
// TODO: Buffer
private boolean onePiecePerBlock = false;
private int BLOCK_FILLSIZE = 21;
private final static int BLOCK_SPACING = 3;
private int BLOCK_SIZE = BLOCK_FILLSIZE + BLOCK_SPACING;
private final static int BLOCKCOLOR_HAVEALL = 0;
private final static int BLOCKCOLOR_NOHAVE = 1;
private final static int BLOCKCOLOR_UPLOADING = 2;
private final static int BLOCKCOLOR_DOWNLOADING = 3;
private final static int BLOCKCOLOR_NOAVAIL = 4;
private final static int BLOCKCOLOR_HAVESOME = 5;
private Color[] blockColors;
private Canvas canvas;
private Image img;
private Image imgHaveAll;
private Image imgNoHave;
private DownloadManager dlm;
private Comparator compFindPEPiece;
private final SWTSkinProperties properties;
private double[] squareCache;
private UISWTView swtView;
public PieceGraphView() {
this.properties = SWTSkinFactory.getInstance().getSkinProperties();
}
// @see org.gudy.azureus2.ui.swt.views.AbstractIView#initialize(org.eclipse.swt.widgets.Composite)
private void initialize(Composite parent) {
blockColors = new Color[] {
properties.getColor("color.pieceview.alldone"),
properties.getColor("color.pieceview.notdone"),
properties.getColor("color.pieceview.uploading"),
properties.getColor("color.pieceview.downloading"),
properties.getColor("color.pieceview.noavail"),
properties.getColor("color.pieceview.havesome"),
};
compFindPEPiece = new Comparator() {
public int compare(Object arg0, Object arg1) {
int arg0no = (arg0 instanceof PEPiece)
? ((PEPiece) arg0).getPieceNumber() : ((Long) arg0).intValue();
int arg1no = (arg1 instanceof PEPiece)
? ((PEPiece) arg1).getPieceNumber() : ((Long) arg1).intValue();
return arg0no - arg1no;
}
};
canvas = new Canvas(parent, SWT.NO_BACKGROUND);
canvas.setLayout(new FillLayout());
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) {
e.gc.drawImage(img, e.x, e.y, e.width, e.height, e.x, e.y, e.width,
e.height);
}
} else {
e.gc.fillRectangle(e.x, e.y, e.width, e.height);
}
}
});
canvas.addListener(SWT.Resize, new Listener() {
public void handleEvent(Event event) {
calcBlockSize();
}
});
}
// @see org.gudy.azureus2.ui.swt.views.AbstractIView#dataSourceChanged(java.lang.Object)
private void dataSourceChanged(Object newDataSource) {
if (newDataSource instanceof DownloadManager) {
dlm = (DownloadManager) newDataSource;
} else {
dlm = null;
}
calcBlockSize();
}
// @see org.gudy.azureus2.ui.swt.views.AbstractIView#refresh()
private void refresh() {
buildImage();
}
private void calcBlockSize() {
if (!onePiecePerBlock) {
buildImage();
return;
}
Utils.execSWTThread(new AERunnable() {
public void runSupport() {
if (canvas == null || canvas.isDisposed()) {
return;
}
TOTorrent torrent = dlm == null ? null : dlm.getTorrent();
if (torrent == null) {
BLOCK_SIZE = 21 + BLOCK_SPACING;
} else {
long numPieces = torrent.getNumberOfPieces();
Rectangle bounds = canvas.getClientArea();
BLOCK_SIZE = (int) Math.sqrt((double) (bounds.width * bounds.height)
/ numPieces);
if (BLOCK_SIZE <= 0) {
BLOCK_SIZE = 1;
}
// since calc above doesn't account for not splitting squares across
// rows, make sure we can fit. If we can't, we have to shrink
int numCanFit = (bounds.width / BLOCK_SIZE)
* (bounds.height / BLOCK_SIZE);
if (numCanFit < numPieces) {
BLOCK_SIZE--;
}
//System.out.println((float)(bounds.width * bounds.height) / numPieces);
//System.out.println(BLOCK_SIZE + ";" + (bounds.width / BLOCK_SIZE));
if (BLOCK_SIZE < 2) {
BLOCK_SIZE = 2;
}
}
BLOCK_FILLSIZE = BLOCK_SIZE - BLOCK_SPACING;
Utils.disposeSWTObjects(new Object[] {
imgHaveAll,
imgNoHave
});
buildImage();
}
});
}
private void buildImage() {
if (canvas == null || canvas.isDisposed()) {
return;
}
//canvas.setBackground(ColorCache.getColor(canvas.getDisplay(), "#1b1b1b"));
Rectangle bounds = canvas.getClientArea();
if (bounds.isEmpty()) {
return;
}
if (dlm == null) {
canvas.redraw();
return;
}
DiskManagerPiece[] dm_pieces = null;
PEPeerManager pm = dlm.getPeerManager();
DiskManager dm = dlm.getDiskManager();
if (pm == null || dm == null) {
canvas.redraw();
return;
}
dm_pieces = dm.getPieces();
if (dm_pieces == null || dm_pieces.length == 0) {
canvas.redraw();
return;
}
int numPieces = dm_pieces.length;
if (imgHaveAll == null || imgHaveAll.isDisposed()) {
imgHaveAll = new Image(canvas.getDisplay(), BLOCK_SIZE, BLOCK_SIZE);
GC gc = new GC(imgHaveAll);
try {
try {
gc.setAntialias(SWT.ON);
} catch (Exception e) {
// ignore
}
gc.setBackground(canvas.getBackground());
gc.fillRectangle(imgHaveAll.getBounds());
gc.setBackground(blockColors[BLOCKCOLOR_HAVEALL]);
gc.fillRoundRectangle(1, 1, BLOCK_FILLSIZE, BLOCK_FILLSIZE,
BLOCK_FILLSIZE, BLOCK_FILLSIZE);
} finally {
gc.dispose();
}
}
if (imgNoHave == null || imgNoHave.isDisposed()) {
imgNoHave = new Image(canvas.getDisplay(), BLOCK_SIZE, BLOCK_SIZE);
GC gc = new GC(imgNoHave);
try {
try {
gc.setAntialias(SWT.ON);
} catch (Exception e) {
// ignore
}
gc.setBackground(canvas.getBackground());
gc.fillRectangle(imgNoHave.getBounds());
gc.setBackground(blockColors[BLOCKCOLOR_NOHAVE]);
gc.fillRoundRectangle(1, 1, BLOCK_FILLSIZE, BLOCK_FILLSIZE,
BLOCK_FILLSIZE, BLOCK_FILLSIZE);
} finally {
gc.dispose();
}
}
boolean clearImage = img == null || img.isDisposed()
|| img.getBounds().width != bounds.width
|| img.getBounds().height != bounds.height;
if (clearImage) {
if (img != null && !img.isDisposed()) {
img.dispose();
}
//System.out.println("clear " + img);
img = new Image(canvas.getDisplay(), bounds.width, bounds.height);
squareCache = null;
}
PEPiece[] currentDLPieces = dlm.getCurrentPieces();
Arrays.sort(currentDLPieces, compFindPEPiece);
// find upload pieces
ArrayList currentULPieces = new ArrayList();
ArrayList futureULPieces = new ArrayList();
PEPeer[] peers = (PEPeer[]) pm.getPeers().toArray(new PEPeer[0]);
for (int i = 0; i < peers.length; i++) {
PEPeer peer = peers[i];
int[] peerRequestedPieces = peer.getIncomingRequestedPieceNumbers();
if (peerRequestedPieces != null && peerRequestedPieces.length > 0) {
currentULPieces.add(new Long(peerRequestedPieces[0]));
for (int j = 1; j < peerRequestedPieces.length; j++) {
futureULPieces.add(new Long(peerRequestedPieces[j]));
}
}
// we'll have duplicates
Collections.sort(currentULPieces);
Collections.sort(futureULPieces);
}
int iNumCols = bounds.width / BLOCK_SIZE;
int iNumRows = bounds.height / BLOCK_SIZE;
int numSquares = onePiecePerBlock ? numPieces : iNumCols * iNumRows;
double numPiecesPerSquare = numPieces / (double) numSquares;
//System.out.println("numPiecesPerSquare=" + numPiecesPerSquare);
if (squareCache == null || squareCache.length != numSquares) {
squareCache = new double[numSquares];
Arrays.fill(squareCache, -1);
}
int[] availability = pm.getAvailability();
int numRedraws = 0;
GC gc = new GC(img);
try {
int iRow = 0;
if (clearImage) {
gc.setBackground(canvas.getBackground());
gc.fillRectangle(bounds);
}
try {
gc.setAdvanced(true);
gc.setAntialias(SWT.ON);
gc.setInterpolation(SWT.HIGH);
} catch (Exception e) {
// ignore
}
int iCol = 0;
for (int squareNo = 0; squareNo < numSquares; squareNo++) {
if (iCol >= iNumCols) {
iCol = 0;
iRow++;
}
int startNo = (int) (squareNo * numPiecesPerSquare);
int count = (int) ((squareNo + 1) * numPiecesPerSquare) - startNo;
if (count == 0) {
count = 1;
}
//if (count > 1) System.out.println("!!! " + startNo);
//System.out.println(startNo + ";" + count);
double pctDone = getPercentDone(startNo, count, dm_pieces);
//System.out.print(pctDone + ";");
int colorIndex;
int iXPos = iCol * BLOCK_SIZE;
int iYPos = iRow * BLOCK_SIZE;
if (pctDone == 1) {
if (squareCache[squareNo] != pctDone) {
squareCache[squareNo] = pctDone;
gc.drawImage(imgHaveAll, iXPos, iYPos);
if (!clearImage) {
numRedraws++;
canvas.redraw(iXPos, iYPos, BLOCK_SIZE, BLOCK_SIZE, false);
}
}
} else if (pctDone == 0) {
if (squareCache[squareNo] != pctDone) {
squareCache[squareNo] = pctDone;
gc.drawImage(imgNoHave, iXPos, iYPos);
if (!clearImage) {
numRedraws++;
canvas.redraw(iXPos, iYPos, BLOCK_SIZE, BLOCK_SIZE, false);
}
}
} else {
// !done
boolean isDownloading = false;
for (int i = startNo; i < startNo + count; i++) {
if (Arrays.binarySearch(currentDLPieces, new Long(i),
compFindPEPiece) >= 0) {
isDownloading = true;
break;
}
}
double val = pctDone + (isDownloading ? 0 : 1);
if (squareCache[squareNo] != val) {
squareCache[squareNo] = val;
gc.drawImage(imgNoHave, iXPos, iYPos);
int size = (int) (BLOCK_FILLSIZE * pctDone);
if (size == 0) {
size = 1;
}
int q = (int) ((BLOCK_FILLSIZE - size) / 2.0 + 0.5) + 1;
colorIndex = isDownloading ? BLOCKCOLOR_DOWNLOADING
: BLOCKCOLOR_HAVESOME;
gc.setBackground(blockColors[colorIndex]);
gc.fillOval(iXPos + q, iYPos + q, size, size);
//gc.fillRoundRectangle(iXPos + q, iYPos + q, size, size, size, size);
if (!clearImage) {
numRedraws++;
canvas.redraw(iXPos, iYPos, BLOCK_SIZE, BLOCK_SIZE, false);
}
}
}
for (int i = startNo; i < startNo + count; i++) {
if (Collections.binarySearch(currentULPieces, new Long(i)) >= 0) {
colorIndex = BLOCKCOLOR_UPLOADING;
int size = BLOCK_FILLSIZE + 1;
gc.setForeground(blockColors[colorIndex]);
gc.drawRoundRectangle(iXPos, iYPos, size, size, size, size);
if (!clearImage) {
numRedraws++;
canvas.redraw(iXPos, iYPos, BLOCK_SIZE, BLOCK_SIZE, false);
}
squareCache[squareNo] = -1;
break;
} else if (Collections.binarySearch(futureULPieces, new Long(i)) >= 0) {
colorIndex = BLOCKCOLOR_UPLOADING;
int size = BLOCK_FILLSIZE + 1;
gc.setForeground(blockColors[colorIndex]);
gc.setLineStyle(SWT.LINE_DOT);
gc.drawRoundRectangle(iXPos, iYPos, size, size, size, size);
if (!clearImage) {
numRedraws++;
canvas.redraw(iXPos, iYPos, BLOCK_SIZE, BLOCK_SIZE, false);
}
gc.setLineStyle(SWT.LINE_SOLID);
squareCache[squareNo] = -1;
break;
}
}
if (availability != null) {
boolean hasNoAvail = false;
for (int i = startNo; i < startNo + count; i++) {
if (availability[i] == 0) {
hasNoAvail = true;
squareCache[squareNo] = -1;
break;
}
}
if (hasNoAvail) {
gc.setForeground(blockColors[BLOCKCOLOR_NOAVAIL]);
gc.drawRectangle(iXPos, iYPos, BLOCK_FILLSIZE + 1,
BLOCK_FILLSIZE + 1);
if (!clearImage) {
numRedraws++;
canvas.redraw(iXPos, iYPos, BLOCK_SIZE, BLOCK_SIZE, false);
}
}
}
iCol++;
}
//System.out.println("redraws " + numRedraws);
} catch (Exception e) {
Debug.out(e);
} finally {
gc.dispose();
}
//canvas.redraw();
}
/**
* @param startNo
* @param count
* @param dm_pieces
* @return
*
* @since 3.0.1.1
*/
private double getPercentDone(int startNo, int count,
DiskManagerPiece[] dm_pieces) {
int totalComplete = 0;
int totalBlocks = 0;
for (int i = startNo; i < startNo + count; i++) {
DiskManagerPiece piece = dm_pieces == null ? null : dm_pieces[i];
int numBlocks = piece.getNbBlocks();
totalBlocks += numBlocks;
boolean done = (piece == null) ? false : piece.isDone();
if (done) {
totalComplete += numBlocks;
} else {
// !done
totalComplete += piece.getNbWritten();
}
}
return (double) totalComplete / totalBlocks;
}
public boolean eventOccurred(UISWTViewEvent event) {
switch (event.getType()) {
case UISWTViewEvent.TYPE_CREATE:
swtView = (UISWTView)event.getData();
break;
case UISWTViewEvent.TYPE_DESTROY:
delete();
break;
case UISWTViewEvent.TYPE_INITIALIZE:
initialize((Composite)event.getData());
break;
case UISWTViewEvent.TYPE_LANGUAGEUPDATE:
Messages.updateLanguageForControl(canvas);
break;
case UISWTViewEvent.TYPE_DATASOURCE_CHANGED:
dataSourceChanged(event.getData());
break;
case UISWTViewEvent.TYPE_REFRESH:
refresh();
break;
}
return true;
}
/**
*
*
* @since 3.1.0.1
*/
private void delete() {
// TODO Auto-generated method stub
}
}