org.semanticsoft.vaaclipse.presentation.renderers.SashRenderer.java Source code

Java tutorial

Introduction

Here is the source code for org.semanticsoft.vaaclipse.presentation.renderers.SashRenderer.java

Source

/*******************************************************************************
 * Copyright (c) 2012 Rushan R. Gilmullin and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Rushan R. Gilmullin - initial API and implementation
 *******************************************************************************/

package org.semanticsoft.vaaclipse.presentation.renderers;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;

import org.eclipse.e4.core.services.log.Logger;
import org.eclipse.e4.ui.model.application.ui.MElementContainer;
import org.eclipse.e4.ui.model.application.ui.MUIElement;
import org.eclipse.e4.ui.model.application.ui.basic.MPartSashContainer;
import org.eclipse.e4.ui.model.application.ui.basic.MPartSashContainerElement;
import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
import org.eclipse.e4.ui.services.internal.events.EventBroker;
import org.eclipse.e4.ui.workbench.UIEvents;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
import org.semanticsoft.vaaclipse.presentation.widgets.TrimmedWindowContent;
import org.semanticsoft.vaaclipse.publicapi.model.Tags;
import org.semanticsoft.vaaclipse.widgets.SashWidget;
import org.semanticsoft.vaaclipse.widgets.SashWidgetHorizontal;
import org.semanticsoft.vaaclipse.widgets.SashWidgetVertical;
import org.semanticsoft.vaaclipse.widgets.SplitPositionChangedListener;

import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.AbstractSplitPanel;
import com.vaadin.ui.Component;
import com.vaadin.ui.ComponentContainer;
import com.vaadin.ui.Panel;
import com.vaadin.ui.VerticalLayout;

public class SashRenderer extends VaadinRenderer {

    @Inject
    EventBroker eventBroker;

    @Inject
    Logger logger;

    private boolean ignoreSashWeights = false;

    private EventHandler sashWeightHandler = new EventHandler() {
        public void handleEvent(Event event) {

            if (ignoreSashWeights)
                return;

            // Ensure that this event is for a MPartSashContainer
            MUIElement element = (MUIElement) event.getProperty(UIEvents.EventTags.ELEMENT);
            MElementContainer<MUIElement> parent = element.getParent();
            if (parent.getRenderer() != SashRenderer.this)
                return;

            MPartSashContainer sash = (MPartSashContainer) (MElementContainer<?>) element.getParent();
            setWeights(sash);
        }
    };

    private final EventHandler visibilityHandler = new EventHandler() {
        @Override
        public void handleEvent(Event event) {
            MUIElement changedElement = (MUIElement) event.getProperty(UIEvents.EventTags.ELEMENT);

            if ((MElementContainer<?>) changedElement.getParent() instanceof MPartSashContainer) {
                MPartSashContainer sash = (MPartSashContainer) (MElementContainer<?>) changedElement.getParent();

                ((SashRenderer) sash.getRenderer()).refreshSashContainer(sash);

                boolean visible = false;
                for (MPartSashContainerElement child : sash.getChildren()) {
                    if (child.isVisible()) {
                        visible = true;
                        break;
                    }
                }
                if (sash.isVisible() != visible)
                    sash.setVisible(visible);
            }
        }
    };

    @Override
    public void createWidget(MUIElement element, MElementContainer<MUIElement> parent) {
        if (!(element instanceof MPartSashContainer)) {
            return;
        }
        VerticalLayout layout = new VerticalLayout();
        layout.setSizeFull();

        element.setWidget(layout);
    }

    @Override
    public void processContents(final MElementContainer<MUIElement> element) {
        refreshSashContainer((MPartSashContainer) (MElementContainer<?>) element);
    }

