sysperfana/analyzetoolext/com.nokia.s60tools.analyzetool/src/com/nokia/s60tools/analyzetool/internal/ui/graph/AnalyzeToolGraph.java
author Matti Laitinen <matti.t.laitinen@nokia.com>
Tue, 24 Aug 2010 12:16:27 +0300
changeset 15 0367d2db2c06
parent 6 f65f740e69f9
permissions -rw-r--r--
AnalyzeTool Carbide extension 1.10.0

/*
 * Copyright (c) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
 * All rights reserved.
 * This component and the accompanying materials are made available
 * under the terms of "Eclipse Public License v1.0"
 * which accompanies this distribution, and is available
 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
 *
 * Initial Contributors:
 * Nokia Corporation - initial contribution.
 *
 * Contributors:
 *
 * Description:  Definitions for the class AnalyzeToolGraph
 *
 */
package com.nokia.s60tools.analyzetool.internal.ui.graph;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.draw2d.FigureCanvas;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.KeyEvent;
import org.eclipse.draw2d.KeyListener;
import org.eclipse.draw2d.MouseEvent;
import org.eclipse.draw2d.MouseListener;
import org.eclipse.draw2d.MouseMotionListener;
import org.eclipse.draw2d.Panel;
import org.eclipse.draw2d.XYLayout;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IPartService;
import org.eclipse.ui.IWindowListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.views.properties.PropertySheet;

import com.nokia.s60tools.analyzetool.Activator;
import com.nokia.s60tools.analyzetool.engine.IMemoryActivityModel;
import com.nokia.s60tools.analyzetool.engine.IMemoryActivityModelChangeListener;
import com.nokia.s60tools.analyzetool.engine.statistic.AllocInfo;
import com.nokia.s60tools.analyzetool.engine.statistic.BaseInfo;
import com.nokia.s60tools.analyzetool.engine.statistic.FreeInfo;
import com.nokia.s60tools.analyzetool.engine.statistic.ProcessInfo;
import com.nokia.s60tools.analyzetool.engine.statistic.SymReader;
import com.nokia.s60tools.analyzetool.global.Util;
import com.nokia.s60tools.analyzetool.internal.ui.util.ColorUtil;
import com.nokia.s60tools.analyzetool.internal.ui.util.GraphUtils;
import com.nokia.s60tools.analyzetool.ui.MainView;
import com.nokia.s60tools.analyzetool.ui.ResourceVisitor;

/**
 * A FigureCanvas containing the graph and X-axis area of the AnalyzeTool chart.
 */
