net.sf.versiontree.ui.TreeView.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.versiontree.ui.TreeView.java

Source

/*******************************************************************************
 * Copyright (c) 2003 Jan Karstens, Andr Langhorst.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 *
 * Contributors:
 *     Jan Karstens <jan.karstens@web.de> - initial implementation
 *     Andr Langhorst <andre@masse.de> - extensions
 *******************************************************************************/
package net.sf.versiontree.ui;

import java.util.List;

import net.sf.versiontree.VersionTreePlugin;
import net.sf.versiontree.data.IBranch;
import net.sf.versiontree.data.IRevision;
import net.sf.versiontree.data.ITreeElement;
import net.sf.versiontree.data.MergePoint;
import net.sf.versiontree.layout.drawer.IDrawMethod;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;

/**
 * @author Jan
 *
 * This class is a scrollable composite that displays the branches and
 * revisions. The Branches and Revisions to draw are delivered via a
 * callback function.
 */
public class TreeView extends ScrolledComposite implements MouseListener, IDrawMethod {
    /**
     * The display configuration for this view
     */
    private TreeViewConfig treeViewConfig;

    /**
     * the revision we are currently working on in the workspace.
     */
    private Revision currentRevision;

    /**
     * Composite that is the content of this scrollable composite.
     */
    private ConnectArrows connectors = null;

    /**
     * Listener that get notified on selection changes.
     */
    private LogEntrySelectionListener logSelectionListener;

    /**
     * Manages all selection events.
     */
    TreeSelectionManager selectionManager = new TreeSelectionManager();

    /**
     * Preferences for revision and branch size/spacing.
     */
    private int hspacing;
    private int vspacing;
    private int height;
    private int width;

    private static final int BORDER = 5;

    /**
     * Creates a new TreeView Component.
     * @param parent Parent composite.
     * @param style SWT style for this component.
     * @param listener listener for log entry selections.
     */
    public TreeView(Composite parent, int style, LogEntrySelectionListener listener) {
        super(parent, style);
        logSelectionListener = listener;
        treeViewConfig = new TreeViewConfig();
        reloadPrefrences();
        initViewer();
    }

    @Override
    public void setMenu(Menu menu) {
        connectors.setMenu(menu);
        super.setMenu(menu);
    }

    /**
     * Initializes the TreeView widget.
     */
    public void initViewer() {
        connectors = new ConnectArrows(this, SWT.NONE);
        connectors.setLayout(null);
        this.setContent(connectors);
        this.getVerticalBar().setIncrement(width);
        this.getHorizontalBar().setIncrement(height);
    }

    /**
     * Removes all Components from the view.
     */
    public void clear() {
        selectionManager.clearSelection();
        currentRevision = null;
        removeAllWidgets();
        connectors.clearConnectors();
        connectors.redraw();
    }

    /**
     * Implementation of callback from layout algorithm. This method is called once for each
     * revision or branch marker to be added to the view.
     * @see net.sf.versiontree.layout.drawer.IDrawMethod#draw(net.sf.versiontree.data.ITreeElement, int, int)
     */
    public void draw(ITreeElement element, int x, int y) {
        if (element instanceof IRevision) {
            Revision revision = new Revision(connectors, 0);
            revision.setRevisionData((IRevision) element);
            revision.addMouseListener(this);
            revision.setMenu(this.getMenu());
            revision.setLocation(BORDER + x * (width + hspacing), BORDER + y * (height + vspacing));
            revision.setSize(revision.computeSize(SWT.DEFAULT, SWT.DEFAULT));
            // check if this is the current revision
            if ((revision.getRevisionData().getState() & ITreeElement.STATE_CURRENT) != 0) {
                currentRevision = revision;
                selectionManager.revisionSelected(currentRevision.getRevisionData(), 1);
                logSelectionListener.logEntrySelected(currentRevision.getRevisionData().getLogEntry());
            }
        } else {
            Branch branch = new Branch(connectors, 0);
            branch.setBranchData(((IBranch) element));
            branch.addMouseListener(this);
            branch.setLocation(BORDER + x * (width + hspacing), BORDER + y * (height + vspacing));
            branch.setSize(branch.computeSize(SWT.DEFAULT, SWT.DEFAULT));
        }
    }

    private void createConnector(ITreeElement from, ITreeElement to, int connectorType) {
        int xPos1 = from.getX();
        int yPos1 = from.getY();
        int xPos2 = to.getX();
        int yPos2 = to.getY();
        Point begin = new Point(0, 0);
        Point end = new Point(0, 0);
        int mode = Connector.HORIZONTAL;
        // connect horizontal if x offset is equal
        int sy1 = height / 2, sy2 = height / 2;
        if (yPos2 > yPos1) {
            sy1 = height;
            sy2 = 0;
        } else if (yPos2 < yPos1) {
            sy1 = 0;
            sy2 = height;
        }
        int sx1 = width / 2, sx2 = width / 2;
        if (xPos2 > xPos1) {
            sx1 = width;
            sx2 = 0;
        }
        if (xPos2 < xPos1) {
            sx1 = 0;
            sx2 = width;
        }
        begin.x = BORDER + xPos1 * (hspacing + width) + sx1;
        begin.y = BORDER + yPos1 * (vspacing + height) + sy1;
        end.x = BORDER + xPos2 * (hspacing + width) + sx2;
        end.y = BORDER + yPos2 * (vspacing + height) + sy2;

        int dX = end.x - begin.x;
        int dY = end.y - begin.y;
        int arrowLength = (int) Math.sqrt((dX * dX) + (dY * dY));
        if (arrowLength > 0) {
            if (mode == Connector.RIGHT) {
                ConnectArrow arrow = new ConnectArrow(begin, end);
                connectors.addConnectArrow(arrow);
            } else {
                ConnectArrow arrow = new ConnectArrow(end, begin);
                connectors.addConnectArrow(arrow);
            }
        } else {
            VersionTreePlugin.log(IStatus.ERROR, "Attempt to draw a line between the same begin and end points.");
            return;
        }
    }