    public void generateSplitPanelStructure(MPartSashContainer sash) {
        VerticalLayout layout = (VerticalLayout) sash.getWidget();
        layout.removeAllComponents();

        ComponentContainer sashWidget = null;

        @SuppressWarnings("unchecked")
        List<MPartSashContainerElement> renderableAndVisible = (List<MPartSashContainerElement>) filterRenderableAndVisibleElements(
                sash);

        if (renderableAndVisible.isEmpty()) {
            sashWidget = new VerticalLayout();
        } else if (renderableAndVisible.size() == 1) {
            sashWidget = new VerticalLayout();
            MPartSashContainerElement child = renderableAndVisible.get(0);
            sashWidget.addComponent((Component) child.getWidget());
        } else {
            sashWidget = sash.isHorizontal() ? new SashWidgetHorizontal() : new SashWidgetVertical();
            AbstractSplitPanel currentSashWidget = (AbstractSplitPanel) sashWidget;
            currentSashWidget.setLocked(sash.getTags().contains(Tags.NO_RESIZE));
            for (int i = 0; i < renderableAndVisible.size(); i++) {
                MPartSashContainerElement child = renderableAndVisible.get(i);

                if (currentSashWidget.getFirstComponent() == null) {
                    currentSashWidget.setFirstComponent((Component) child.getWidget());
                } else {
                    if (i == renderableAndVisible.size() - 1) {
                        currentSashWidget.setSecondComponent((Component) child.getWidget());
                    } else {
                        AbstractSplitPanel newSashWidget = sash.isHorizontal() ? new SashWidgetHorizontal()
                                : new SashWidgetVertical();
                        newSashWidget.setLocked(sash.getTags().contains(Tags.NO_RESIZE));
                        newSashWidget.setFirstComponent((Component) child.getWidget());
                        currentSashWidget.setSecondComponent(newSashWidget);
                        currentSashWidget = newSashWidget;
                    }
                }
            }
        }

        sashWidget.setSizeFull();
        layout.addComponent(sashWidget);

        setWeights(sash);
    }

    void setWeights(MPartSashContainer sash) {
        @SuppressWarnings("unchecked")
        List<MPartSashContainerElement> renderableAndVisible = (List<MPartSashContainerElement>) filterRenderableAndVisibleElements(
                sash);
        if (renderableAndVisible.size() < 2)
            return;

        Map<MPartSashContainerElement, Double> weights = new HashMap<MPartSashContainerElement, Double>();
        Map<Component, MPartSashContainerElement> map = new HashMap<Component, MPartSashContainerElement>();
        double total_weight = 0;
        for (MPartSashContainerElement children : renderableAndVisible) {
            String data = children.getContainerData();
            double weight = parseContainerData(data);

            map.put((Component) children.getWidget(), children);
            weights.put(children, weight);
            total_weight += weight;
        }

        if (total_weight == 0.0) //all child elements has zero weight
            total_weight = 1.0;

        AbstractSplitPanel topSashWidget = (AbstractSplitPanel) ((VerticalLayout) sash.getWidget()).getComponent(0);
        AbstractSplitPanel currentSashWidget = topSashWidget;
        while (true) {
            MPartSashContainerElement e1 = map.get(currentSashWidget.getFirstComponent());
            //the first - is always element
            double w = weights.get(e1);
            double pos = (w / total_weight) * 100;
            currentSashWidget.setSplitPosition((float) pos);

            if (map.containsKey(currentSashWidget.getSecondComponent()))
                break;

            currentSashWidget = (AbstractSplitPanel) currentSashWidget.getSecondComponent();
            total_weight = total_weight - w;
        }
    }

    public void refreshSashContainer(MPartSashContainer sash) {
        generateSplitPanelStructure(sash);
        addSplitPaneListener(sash);
    }

    @PostConstruct
    void postConstruct() {
        eventBroker.subscribe(UIEvents.UIElement.TOPIC_CONTAINERDATA, sashWeightHandler);
        eventBroker.subscribe(UIEvents.UIElement.TOPIC_VISIBLE, visibilityHandler);
    }

    @PreDestroy
    public void preDestroy() {
        eventBroker.unsubscribe(sashWeightHandler);
        eventBroker.unsubscribe(visibilityHandler);
    }

    @Override
    public void hookControllerLogic(MUIElement element) {
        //Controller logic is added in refreshSashContainer method. The widget hierarchy regenerated on each add and remove gui,
        //so listeners must added each time when sash widget created
    }

