org.iplantc.phyloviewer.viewer.client.Phyloviewer.java Source code

Java tutorial

Introduction

Here is the source code for org.iplantc.phyloviewer.viewer.client.Phyloviewer.java

Source

/**
 * Copyright (c) 2009, iPlant Collaborative, Texas Advanced Computing Center This software is licensed
 * under the CC-GNU GPL version 2.0 or later. License: http://creativecommons.org/licenses/GPL/2.0/
 */

package org.iplantc.phyloviewer.viewer.client;

import java.util.ArrayList;
import java.util.List;

import org.iplantc.phyloviewer.client.tree.viewer.render.svg.SVGGraphics;
import org.iplantc.phyloviewer.shared.math.Box2D;
import org.iplantc.phyloviewer.shared.model.IDocument;
import org.iplantc.phyloviewer.shared.model.ITree;
import org.iplantc.phyloviewer.shared.render.RenderPreferences;
import org.iplantc.phyloviewer.shared.render.style.BranchStyle;
import org.iplantc.phyloviewer.shared.render.style.GlyphStyle;
import org.iplantc.phyloviewer.shared.render.style.IStyleMap;
import org.iplantc.phyloviewer.shared.render.style.LabelStyle;
import org.iplantc.phyloviewer.shared.render.style.NodeStyle;
import org.iplantc.phyloviewer.shared.render.style.Style;
import org.iplantc.phyloviewer.viewer.client.TreeWidget.ViewType;
import org.iplantc.phyloviewer.viewer.client.model.RemoteTree;
import org.iplantc.phyloviewer.viewer.client.services.CombinedService;
import org.iplantc.phyloviewer.viewer.client.services.CombinedService.LayoutResponse;
import org.iplantc.phyloviewer.viewer.client.services.CombinedService.NodeResponse;
import org.iplantc.phyloviewer.viewer.client.services.CombinedServiceAsync;
import org.iplantc.phyloviewer.viewer.client.services.CombinedServiceAsyncImpl;
import org.iplantc.phyloviewer.viewer.client.services.SearchServiceAsyncImpl;
import org.iplantc.phyloviewer.viewer.client.services.SearchServiceAsyncImpl.RemoteNodeSuggestion;
import org.iplantc.phyloviewer.viewer.client.services.StyleServiceClient;
import org.iplantc.phyloviewer.viewer.client.services.StyleServiceClient.StyleServiceException;
import org.iplantc.phyloviewer.viewer.client.services.TreeListService;
import org.iplantc.phyloviewer.viewer.client.services.TreeListServiceAsync;
import org.iplantc.phyloviewer.viewer.client.ui.BranchStyleWidget;
import org.iplantc.phyloviewer.viewer.client.ui.ColorBox;
import org.iplantc.phyloviewer.viewer.client.ui.ContextMenu;
import org.iplantc.phyloviewer.viewer.client.ui.GlyphStyleWidget;
import org.iplantc.phyloviewer.viewer.client.ui.LabelStyleWidget;
import org.iplantc.phyloviewer.viewer.client.ui.NodeStyleWidget;
import org.iplantc.phyloviewer.viewer.client.ui.NodeTable;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
import com.google.gwt.user.client.ui.VerticalPanel;

/**
 * The EntryPoint class for phyloviewer.  Builds the user interface and sets up remote services.
 */
public class Phyloviewer implements EntryPoint {

    TreeWidget widget;

    List<ITree> trees;

    CombinedServiceAsync combinedService = new CombinedServiceAsyncImpl();
    SearchServiceAsyncImpl searchService = new SearchServiceAsyncImpl();
    TreeListServiceAsync treeList = GWT.create(TreeListService.class);

    EventBus eventBus = new SimpleEventBus();

    String layoutType = "LAYOUT_TYPE_CLADOGRAM"; //determines whether we fetch layouts with branch lengths (LAYOUT_TYPE_PHYLOGRAM) or without (LAYOUT_TYPE_CLADOGRAM).  TODO make this an Enum in the shared package.