    /**
     * Computes the size of the view. This function needs to be called after
     * adding new components to the view.
     */
    public void show() {
        // resize content widget
        Point size = connectors.computeSize(SWT.DEFAULT, SWT.DEFAULT);
        size.x += BORDER;
        size.y += BORDER;
        connectors.setSize(size);
        // scroll to current revision
        if (currentRevision != null) {
            scrollToRevision(currentRevision);
        }
    }

    /**
     * Scrolls the content so that the given revision is visible.
     * @param revision the revision to scroll the client area to.
     */
    private void scrollToRevision(Revision revision) {
        Rectangle revBounds = revision.getBounds();
        Rectangle clientArea = getClientArea();
        clientArea.x = getOrigin().x;
        clientArea.y = getOrigin().y;
        if (!clientArea.contains(revBounds.x, revBounds.y)
                && !clientArea.contains(revBounds.x + revBounds.width, revBounds.y + revBounds.height)) {
            setOrigin(revBounds.x, revBounds.y);
        }
    }

    /**
     * Removes all widgets from the content pane.
     */
    private void removeAllWidgets() {
        Control[] childs = connectors.getChildren();
        for (Control control : childs) {
            control.setMenu(null);
            control.dispose();
        }
        reloadPrefrences();
    }

    /**
     * Reloads some layout relevant preferences from the PreferenceStore
     * to include any preference changes made in between.
     */
    private void reloadPrefrences() {
        IPreferenceStore prefs = VersionTreePlugin.getDefault().getPreferenceStore();
        hspacing = prefs.getInt(VersionTreePlugin.PREF_HSPACING);
        vspacing = prefs.getInt(VersionTreePlugin.PREF_VSPACING);
        height = prefs.getInt(VersionTreePlugin.PREF_ELEMENT_HEIGHT);
        width = prefs.getInt(VersionTreePlugin.PREF_ELEMENT_WIDTH);
    }

    /**
     * Returns the elements currently selected.
     * @return The elements currently selected.
     */
    public IStructuredSelection getSelection() {
        return selectionManager.getStructuredSelection();
    }

    /**
     * Forwards double clicks to the log entry listener.
     */
    public void mouseDoubleClick(MouseEvent e) {
        if (e.getSource() instanceof Revision && e.button == 1) {
            logSelectionListener.logEntryDoubleClicked(selectionManager.getStructuredSelection());
        }
    }

    /**
     * Listener for all components on the view. Checks for left clicks
     * on Revisions, changes the selection and notifies the LogEntrySelectionListener.
     */
    public void mouseDown(MouseEvent e) {
        if (e.getSource() instanceof Revision) {
            Revision selected = (Revision) e.getSource();
            // exit if user wants to open contex menu on
            // a already selected revision
            if (e.button == 3 && selected.isSelected()) {
                return;
            }
            selectionManager.revisionSelected(selected.getRevisionData(), e.stateMask);
            redrawAll();
            // notify listener
            if (selected.isSelected()) {
                logSelectionListener.logEntrySelected(selected.getRevisionData().getLogEntry());
            }
        } else if (e.getSource() instanceof Branch) {
            Branch selected = (Branch) e.getSource();
            List<IRevision> revisions = selected.getBranchData().getRevisions();
            if (revisions.size() > 0) {
                selectionManager.branchSelected(revisions, e.stateMask);
                redrawAll();
            }
        }
    }

    /**
     * Redraws all childs.
     */
    private void redrawAll() {
        Control[] childs = connectors.getChildren();
        for (Control child : childs) {
            child.redraw();
        }
    }

    public void mouseUp(MouseEvent e) {
    }

    /**
     * Returns the SelectionProvider for the TreeView.
     * @return
     */
    public ISelectionProvider getSelectionProvider() {
        return selectionManager;
    }

    /**
     * @return
     */
    public TreeViewConfig getTreeViewConfig() {
        return treeViewConfig;
    }

    public void drawConnectors(ITreeElement node) {
        for (ITreeElement child : node.getChildren()) {
            if (child instanceof IRevision) {
                createConnector(node, child, MergePoint.INITIAL);
                for (MergePoint mergeToPoint : ((IRevision) child).getMergeToRevisions()) {
                    IRevision mergeToRevision = mergeToPoint.getMergeRevision();
                    for (String brTo : mergeToRevision.getBranchTags()) {
                        if (isBranchVisible(brTo)) {
                            createConnector(child, mergeToRevision, MergePoint.MERGE);
                            break;
                        }
                    }
                }
                drawConnectors(child);
            } else if (child instanceof IBranch) {
                IBranch childBranch = (IBranch) child;
                if ((!childBranch.isEmpty() || this.getTreeViewConfig().drawEmptyBranches())
                        && isBranchVisible(childBranch.getName())) {
                    // case when parent is dead revision and child element is branch
                    if (!(node instanceof IRevision && ((IRevision) node).getLogEntry().isDeletion())) {
                        createConnector(node, child, MergePoint.INITIAL);
                    }
                    drawConnectors(child);
                }
            }
        }

    }

    private boolean isBranchVisible(String branchName) {
        return (!branchName.equals(IBranch.N_A_BRANCH) || this.getTreeViewConfig().drawNABranches())
                && (treeViewConfig.isBranchFilterPenetrated(branchName));
    }

}