com.nokia.s60tools.analyzetool.internal.ui.graph.AnalyzeToolGraph.java Source code

Java tutorial

Introduction

Here is the source code for com.nokia.s60tools.analyzetool.internal.ui.graph.AnalyzeToolGraph.java

Source

/*
 * 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;
    }
}