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