sysperfana/analyzetoolext/com.nokia.s60tools.analyzetool/src/com/nokia/s60tools/analyzetool/internal/ui/graph/AnalyzeToolGraph.java
changeset 1 1050670c6980
child 6 f65f740e69f9
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sysperfana/analyzetoolext/com.nokia.s60tools.analyzetool/src/com/nokia/s60tools/analyzetool/internal/ui/graph/AnalyzeToolGraph.java	Thu Feb 11 15:22:14 2010 +0200
@@ -0,0 +1,1123 @@
+/*
+ * Copyright (c) 2008-2009 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 synchronisation 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;
+
+
+	/**
+	 * 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 org.eclipse.draw2d.geometry.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)))){
+					//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();
+			}
+		}
+	}
+	
+	
+
+	/**
+	 * 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 = ((int)(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
+	 */
+	public double getScale() {
+		return scale;
+	}
+
+	/**
+	 * Sets the scaling factor for the graph's width
+	 * @param scale
+	 */
+	public void setScale(double newScale) {
+		this.scale = newScale;
+	}
+
+	/**
+	 * Returns the highest time value of the current graph in milliseconds
+	 * @return
+	 */
+	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 model the IMemoryActivityModel to use
+	 */
+	public void setInput(IMemoryActivityModel newModel) {
+		if (this.model != null){
+			this.model.removeListener(this);
+		}
+		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) {
+
+			}
+
+			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) {
+		}
+
+		/*
+		 * (non-Javadoc)
+		 * 
+		 * @see
+		 * org.eclipse.draw2d.MouseMotionListener#mouseExited(org.eclipse.draw2d
+		 * .MouseEvent)
+		 */
+		public void mouseExited(MouseEvent e) {
+		}
+
+		/*
+		 * (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) {
+		}
+
+		/*
+		 * (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)));
+					lastShownAlloc = info;
+				}
+				}
+
+			}
+
+
+		public void keyPressed(KeyEvent ke) {
+		}
+
+		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)));
+						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() > 25000 ? 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)
+		 * @see org.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);
+		}
+	}
+
+}