    /**
     * This is the entry point method.
     */
    public void onModuleLoad() {

        widget = new TreeWidget(searchService, eventBus);

        Style highlight = new Style("highlight");
        highlight.setNodeStyle(new NodeStyle("#C2C2F5", Double.NaN, null));
        highlight.setLabelStyle(new LabelStyle(null));
        highlight.setGlyphStyle(new GlyphStyle(null, "#C2C2F5", Double.NaN));
        highlight.setBranchStyle(new BranchStyle("#C2C2F5", Double.NaN));

        RenderPreferences rp = new RenderPreferences();
        rp.setHighlightStyle(highlight);
        widget.setRenderPreferences(rp);

        MenuBar fileMenu = new MenuBar(true);
        fileMenu.addItem("Open...", new Command() {

            @Override
            public void execute() {
                Phyloviewer.this.displayTrees();
            }

        });

        Command openURL = new Command() {
            @Override
            public void execute() {
                Window.open(widget.exportImageURL(), "_blank", "");
            }
        };

        fileMenu.addItem("Get image (opens in a popup window)", openURL);

        Command showSVG = new Command() {
            @Override
            public void execute() {
                String svg = getSVG();
                String url = "data:image/svg+xml;charset=utf-8," + svg;
                Window.open(url, "_blank", "");
            }
        };
        fileMenu.addItem("Export to SVG", showSVG);

        MenuBar layoutMenu = new MenuBar(true);
        layoutMenu.addItem("Rectangular Cladogram", new Command() {
            @Override
            public void execute() {
                ITree tree = widget.getDocument().getTree();
                byte[] treeID = ((RemoteTree) tree).getHash();
                widget.setDocument(null);
                widget.setViewType(TreeWidget.ViewType.VIEW_TYPE_CLADOGRAM);
                layoutType = "LAYOUT_TYPE_CLADOGRAM";

                loadTree(null, treeID, layoutType);
            }
        });
        layoutMenu.addItem("Rectangular Phylogram", new Command() {
            @Override
            public void execute() {
                ITree tree = widget.getDocument().getTree();
                byte[] treeID = ((RemoteTree) tree).getHash();
                widget.setDocument(null);
                widget.setViewType(TreeWidget.ViewType.VIEW_TYPE_CLADOGRAM);
                layoutType = "LAYOUT_TYPE_PHYLOGRAM";

                loadTree(null, treeID, layoutType);
            }
        });
        layoutMenu.addItem("Circular", new Command() {
            @Override
            public void execute() {
                ITree tree = widget.getDocument().getTree();
                byte[] treeID = ((RemoteTree) tree).getHash();
                widget.setDocument(null);
                widget.setViewType(TreeWidget.ViewType.VIEW_TYPE_RADIAL);
                layoutType = "LAYOUT_TYPE_CLADOGRAM";

                loadTree(null, treeID, layoutType);
            }
        });

        MenuBar styleMenu = new MenuBar(true);
        final TextInputPopup styleTextPopup = new TextInputPopup();
        styleTextPopup.addValueChangeHandler(new ValueChangeHandler<String>() {
            @Override
            public void onValueChange(ValueChangeEvent<String> event) {
                String style = event.getValue();

                try {
                    IStyleMap styleMap = StyleServiceClient.parseJSON(style);
                    widget.getDocument().setStyleMap(styleMap);
                    widget.render();
                } catch (StyleServiceException e) {
                    Window.alert(
                            "Unable to parse style document. See https://pods.iplantcollaborative.org/wiki/display/iptol/Using+Phyloviewer+GWT+client+library#UsingPhyloviewerGWTclientlibrary-Addingstylingmarkuptoviewer for help.");
                }
            }
        });

        styleTextPopup.setModal(true);

        styleMenu.addItem("Import Tree Styling", new Command() {
            @Override
            public void execute() {
                styleTextPopup.setPopupPositionAndShow(new PopupPanel.PositionCallback() {
                    @Override
                    public void setPosition(int offsetWidth, int offsetHeight) {
                        int left = (Window.getClientWidth() - offsetWidth) / 3;
                        int top = (Window.getClientHeight() - offsetHeight) / 3;
                        styleTextPopup.setPopupPosition(left, top);
                    }
                });
            }
        });

        // Make a search box
        final SuggestBox searchBox = new SuggestBox(searchService);
        searchBox.setLimit(10); // TODO make scrollable?
        searchBox.addSelectionHandler(new SelectionHandler<Suggestion>() {
            @Override
            public void onSelection(SelectionEvent<Suggestion> event) {
                Box2D box = ((RemoteNodeSuggestion) event.getSelectedItem()).getResult().layout.boundingBox;
                widget.show(box);
            }
        });

        // create some styling widgets for the context menu
        NodeStyleWidget nodeStyleWidget = new NodeStyleWidget(widget.getDocument());
        BranchStyleWidget branchStyleWidget = new BranchStyleWidget(widget.getDocument());
        GlyphStyleWidget glyphStyleWidget = new GlyphStyleWidget(widget.getDocument());
        LabelStyleWidget labelStyleWidget = new LabelStyleWidget(widget.getDocument());

        // replace their default TextBoxes with ColorBoxes, which jscolor.js will add a color picker to
        nodeStyleWidget.setColorWidget(createColorBox());
        branchStyleWidget.setStrokeColorWidget(createColorBox());
        glyphStyleWidget.setStrokeColorWidget(createColorBox());
        glyphStyleWidget.setFillColorWidget(createColorBox());
        labelStyleWidget.setColorWidget(createColorBox());

        // add the widgets to separate panels on the context menu
        final ContextMenu contextMenuPanel = new ContextMenu(widget);
        contextMenuPanel.add(new NodeTable(), "Node details", 3);
        contextMenuPanel.add(nodeStyleWidget, "Node", 3);
        contextMenuPanel.add(branchStyleWidget, "Branch", 3);
        contextMenuPanel.add(glyphStyleWidget, "Glyph", 3);
        contextMenuPanel.add(labelStyleWidget, "Label", 3);

        HorizontalPanel searchPanel = new HorizontalPanel();
        searchPanel.add(new Label("Search:"));
        searchPanel.add(searchBox);

        // Make the UI.
        MenuBar menu = new MenuBar();
        final DockLayoutPanel mainPanel = new DockLayoutPanel(Unit.EM);
        mainPanel.addNorth(menu, 2);
        mainPanel.addSouth(searchPanel, 2);
        mainPanel.addWest(contextMenuPanel, 0);
        mainPanel.add(widget);
        RootLayoutPanel.get().add(mainPanel);

        MenuBar viewMenu = new MenuBar(true);
        viewMenu.addItem("Layout", layoutMenu);
        viewMenu.addItem("Style", styleMenu);

        contextMenuPanel.setVisible(false);
        viewMenu.addItem("Toggle Context Panel", new Command() {
            @Override
            public void execute() {
                if (contextMenuPanel.isVisible()) {
                    contextMenuPanel.setVisible(false);
                    mainPanel.setWidgetSize(contextMenuPanel, 0);
                    mainPanel.forceLayout();
                } else {
                    contextMenuPanel.setVisible(true);
                    mainPanel.setWidgetSize(contextMenuPanel, 20);
                    mainPanel.forceLayout();
                }
            }
        });

        menu.addItem("File", fileMenu);
        menu.addItem("View", viewMenu);

        // Draw for the first time.
        RootLayoutPanel.get().forceLayout();
        mainPanel.forceLayout();
        widget.setViewType(ViewType.VIEW_TYPE_CLADOGRAM);
        widget.render();

        initColorPicker();

        String[] splitPath = Window.Location.getPath().split("/");
        String treeIdString = null;
        for (int i = 0; i < splitPath.length; i++) {
            if (splitPath[i].equalsIgnoreCase("tree")) {
                treeIdString = splitPath[i + 1];
            }
        }

        if (treeIdString != null && !treeIdString.equals("")) {
            try {
                final byte[] treeId = Hex.decodeHex(treeIdString.toCharArray());
                this.loadTree(null, treeId, layoutType);
            } catch (Exception e) {
                Window.alert("Unable to parse tree ID string from URL");
            }
        } else {
            // Present the user the dialog to load a tree.
            this.displayTrees();
        }

        updateStyle();
        History.addValueChangeHandler(new ValueChangeHandler<String>() {
            @Override
            public void onValueChange(ValueChangeEvent<String> event) {
                updateStyle();
            }
        });
    }