public class AnalyzeToolGraph extends FigureCanvas implements
		IMemoryActivityModelChangeListener {

	private static final int BOUNDARY_OFFSET = 3;
	/** used for range model listener */
	private static final String PROP_MAXIMUM = "maximum"; //$NON-NLS-1$
	private static final int X_AXIS_HEIGHT = 50;
	/** used for "Don't ask again" dialog */
	private static final String PROMPT_KEY = "GraphOptimisationPrompt";//$NON-NLS-1$

	/** the scaling factor used for scaling the x-axis */
	private double scale = 1.0;

	/** the model */
	private IMemoryActivityModel model;

	/** for synchronization with the PropertySheet */
	private DotSelectionProvider iDotsSelecProv = new DotSelectionProvider();
	private IWorkbenchPartSite site;

	/** used when user selects a dot on the graph, and moves with arrow keys */
	private ISelection iCurrentSelectedDot = null;

	/** controls mouse and arrow key movements */
	private MouseAndKeyController mc;
	private SymReader iSymReader = null;
	private IProject iCurrentProject = null;
	/** Contains c++ files info for the current project. */
	private final AbstractList<String> cppFileNames;

	/** "time ->" on axis */
	private Image timeImage;
	private GraphPartServiceListener iGraphPartServiceListener;
	private boolean optimisedDrawing;
	private boolean userInformed;

	/**
	 * The threshold value for filtered drawing of dots. Only draw the dot if
	 * the memory operation size is above or equals / below or equals the
	 * threshold. Zero for no filtering.
	 */
	private long threshold;
	/** indicates whether filtering is above or below the threshold */
	private boolean aboveThreshold;

	/**
	 * Constructor
	 * 
	 * @param parent
	 *            The parent composite
	 */
	public AnalyzeToolGraph(Composite parent) {
		super(parent);
		IPartService partService = PlatformUI.getWorkbench()
				.getActiveWorkbenchWindow().getPartService();
		iGraphPartServiceListener = new GraphPartServiceListener();
		partService.addPartListener(iGraphPartServiceListener);
		PlatformUI.getWorkbench().addWindowListener(iGraphPartServiceListener);
		cppFileNames = new ArrayList<String>();
	}

	/**
	 * Draws the graph on the canvas. Intended to be called on a paint event.
	 * 
	 * @param graphics
	 */
	public void paint(final Graphics graphics) {
		if (optimisedDrawing && !userInformed) {
			userInformed = true;
			IPreferenceStore preferenceStore = Activator.getPreferences();
			if (!preferenceStore.getString(PROMPT_KEY).equals(
					MessageDialogWithToggle.ALWAYS)) {
				String dilaogTitle = "Optimised Graph";
				String message = "The process contains too many memory operations to display efficiently. To optimise, only leaks will be indicated on the graph.";
				String toggleMessage = "Don't show this again";

				MessageDialogWithToggle.openInformation(getShell(),
						dilaogTitle, message, toggleMessage, false,
						preferenceStore, PROMPT_KEY);
			}
		}
		Rectangle visibleRect = graphics.getClip(new Rectangle());
		YConverter yConverter = new YConverter(getClientArea().height, model
				.getHighestCumulatedMemoryAlloc());

		if (model.getSelectedProcess() != null
				&& model.getSelectedProcess().getAllocsFrees().size() > 0) {
			PointList pts = new PointList((model.getSelectedProcess()
					.getAllocsFrees().size() * 2) - 1);
			Point prevPt = null;
			List<Point> dotLocations = new ArrayList<Point>();
			List<Integer> colorDotLocations = new ArrayList<Integer>();

			for (BaseInfo info : model.getSelectedProcess().getAllocsFrees()) {
				int x_point = (int) ((info.getTime() - model
						.getFirstProcessTime()) / getScale());
				int y_point = yConverter.bytesToY(info.getTotalMem());

				if (y_point < 0) {
					y_point = 0;
				}
				if (prevPt != null) {
					pts.addPoint(x_point, prevPt.y);
				}
				Point nextPt = new Point(x_point, y_point);
				if (visibleRect.contains(nextPt)
						&& (isLeak(info) || (!optimisedDrawing
								&& !dotLocations.contains(nextPt) && validInThreshold(info)))) {
					// for improved performance, only draw dots that are in
					// visible clip area
					// and don't draw a dot if there is one already, unless it's
					// a leak
					dotLocations.add(nextPt);
					colorDotLocations.add(getColorForAllocType(info));
				}
				pts.addPoint(nextPt);
				prevPt = nextPt;
			}

			if (pts.size() > 0) {
				graphics.pushState();

				graphics.setForegroundColor(Display.getDefault()
						.getSystemColor(SWT.COLOR_DARK_YELLOW));
				graphics.setLineWidthFloat(optimisedDrawing ? 0.5f : 2.0f);
				graphics.drawPolyline(pts);

				graphics.setLineWidthFloat(optimisedDrawing ? 0.5f : 1.0f);
				graphics.setAntialias(SWT.ON);
				graphics.setForegroundColor(Display.getDefault()
						.getSystemColor(SWT.COLOR_RED));
				graphics.setBackgroundColor(Display.getDefault()
						.getSystemColor(SWT.COLOR_RED));
				int colourCode = SWT.COLOR_RED;
				for (int j = 0; j < dotLocations.size(); j++) {
					Point dotLocation = dotLocations.get(j);
					if (!optimisedDrawing
							&& colorDotLocations.get(j) != colourCode) {
						colourCode = colorDotLocations.get(j);
						graphics.setBackgroundColor(Display.getDefault()
								.getSystemColor(colourCode));
					}
					// paint the dot
					graphics.fillOval(dotLocation.x - 2, dotLocation.y - 2, 5,
							5);
					if (!optimisedDrawing && colourCode == SWT.COLOR_RED) {
						// draw a red line
						graphics.drawLine(dotLocation.x, dotLocation.y,
								dotLocation.x, yConverter.bytesToY(0));
					}
				}
				graphics.popState();
			}
		}
	}

	/**
	 * Returns true of the size of the alloc or free is greater or equals / less
	 * or equals the threshold. Returns true if no threshold is set.
	 * 
	 * @param info
	 *            the memory operation to check
	 * @return
	 */
	private boolean validInThreshold(BaseInfo info) {
		if (threshold <= 0
				|| info instanceof AllocInfo
				&& (aboveThreshold ? (((AllocInfo) info).getSizeInt() >= threshold)
						: (((AllocInfo) info).getSizeInt() <= threshold))) {
			return true;
		}

		if (info instanceof FreeInfo
				&& ((aboveThreshold && ((FreeInfo) info).getSizeInt() >= threshold) || (!aboveThreshold && ((FreeInfo) info)
						.getSizeInt() <= threshold))) {
			// check at least one of its alloc qualifies
			for (AllocInfo allocInfo : ((FreeInfo) info).getFreedAllocs()) {
				if ((aboveThreshold && allocInfo.getSizeInt() >= threshold)
						|| (!aboveThreshold && allocInfo.getSizeInt() <= threshold)) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Draws the background grid on the canvas. Intended to be called on a paint
	 * event.
	 * 
	 * @param graphics
	 */
	public void drawBackGroundLines(Graphics graphics) {
		Rectangle canvasRect = graphics
				.getClip(new org.eclipse.draw2d.geometry.Rectangle());
		graphics.setForegroundColor(ColorUtil.COLOR_100); // middle grey
		graphics.setBackgroundColor(ColorUtil.COLOR_170); // lighter grey

		int height = getClientArea().height;
		int width = getClientArea().width;

		graphics.fillRectangle(new Rectangle(canvasRect.x, 0, width, height
				- X_AXIS_HEIGHT));

		double visY = height - X_AXIS_HEIGHT;

		int k = 0;
		// horizontal lines, height is divided into 10 sections, line is dotted
		// (5 pixel length)
		for (float y = 0; k <= 10; y += visY * 10000 / 100001, k++) {
			for (int x = canvasRect.x; x <= canvasRect.x + canvasRect.width; x += 5) {
				if ((x / 5) % 2 == 0)
					graphics.drawLine(x, ((int) y) + 1, x + 5, ((int) y) + 1);
			}
		}

		graphics.setForegroundColor(ColorUtil.COLOR_100);
		graphics.setBackgroundColor(ColorUtil.WHITE);

		int alignedLeftEdge = (canvasRect.x / 50) * 50;

		// vertical lines (one darker, one lighter vertical line in turns every
		// 50 points in width)
		if (width > 0) {
			for (int x = alignedLeftEdge; x <= canvasRect.x + canvasRect.width; x += 50) {
				if (x % 100 == 0)
					graphics.setForegroundColor(ColorUtil.COLOR_100);
				else
					graphics.setForegroundColor(ColorUtil.COLOR_200);

				for (int y = 0; y < height; y += 5) {
					if ((y / 5) % 2 == 0)
						graphics.drawLine(x, y, x, y + 5);
				}
			}
		}

		graphics.setForegroundColor(ColorUtil.COLOR_100);
		graphics.setBackgroundColor(ColorUtil.WHITE);

		for (int x = alignedLeftEdge; x <= canvasRect.x + canvasRect.width; x += 50) {
			String timeStringWithUnits = GraphUtils.getTimeStringWithUnits(x
					* scale);
			graphics.drawString(timeStringWithUnits, x + 5, height - 13);
		}
		if (timeImage == null) {
			timeImage = GraphUtils.getVerticalLabel("Time");
		}
		graphics.drawImage(timeImage, width / 2, height - 30);
	}

	/**
	 * Returns the content of the tooltip appropriate for the given coordinate
	 * 
	 * @param x
	 *            the X-coordinate
	 * @param y
	 *            the Y-coordinate
	 * @return String containing the content of the tooltip
	 */
	public String getToolTipText(int x, int y) {
		StringBuilder text = new StringBuilder();
		if (model.getSelectedProcess() == null) {
			return "";
		}

		if (y > getClientArea().height - X_AXIS_HEIGHT) {
			return null;
		}

		double timeInMs = (x * getScale());// x value in milliseconds

		YConverter yConverter = new YConverter(getClientArea().height, model
				.getHighestCumulatedMemoryAlloc());
		double bytes = yConverter.yToBytes(y); // y value in bytes

		text.append(GraphUtils.renderTime(timeInMs));

		BaseInfo allocInfo = findClosestAlloc((int) timeInMs,
				(int) ((x - BOUNDARY_OFFSET) * getScale()),
				(int) ((x + BOUNDARY_OFFSET) * getScale()), (int) bytes,
				(int) yConverter.yToBytes(y + BOUNDARY_OFFSET),
				(int) yConverter.yToBytes(y - BOUNDARY_OFFSET));
		if (allocInfo != null) {
			text.append(String.format("%n%s: %,d B%nTotal: %,d B",
					getMemOpString(allocInfo),
					Math.abs(allocInfo.getSizeInt()), allocInfo.getTotalMem()));
		} else {
			text.append(", " + GraphUtils.formatBytes(bytes));
		}
		return text.toString();
	}

	/**
	 * Returns a string representation of the type of the given memory operation
	 * 
	 * @param allocInfo
	 *            the memory operation to use
	 * @return
	 */
	private String getMemOpString(BaseInfo allocInfo) {
		return allocInfo instanceof AllocInfo ? (((AllocInfo) allocInfo)
				.isFreed() ? "Alloc" : "Leak") : "Free";
	}

	/**
	 * Convenience method for
	 * {@link #findClosestAlloc(int, int, int, int, int, int)} wrapping
	 * conversion from x,y coordinates to time and byte values.
	 * 
	 * @param x
	 *            X-coordinate
	 * @param y
	 *            Y-coordinate
	 * @return closest BaseInfo within bounds, or null if none found
	 */
	private BaseInfo findClosest(int x, int y) {
		if (y > getClientArea().height - X_AXIS_HEIGHT) {
			return null;
		}

		YConverter yConverter = new YConverter(getClientArea().height, model
				.getHighestCumulatedMemoryAlloc());
		return findClosestAlloc((int) (x * getScale()),
				(int) ((x - BOUNDARY_OFFSET) * getScale()),
				(int) ((x + BOUNDARY_OFFSET) * getScale()), (int) (yConverter
						.yToBytes(y)), (int) yConverter.yToBytes(y
						+ BOUNDARY_OFFSET), (int) yConverter.yToBytes(y
						- BOUNDARY_OFFSET));
	}

	/**
	 * Finds the closest matching BaseInfo in the model. BaseInfo has to fit
	 * into the given bounds and be a better match than other BaseInfo in the
	 * same bounds.
	 * 
	 * @param timeInMsMidPoint
	 *            time in milliseconds for the exact point of interest
	 * @param timeInMsBoundLeft
	 *            Left boundary for time in milliseconds
	 * @param timeInMsBoundRight
	 *            Right boundary for time in milliseconds
	 * @param bytesMidPoint
	 *            Cumulative memory in bytes for the exact point of interest
	 * @param bytesBoundLeft
	 *            Left boundary for cumulative memory in bytes
	 * @param bytesBoundRight
	 *            Right boundary for cumulative memory in bytes
	 * @return
	 */
	private BaseInfo findClosestAlloc(int timeInMsMidPoint,
			int timeInMsBoundLeft, int timeInMsBoundRight, int bytesMidPoint,
			int bytesBoundLeft, int bytesBoundRight) {
		BaseInfo ret = null;
		if (model.getSelectedProcess() == null) {
			return ret;
		}

		int marginEnd = timeInMsBoundRight;
		ProcessInfo process = model.getSelectedProcess();
		AbstractList<BaseInfo> allocsFrees = process.getAllocsFrees();
		Long firstTime = model.getFirstProcessTime();

		for (BaseInfo info : allocsFrees) {
			Long infoRelativeTime = info.getTime() - firstTime;
			// check current alloc info is within given bounds
			if (infoRelativeTime >= timeInMsBoundLeft
					&& infoRelativeTime <= marginEnd
					&& info.getTotalMem() >= bytesBoundLeft
					&& info.getTotalMem() <= bytesBoundRight) {
				// check whether current alloc info is a better match than
				// previously found
				if (ret == null
						|| (Math.abs(infoRelativeTime - timeInMsMidPoint) < Math
								.abs((ret.getTime() - firstTime)
										- timeInMsMidPoint))
						|| ((ret.getTime() - firstTime) == infoRelativeTime && Math
								.abs(info.getTotalMem() - bytesMidPoint) < Math
								.abs(ret.getTotalMem() - bytesMidPoint))) {
					ret = info;
				}
			} else if ((info.getTime() - firstTime) > timeInMsBoundRight) {
				break;
			}
		}
		return ret;
	}

	/**
	 * Finds the next AllocInfo in the model.
	 * 
	 * @param allocInfo
	 *            AllocInfo prior to the one to return
	 * @return The next AllocInfo in the model. This may return null if the
	 *         passed object was the last in the model.
	 */
	private BaseInfo findNextAlloc(BaseInfo allocInfo, boolean forward) {
		BaseInfo ret = null;
		ProcessInfo processInfo = model.getSelectedProcess();
		if (processInfo == null) {
			return ret;
		}

		AbstractList<BaseInfo> allocsFrees = processInfo.getAllocsFrees();
		int i = allocsFrees.indexOf(allocInfo);
		if (forward) {
			if (i < allocsFrees.size() - 1) {
				ret = allocsFrees.get(i + 1);
			}
		} else {
			if (i > 0) {
				ret = allocsFrees.get(i - 1);
			}
		}
		return ret;
	}

	/**
	 * This method first zooms in graph to the maximum possible scale and zooms
	 * out so that it fits in the canvas area.
	 * 
	 */
	public void zoomGraph() {
		int width = getClientArea().width;

		if (width <= 0 || model == null)
			return;
		double new_scale = getMaxTimeValueInMilliSeconds() / width;
		setScale(new_scale);
		setZoomedSize(0);
	}

	/**
	 * Returns the current scaling factor for the graph's width
	 * 
	 * @return Scale
	 */
	public double getScale() {
		return scale;
	}

	/**
	 * Sets the scaling factor for the graph's width
	 * 
	 * @param newScale
	 */
	public void setScale(double newScale) {
		this.scale = newScale;
	}

	/**
	 * Returns the highest time value of the current graph in milliseconds
	 * 
	 * @return Last time
	 */
	public long getLastTimeValueInMilliSeconds() {
		return model.getLastProcessTime() - model.getFirstProcessTime();
	}

	/**
	 * Returns the last time value in the model plus 1 per cent.
	 * 
	 * @return
	 */
	private long getMaxTimeValueInMilliSeconds() {
		return getLastTimeValueInMilliSeconds()
				+ (int) (getLastTimeValueInMilliSeconds() * 0.01);
	}

	/**
	 * Adds a new model to this class
	 * 
	 * @param newModel
	 *            the IMemoryActivityModel to use
	 */
	public void setInput(IMemoryActivityModel newModel) {
		if (this.model != null) {
			this.model.removeListener(this);
		}
		threshold = 0; // reset threshold
		this.model = newModel;
		this.model.addListener(this);
	}

	/**
	 * Creates the content of the FigureCanvas. Intended to be called once in
	 * creating the ViewPart content.
	 */
	public void createContent() {
		mc = new MouseAndKeyController();
		Panel panel = new Panel() {
			@Override
			public void paint(Graphics graphics) {
				if (model != null) {
					drawBackGroundLines(graphics);
					AnalyzeToolGraph.this.paint(graphics);
					mc.render(graphics);
				} else {
					erase();
				}
			}
		};

		panel.setLayoutManager(new XYLayout());
		panel.addMouseMotionListener(mc);
		panel.addMouseListener(mc);
		panel.addKeyListener(mc);

		setContents(panel);
		panel.setFocusTraversable(true);
		final org.eclipse.swt.widgets.ScrollBar horizontalBar = getHorizontalBar();

		horizontalBar.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent arg0) {
				// do nothing by design
			}

			public void widgetSelected(SelectionEvent event) {
				AnalyzeToolGraph.this.redraw();
			}

		});

		addControlListener(new ControlAdapter() {
			@Override
			public void controlResized(ControlEvent e) {
				horizontalBar.setPageIncrement(getBounds().width);
				redraw();
			}
		});
	}

	/**
	 * Class containing code to deal with mouse and key events
	 * 
	 */
	private class MouseAndKeyController implements MouseMotionListener,
			MouseListener, KeyListener {

		protected int mouseButton;
		protected Point start;
		protected boolean beingDragged;
		private BaseInfo lastShownAlloc;
		protected Point lastMouse;

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.eclipse.draw2d.MouseMotionListener#mouseDragged(org.eclipse.draw2d
		 * .MouseEvent)
		 */
		public void mouseDragged(MouseEvent me) {
			if (mouseButton == 1) {// for some reason me.button doesn't work
				// here and always returns 0 so we need to
				// track it from mouse pressed
				beingDragged = true;
				lastMouse = me.getLocation();
			} else {
				beingDragged = false;
			}
		}

		public void render(Graphics graphics) {
			graphics.pushState();
			if (beingDragged) {
				graphics.setForegroundColor(Display.getDefault()
						.getSystemColor(SWT.COLOR_BLACK));
				graphics.setLineWidth(1);

				int dragEnd = lastMouse.x;
				int dragLeftX = start.x <= dragEnd ? start.x : dragEnd;// drag
				// area
				// left
				// edge
				int dragRightX = start.x <= dragEnd ? dragEnd : start.x;// drag
				// area
				// right
				// edge
				graphics.drawRectangle(new Rectangle(dragLeftX, 0,
						(dragRightX - dragLeftX), getClientArea().height));
			}

			if (lastShownAlloc != null) {

				// for the selected dot, try to mark related allocs and free
				FreeInfo freeInfo = null;
				if (lastShownAlloc instanceof FreeInfo) {
					freeInfo = (FreeInfo) lastShownAlloc;
				} else {
					freeInfo = ((AllocInfo) lastShownAlloc).getFreedBy();
				}
				if (freeInfo != null) {
					YConverter yConverter = new YConverter(
							getClientArea().height, model
									.getHighestCumulatedMemoryAlloc());
					Rectangle visibleRect = graphics
							.getClip(new org.eclipse.draw2d.geometry.Rectangle());
					Point pfree = getLocationOnGraph(yConverter, freeInfo);
					if (lastShownAlloc != freeInfo
							&& visibleRect.contains(pfree)) {
						graphics.setForegroundColor(Display.getDefault()
								.getSystemColor(SWT.COLOR_DARK_GREEN));
						graphics.drawLine(pfree.x + 1, pfree.y + 3,
								pfree.x + 1, yConverter.bytesToY(0));
						if (optimisedDrawing) { // draw circle as well since we
							// don't have dots
							graphics.drawOval(pfree.x - 2, pfree.y - 2, 5, 5);
						}
					}
					for (AllocInfo freedAlloc : freeInfo.getFreedAllocs()) {
						Point pAlloc = getLocationOnGraph(yConverter,
								freedAlloc);
						if (lastShownAlloc != freedAlloc
								&& visibleRect.contains(pAlloc)) {
							graphics.setForegroundColor(Display.getDefault()
									.getSystemColor(SWT.COLOR_DARK_BLUE));
							graphics.drawLine(pAlloc.x + 1, pAlloc.y + 3,
									pAlloc.x + 1, yConverter.bytesToY(0));
							if (optimisedDrawing) { // draw circle as well since
								// we don't have dots
								graphics.drawOval(pAlloc.x - 2, pAlloc.y - 2,
										5, 5);
							}
						}

					}
				}
				// this should be the alloc that has its details displayed
				// mark it in the graph
				// draw a small circle around the alloc / dealloc, and a
				// vertical line towards the X-axis
				Point p = getLocationOnGraph(lastShownAlloc);
				graphics.setForegroundColor(Display.getDefault()
						.getSystemColor(SWT.COLOR_CYAN));
				graphics.setLineWidth(1);
				graphics.drawOval(p.x - 2, p.y - 2, 5, 5);
				if (optimisedDrawing || !isLeak(lastShownAlloc)) {
					graphics.drawLine(p.x + 1, p.y + 3, p.x + 1,
							getClientArea().height - X_AXIS_HEIGHT);
				}
			}
			graphics.popState();
		}

		private Point getLocationOnGraph(BaseInfo alloc) {
			YConverter yConverter = new YConverter(getClientArea().height,
					model.getHighestCumulatedMemoryAlloc());
			return getLocationOnGraph(yConverter, alloc);
		}

		private Point getLocationOnGraph(YConverter yConverter, BaseInfo alloc) {
			int x = (int) ((alloc.getTime() - model.getFirstProcessTime()) / getScale());
			int y = yConverter.bytesToY(alloc.getTotalMem());

			if (y < 0) {
				y = 0;
			}
			return new Point(x, y);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.eclipse.draw2d.MouseMotionListener#mouseEntered(org.eclipse.draw2d
		 * .MouseEvent)
		 */
		public void mouseEntered(MouseEvent e) {
			// do nothing by design
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.eclipse.draw2d.MouseMotionListener#mouseExited(org.eclipse.draw2d
		 * .MouseEvent)
		 */
		public void mouseExited(MouseEvent e) {
			// do nothing by design
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.eclipse.draw2d.MouseMotionListener#mouseHover(org.eclipse.draw2d
		 * .MouseEvent)
		 */
		public void mouseHover(MouseEvent e) {
			if (model != null) {
				setToolTipText(getToolTipText(e.x, e.y));
				redraw();
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.eclipse.draw2d.MouseMotionListener#mouseMoved(org.eclipse.draw2d
		 * .MouseEvent)
		 */
		public void mouseMoved(MouseEvent e) {
			// do nothing by design
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.eclipse.draw2d.MouseListener#mouseDoubleClicked(org.eclipse.draw2d
		 * .MouseEvent)
		 */
		public void mouseDoubleClicked(MouseEvent e) {
			beingDragged = false;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.eclipse.draw2d.MouseListener#mousePressed(org.eclipse.draw2d.
		 * MouseEvent)
		 */
		public void mousePressed(MouseEvent e) {
			beingDragged = false;
			start = e.getLocation();
			mouseButton = e.button;
			e.consume(); // don't pass on to other listeners
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.eclipse.draw2d.MouseListener#mouseReleased(org.eclipse.draw2d
		 * .MouseEvent)
		 */
		public void mouseReleased(MouseEvent me) {

			if (beingDragged) {
				beingDragged = false;
				// finished drag - perform zoom in now

				// set new scale
				int dragEnd = me.getLocation().x;
				int dragLeftX = start.x <= dragEnd ? start.x : dragEnd;// drag
				// area
				// left
				// edge
				int dragRightX = start.x <= dragEnd ? dragEnd : start.x;// drag
				// area
				// right
				// edge
				int dragWindowStartTime = (int) (dragLeftX * getScale());

				int dragWidth = dragRightX - dragLeftX;// drag area width
				int oldVisibleWidth = getViewport().getSize().width;// visible
				// window
				// width
				double newScale = getScale() * dragWidth / oldVisibleWidth;
				if (newScale < 1.0) {
					// don't allow a scale smaller than 1 millisecond per pixel
					// TODO: indicate a failed zoom
					return;
				}

				int dragLeftXScaled = (int) (dragWindowStartTime / newScale);

				// set new window location
				setScale(newScale);
				setZoomedSize(dragLeftXScaled);

				// make sure the drag window disappears
				AnalyzeToolGraph.this.redraw();
			} else if (me.button == 3) {
				// mouse right click - zoom out

				double maxScale = getMaxTimeValueInMilliSeconds()
						/ getClientArea().width;
				if (getScale() < maxScale) {
					double newScale = GraphUtils.nextScale(getScale(), true);
					if (getScale() != newScale) {
						if (newScale > maxScale) {
							newScale = maxScale;
						}

						// get left window edge is ms to be set to after zooming
						double leftEdgeInMs = getViewport().getViewLocation().x
								* getScale();
						setScale(newScale);
						setZoomedSize((int) (leftEdgeInMs / newScale));
					}
				}
			} else if (me.button == 1) {
				BaseInfo info = findClosest(me.x, me.y);
				if (info != null) {
					iDotsSelecProv
							.setSelection(new StructuredSelection(
									new MemOpDescriptor(model
											.getFirstProcessTime(), info,
											iCurrentProject, iSymReader,
											cppFileNames, model
													.getCallstackManager())));
					lastShownAlloc = info;
				}
			}
		}

		public void keyPressed(KeyEvent ke) {
			// do nothing by design
		}

		public void keyReleased(KeyEvent ke) {
			if (ke.keycode == SWT.ARROW_RIGHT || ke.keycode == SWT.ARROW_LEFT) {
				if (lastShownAlloc != null) {
					BaseInfo info = findNextAlloc(lastShownAlloc,
							ke.keycode == SWT.ARROW_RIGHT);
					if (info != null) {
						iDotsSelecProv.setSelection(new StructuredSelection(
								new MemOpDescriptor(
										model.getFirstProcessTime(), info,
										iCurrentProject, iSymReader,
										cppFileNames, model
												.getCallstackManager())));
						lastShownAlloc = info;

						// if info is hidden from the visible graph area, scroll
						// to reveal
						int x = getLocationOnGraph(info).x;
						int leftEdge = getViewport().getHorizontalRangeModel()
								.getValue();
						int width = getViewport().getHorizontalRangeModel()
								.getExtent();
						if (x < leftEdge) {
							if (x > 10) {
								x -= 10;
							}
							getViewport().getHorizontalRangeModel().setValue(x);
						} else if (x > (leftEdge + width)) {
							x -= (width - 10);
							getViewport().getHorizontalRangeModel().setValue(x);
						}
						AnalyzeToolGraph.this.redraw();
					}
				}
			}
		}

		/**
		 * Resets any state.
		 */
		public void clearState() {
			mouseButton = 0;
			start = null;
			beingDragged = false;
			lastShownAlloc = null;
			lastMouse = null;
			if (iDotsSelecProv != null) {
				iDotsSelecProv.setSelection(StructuredSelection.EMPTY);
			}
		}
	}

	/**
	 * Returns an SWT system color code depending on the type of the paramter
	 * passed. </br> SWT.COLOR_DARK_BLUE for a alloc (that is not a leak) </br>
	 * SWT.COLOR_RED for a leak </br> SWT.COLOR_DARK_GREEN for a free
	 * 
	 * @param info
	 *            the allocation to evaluate
	 * @return the appropriate SWT system color code
	 */
	private int getColorForAllocType(BaseInfo info) {
		int color;
		if (info instanceof AllocInfo) {
			if (((AllocInfo) info).isFreed()) {
				color = SWT.COLOR_DARK_BLUE;
			} else {
				color = SWT.COLOR_RED;
			}
		} else {
			color = SWT.COLOR_DARK_GREEN;
		}
		return color;
	}

	/**
	 * Returns true if the passed info is a leak, false otherwise. AllocInfo
	 * that haven't been freed are considered leaks.
	 * 
	 * @param info
	 *            the BaseInfo to test
	 * @return true if leak, false otherwise
	 */
	private static boolean isLeak(BaseInfo info) {
		return info instanceof AllocInfo && !(((AllocInfo) info).isFreed()) ? true
				: false;
	}

	/**
	 * 
	 * Converts bytes from/to Y values
	 * 
	 */
	class YConverter {
		private final double visY;
		private final double multiplier;

		public YConverter(int clientAreaHeight, int maxAllocValueInBytes) {
			visY = clientAreaHeight - X_AXIS_HEIGHT;
			multiplier = GraphUtils.prettyMaxBytes(maxAllocValueInBytes) / visY;
		}

		public int bytesToY(int bytes) {
			return (int) (visY - (bytes / multiplier));
		}

		public double yToBytes(int y) {
			return (visY - y) * multiplier;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.nokia.s60tools.analyzetool.engine.IMemoryActivityModelChangeListener
	 * #onProcessesAdded()
	 */
	public void onProcessesAdded() {
		// the model is now ready to use - call a redraw on the graph
		PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
			public void run() {
				mc.clearState();
				zoomGraph();
				redraw();
			}
		});
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.nokia.s60tools.analyzetool.engine.IMemoryActivityModelChangeListener
	 * #onProcessSelected
	 * (com.nokia.s60tools.analyzetool.engine.statistic.ProcessInfo)
	 */
	public void onProcessSelected(ProcessInfo p) {
		PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
			public void run() {
				mc.clearState();
				optimisedDrawing = model.getSelectedProcess().getAllocsFrees()
						.size() > ChartContainer.OPT_DRAWING_LIMIT ? true
						: false;
				userInformed = false;
				zoomGraph();
				redraw();
			}
		});
	}

	@Override
	public void dispose() {
		// there is no dispose() entry point in this class; we may have to call
		// this via MainView.dispose()
		site.setSelectionProvider(null);
		iDotsSelecProv = null;
		if (timeImage != null) {
			timeImage.dispose();
		}
		IPartService partService = PlatformUI.getWorkbench()
				.getActiveWorkbenchWindow().getPartService();
		partService.removePartListener(iGraphPartServiceListener);
		iGraphPartServiceListener = null;
		if (iSymReader != null) {
			iSymReader.dispose();
			iSymReader = null;
		}
	}

	private void setZoomedSize(final int newXLocation) {
		int prefSize = (int) (getMaxTimeValueInMilliSeconds() / getScale());

		// new width has to propagate to viewport first before we can set
		// the new viewport selection
		// set newXLocation in listener
		Panel panel = (Panel) (getContents());
		panel.setPreferredSize(prefSize, 0);

		if (prefSize > getClientArea().width) {
			getViewport().getHorizontalRangeModel().addPropertyChangeListener(
					new PropertyChangeListener() {
						public void propertyChange(PropertyChangeEvent e) {
							if (e.getPropertyName().equals(PROP_MAXIMUM)) {
								getViewport().getHorizontalRangeModel()
										.removePropertyChangeListener(this);
								getViewport().getHorizontalRangeModel()
										.setValue(newXLocation);
							}
						}

					});
			panel.setSize(prefSize, 0);

		} else {
			// this only works if the canvas is large enough, otherwise use
			// property listener
			getViewport().getHorizontalRangeModel().setValue(newXLocation);
		}
	}

	/**
	 * DotSelectionProvider : when a user selects a Dot on the graph, this class
	 * delivers the associated descriptor MemOpDescriptor to the views
	 * interested in it mainly the Properties View.
	 * 
	 */
	private class DotSelectionProvider implements ISelectionProvider,
			SelectionListener {
		private ListenerList iSelectionChangedListeners = new ListenerList();

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.eclipse.jface.viewers.ISelectionProvider#addSelectionChangedListener
		 * (org.eclipse.jface.viewers.ISelectionChangedListener)
		 */
		public void addSelectionChangedListener(
				ISelectionChangedListener listener) {
			// TODO is there a way to allow only properties view to register.
			iSelectionChangedListeners.add(listener);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.eclipse.jface.viewers.ISelectionProvider#getSelection()
		 */
		public ISelection getSelection() {
			return iCurrentSelectedDot;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @seeorg.eclipse.jface.viewers.ISelectionProvider#
		 * removeSelectionChangedListener
		 * (org.eclipse.jface.viewers.ISelectionChangedListener)
		 */
		public void removeSelectionChangedListener(
				ISelectionChangedListener listener) {
			iSelectionChangedListeners.remove(listener);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.eclipse.jface.viewers.ISelectionProvider#setSelection(org.eclipse
		 * .jface.viewers.ISelection)
		 */
		public void setSelection(ISelection selection) {
			iCurrentSelectedDot = selection;
			// notify the listeners mainly the property view
			for (final Object listenerObj : iSelectionChangedListeners
					.getListeners()) {
				((ISelectionChangedListener) listenerObj)
						.selectionChanged(new SelectionChangedEvent(this,
								getSelection()));
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org
		 * .eclipse.swt.events.SelectionEvent)
		 */
		public void widgetDefaultSelected(SelectionEvent event) {
			widgetSelected(event);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse
		 * .swt.events.SelectionEvent)
		 */
		public void widgetSelected(SelectionEvent arg0) {
			for (final Object listenerObj : iSelectionChangedListeners
					.getListeners()) {
				((ISelectionChangedListener) listenerObj)
						.selectionChanged(new SelectionChangedEvent(this,
								getSelection()));
			}
		}
	}

	/**
	 * GraphPartServiceListener is for convenience only. It serves to find an
	 * appropriate time to add DotSelectionProvider to the part site. It also
	 * covers that case where the properties View is already open before opening
	 * AnalyzeTool View.
	 * 
	 */
	private class GraphPartServiceListener implements IPartListener,
			IWindowListener {

		public void partActivated(IWorkbenchPart part) {
			if (part instanceof MainView || part instanceof PropertySheet) {

				site = part.getSite();
				// set property sheet selection provider.
				site.setSelectionProvider(iDotsSelecProv);
				if (part instanceof MainView && iSymReader != null) {
					iSymReader.reOpenCachedSymbolFiles();
				}
			}
		}

		public void partBroughtToTop(IWorkbenchPart part) {
			// do nothing
		}

		public void partClosed(IWorkbenchPart part) {
			// TODO this is not working. why?
			if (part instanceof MainView || part instanceof PropertySheet) {
				site = part.getSite();
				iDotsSelecProv.setSelection(StructuredSelection.EMPTY);
				// set property sheet selection provider.
				site.setSelectionProvider(null);
			}
		}

		public void partDeactivated(IWorkbenchPart part) {
			// the user might want to rebuild the project, so close the sumbol
			// files
			if (part instanceof MainView && iSymReader != null) {
				iSymReader.closeCachedSymbolFiles();
			}
		}

		public void partOpened(IWorkbenchPart part) {
			if (part instanceof MainView) {
				try {
					part.getSite().getPage().showView(
							"org.eclipse.ui.views.PropertySheet");//$NON-NLS-1$
				} catch (PartInitException e) {
					// just log the exception
					Activator.getDefault()
							.log(IStatus.ERROR, e.getMessage(), e);
				}
			}
		}

		public void windowActivated(IWorkbenchWindow window) {
			if (iSymReader != null
					&& window.getActivePage().getActivePart() instanceof MainView) {
				iSymReader.reOpenCachedSymbolFiles();
			}
		}

		public void windowClosed(IWorkbenchWindow window) {
			// do nothing

		}

		public void windowDeactivated(IWorkbenchWindow window) {
			// the user might do a re-build from the command line
			if (iSymReader != null) {
				iSymReader.closeCachedSymbolFiles();
			}
		}

		public void windowOpened(IWorkbenchWindow window) {
			// do nothing
		}
	}

	/**
	 * set a new project
	 * 
	 * @param aProject
	 */
	public void setProject(IProject aProject) {
		if (iCurrentProject != aProject) {
			iCurrentProject = aProject;
			iSymReader = new SymReader(aProject);
			iSymReader.loadProjectTargetsInfo();
			ResourceVisitor visitor = new ResourceVisitor(this);
			try {
				iCurrentProject.accept(visitor);
			} catch (CoreException ce) {
				// just log the exception
				Activator.getDefault().log(IStatus.ERROR, ce.getMessage(), ce);
			}
		}
	}

	/**
	 * Load all cpp files from the project. This is callback to
	 * ResourcceVisitor.
	 * 
	 * @param resource
	 */
	public final void loadFileInfo(IResource resource) {
		// get all the cpp file info which are belongs to current project
		String cppFileName = Util.getCPPFileNameAndPath(resource);

		// if cpp file found, save it
		if (cppFileName != null && !cppFileNames.contains(cppFileName)) {
			this.cppFileNames.add(cppFileName);
		}
	}

	/**
	 * Sets the threshold. Only memory operations of a size greater or equals /
	 * lower or equals the threshold will be drawn on the graph.
	 * 
	 * @param value
	 *            the threshold value in bytes
	 * @param above
	 *            true if filtering "above", false if "below" the threshold
	 */
	public void setThreshold(long value, boolean above) {
		if (value < 0) {
			throw new IllegalArgumentException(
					"The threshold cannot be less than 0");
		}
		threshold = value;
		aboveThreshold = above;
	}
}