    public void addSplitPaneListener(MUIElement element) {
        final MPartSashContainer sash = (MPartSashContainer) element;

        List<MPartSashContainerElement> renderableAndVisible = (List<MPartSashContainerElement>) filterRenderableAndVisibleElements(
                sash);

        if (renderableAndVisible.size() > 1) {
            for (MPartSashContainerElement child : renderableAndVisible) {
                Component childComponent = (Component) child.getWidget();
                if (childComponent.getParent() instanceof SashWidget) {
                    SashWidget sashWidget = (SashWidget) childComponent.getParent();
                    sashWidget.addListener(new SplitPositionChangedListener() {

                        @Override
                        public void processEvent(AbstractSplitPanel splitPanel, float newSplitPos) {
                            AbstractComponent firstWidget = (AbstractComponent) splitPanel.getFirstComponent();

                            //filter renderable and visible again (list can be changed)
                            List<MPartSashContainerElement> renderableAndVisible = (List<MPartSashContainerElement>) filterRenderableAndVisibleElements(
                                    sash);
                            MPartSashContainerElement firstChild = null;
                            double rest_weight = 0;
                            List<MPartSashContainerElement> restChilds = new LinkedList<MPartSashContainerElement>();
                            for (int i = 0; i < renderableAndVisible.size(); i++) {
                                MPartSashContainerElement child = renderableAndVisible.get(i);
                                if (firstWidget.equals(child.getWidget())) {
                                    firstChild = child;
                                }

                                if (firstChild != null) {
                                    try {
                                        double w = parseContainerData(child.getContainerData());
                                        rest_weight += w;
                                    } catch (NumberFormatException e) {
                                        logger.error(
                                                "Changing weights of SashContainer's childs is failed. Can not parse children container data");
                                        return;
                                    }

                                    restChilds.add(child);
                                }
                            }

                            if (restChilds.size() > 1) {
                                //String debugstr = "weights: ";
                                ignoreSashWeights = true;

                                double rest_weight_except_first = rest_weight
                                        - parseContainerData(firstChild.getContainerData());
                                double newW1 = (newSplitPos / 100) * rest_weight;
                                double new_rest_weight_except_first = rest_weight - newW1;
                                long longVal1 = Math.round(newW1);
                                firstChild.setContainerData(Long.toString(longVal1));
                                //debugstr += longVal1;

                                //if the weight of remainder (except first) is not zero, then we distribute the new space appropriate weights
                                if (rest_weight_except_first > 0.0) {
                                    for (int i = 1; i < restChilds.size(); i++) {
                                        MPartSashContainerElement child = restChilds.get(i);
                                        double w = parseContainerData(child.getContainerData());
                                        double newW = (w / rest_weight_except_first) * new_rest_weight_except_first;
                                        long longVal = Math.round(newW);

                                        child.setContainerData(Long.toString(longVal));
                                        //debugstr += ", " + longVal;
                                    }
                                } else //otherwise we assign all new space to the last component
                                {
                                    MPartSashContainerElement rest1 = restChilds.get(restChilds.size() - 1);
                                    rest1.setContainerData(Long.toString(Math.round(new_rest_weight_except_first)));
                                }

                                ignoreSashWeights = false;

                                //System.out.println(debugstr);

                                //ATTENTION! Really line below is not required if code above works correctly.
                                //But if there are any wrong behaviour appear then we have wrong synchronized state
                                //that may caused side effects, so we do back syncronization (and bug if it occur become obvious).
                                //This is also zeroed weight mismatch occuring when double rounded (and possible when vaadin process changes),
                                //so we avoid mismatch accumulating. 
                                //Most likely in the future this will be deleted (when this code will be proved that all ok).
                                setWeights(sash);
                            } else
                                logger.error(
                                        "Changing SashContainer child weights is failed. User changes is not processed correctly");

                            //and last thing what we must do - tell the WorkbenchWindow to recalculate bounds of it content
                            //(because bounds of some content of workbench window changed after sash widget split position changed)
                            MWindow window = modelService.getTopLevelWindowFor(sash);
                            TrimmedWindowContent windowContent = (TrimmedWindowContent) ((Panel) window.getWidget())
                                    .getContent();
                            windowContent.invalidateBounds();
                        }
                    });
                } else
                    logger.error(
                            "Error in  widget hierarchy detected - if sash container has more than one element its child widget must has SashWidget as a parent");
            }
        }
    }

    private double parseContainerData(String containerData) {
        if (containerData == null)
            return 0.0d;

        containerData = containerData.trim();

        try {
            return Double.parseDouble(containerData);
        } catch (NumberFormatException e) {
            return 0.0d;
        }
    }

    @Override
    public void addChildGui(MUIElement child, MElementContainer<MUIElement> element) {
        if (!(child instanceof MPartSashContainerElement)
                || !((MElementContainer<?>) element instanceof MPartSashContainer))
            return;

        refreshSashContainer((MPartSashContainer) (MElementContainer<?>) element);
    }

    @Override
    public void removeChildGui(MUIElement child, MElementContainer<MUIElement> element) {
        if (!(child instanceof MPartSashContainerElement)
                || !((MElementContainer<?>) element instanceof MPartSashContainer))
            return;

        refreshSashContainer((MPartSashContainer) (MElementContainer<?>) element);
    }
}