    private ColorBox createColorBox() {
        ColorBox colorBox = new ColorBox();
        colorBox.addStyleName("{hash:true,required:false}"); // jscolor config
        return colorBox;
    }

    private final native void initColorPicker()
    /*-{
       $wnd.jscolor.init();
    }-*/;

    private void loadTree(final PopupPanel displayTreePanel, final byte[] treeId, final String layoutID) {
        combinedService.getRootNode(treeId, layoutID, new AsyncCallback<CombinedService.NodeResponse>() {

            @Override
            public void onFailure(Throwable caught) {
                Window.alert(caught.getMessage());
            }

            @Override
            public void onSuccess(NodeResponse result) {
                RemoteTree tree = new RemoteTree();
                tree.setRootNode(result.node);
                tree.setHash(treeId);
                PagedDocument document = new PagedDocument(tree, result.layout);
                setDocument(document);

                if (displayTreePanel != null) {
                    displayTreePanel.hide();
                }
            }
        });
    }

    private void setDocument(PagedDocument document) {
        document.setCombinedService(combinedService);
        document.setEventBus(eventBus);
        searchService.setDocument(document);
        widget.setDocument(document);
        updateStyle();
    }

    private void loadTree(final PopupPanel displayTreePanel, final RemoteTree tree, final String layoutID) {
        combinedService.getLayout(tree.getRootNode(), layoutID,
                new AsyncCallback<CombinedService.LayoutResponse>() {
                    @Override
                    public void onFailure(Throwable caught) {
                        Window.alert(caught.getMessage());

                        if (displayTreePanel != null) {
                            displayTreePanel.hide();
                        }
                    }

                    @Override
                    public void onSuccess(LayoutResponse layoutResponse) {
                        PagedDocument document = new PagedDocument(tree, layoutResponse);
                        setDocument(document);

                        if (displayTreePanel != null) {
                            displayTreePanel.hide();
                        }
                    }
                });
    }

    private void displayTrees() {
        final PopupPanel displayTreePanel = new PopupPanel();
        displayTreePanel.setModal(true);

        displayTreePanel.setPopupPositionAndShow(new PopupPanel.PositionCallback() {
            public void setPosition(int offsetWidth, int offsetHeight) {
                int left = (Window.getClientWidth() - offsetWidth) / 2;
                int top = (Window.getClientHeight() - offsetHeight) / 2 - 100;
                displayTreePanel.setPopupPosition(left, top);
            }
        });

        final VerticalPanel vPanel = new VerticalPanel();
        displayTreePanel.add(vPanel);

        Label messageLabel = new Label("Select a tree to load:");
        vPanel.add(messageLabel);

        final Label label = new Label("Retrieving tree list...");
        vPanel.add(label);

        final ListBox lb = new ListBox();
        lb.setVisible(false);
        vPanel.add(lb);

        final HorizontalPanel hPanel = new HorizontalPanel();
        hPanel.add(new Button("OK", new ClickHandler() {

            @Override
            public void onClick(ClickEvent arg0) {

                label.setText("Loading...");
                label.setVisible(true);
                lb.setVisible(false);
                hPanel.setVisible(false);

                int index = lb.getSelectedIndex();
                if (index >= 0 && trees != null) {
                    final ITree data = trees.get(index);
                    if (data != null) {
                        loadTree(displayTreePanel, ((RemoteTree) data), layoutType);
                    }
                }
            }

        }));

        hPanel.add(new Button("Cancel", new ClickHandler() {

            @Override
            public void onClick(ClickEvent arg0) {
                displayTreePanel.hide();
            }
        }));

        vPanel.add(hPanel);

        displayTreePanel.show();

        treeList.getTreeList(new AsyncCallback<List<ITree>>() {

            @Override
            public void onFailure(Throwable arg0) {
                Window.alert(arg0.getMessage());
            }

            @Override
            public void onSuccess(List<ITree> trees) {
                Phyloviewer.this.trees = new ArrayList<ITree>();
                for (ITree tree : trees) {
                    if (((RemoteTree) tree).isPublic()) {
                        lb.addItem(tree.getName());
                        Phyloviewer.this.trees.add(tree);
                    }
                }

                label.setVisible(false);
                lb.setVisible(true);
            }
        });
    }

    private void updateStyle() {
        final String styleID = Window.Location.getParameter("styleID");

        if (styleID == null) {
            return;
        }

        AsyncCallback<IStyleMap> callback = new AsyncCallback<IStyleMap>() {
            @Override
            public void onFailure(Throwable caught) {
                Window.alert("Failed to get style " + styleID + "\n" + caught.getMessage());
            }

            @Override
            public void onSuccess(IStyleMap style) {
                IDocument document = widget.getDocument();
                if (document != null) {
                    document.setStyleMap(style);
                    widget.render();
                }
            }
        };

        StyleServiceClient.getStyle(styleID, callback);
    }

    private String getSVG() {
        SVGGraphics graphics = new SVGGraphics();
        graphics.setSize(widget.getView().getOffsetWidth(), widget.getView().getOffsetHeight());
        widget.getView().renderTo(graphics);
        return graphics.toString();
    }
}