| /******************************************************************************* |
| * 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 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Rushan R. Gilmullin - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.osbp.vaaclipse.presentation.renderers; |
| |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import javax.annotation.PostConstruct; |
| import javax.annotation.PreDestroy; |
| import javax.inject.Inject; |
| |
| import org.eclipse.e4.core.di.annotations.Optional; |
| import org.eclipse.e4.core.services.events.IEventBroker; |
| 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.workbench.UIEvents; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.osbp.runtime.designer.api.IDesignerService; |
| import org.eclipse.osbp.runtime.designer.api.IDesignerService.IDesignListener; |
| import org.eclipse.osbp.runtime.designer.api.IWidgetDesignConfigurator; |
| import org.eclipse.osbp.vaaclipse.presentation.widgets.TrimmedWindowContent; |
| import org.eclipse.osbp.vaaclipse.publicapi.model.Tags; |
| import org.eclipse.osbp.vaaclipse.widgets.SashWidget; |
| import org.eclipse.osbp.vaaclipse.widgets.SashWidgetHorizontal; |
| import org.eclipse.osbp.vaaclipse.widgets.SashWidgetVertical; |
| import org.eclipse.osbp.vaaclipse.widgets.SplitPositionChangedListener; |
| import org.osgi.service.event.Event; |
| import org.osgi.service.event.EventHandler; |
| |
| 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 implements IDesignListener { |
| |
| @Inject |
| IEventBroker eventBroker; |
| |
| @Inject |
| Logger logger; |
| |
| @Inject |
| @Optional |
| private IDesignerService designerService; |
| |
| 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(); |
| |
| if (sash.getRenderer() == null) { |
| return; |
| } |
| |
| ((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); |
| } |
| } |
| }; |
| |
| private Set<MPartSashContainer> sashContainers = new HashSet<>(); |
| |
| @Override |
| public void createWidget(MUIElement element, |
| MElementContainer<MUIElement> parent) { |
| if (!(element instanceof MPartSashContainer)) { |
| return; |
| } |
| VerticalLayout layout = new VerticalLayout(); |
| layout.setSizeFull(); |
| |
| element.setWidget(layout); |
| |
| sashContainers.add((MPartSashContainer) element); |
| |
| if (designerService != null) { |
| designerService.addListener(this); |
| |
| if (designerService.isDesignMode()) { |
| updateDesigner(true); |
| } |
| } |
| |
| } |
| |
| @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); |
| } |
| |
| @Override |
| public void notify(IDesignerService.DesignEvent event) { |
| updateDesigner(event.getType() == IDesignerService.EventType.ENABLED); |
| } |
| |
| private void updateDesigner(boolean enabled) { |
| for (MPartSashContainer container : sashContainers) { |
| for (MPartSashContainerElement child : container.getChildren()) { |
| if (child.getWidget() != null && child.isToBeRendered()) { |
| updateDesigner(enabled, child); |
| } |
| } |
| } |
| } |
| |
| private void updateDesigner(boolean enabled, MPartSashContainerElement child) { |
| IWidgetDesignConfigurator designConfigurator = context |
| .get(IWidgetDesignConfigurator.class); |
| if (designConfigurator != null) { |
| designConfigurator.configure(child.getWidget(), (EObject) child, |
| enabled); |
| } |
| } |
| |
| 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) { |
| |
| // sashContainersMap.remove(sash); |
| |
| generateSplitPanelStructure(sash); |
| addSplitPaneListener(sash); |
| } |
| |
| @PostConstruct |
| void postConstruct() { |
| eventBroker.subscribe(UIEvents.UIElement.TOPIC_CONTAINERDATA, |
| sashWeightHandler); |
| eventBroker.subscribe(UIEvents.UIElement.TOPIC_VISIBLE, |
| visibilityHandler); |
| eventBroker.subscribe(UIEvents.ElementContainer.TOPIC_CHILDREN, |
| childrenMoveUpdater); |
| } |
| |
| @PreDestroy |
| public void preDestroy() { |
| |
| if (designerService != null) { |
| designerService.removeListener(this); |
| } |
| |
| eventBroker.unsubscribe(sashWeightHandler); |
| eventBroker.unsubscribe(visibilityHandler); |
| eventBroker.unsubscribe(childrenMoveUpdater); |
| } |
| |
| private EventHandler childrenMoveUpdater = new EventHandler() { |
| public void handleEvent(Event event) { |
| // Ensure that this event is for a MMenuItem |
| if (!(event.getProperty(UIEvents.EventTags.ELEMENT) instanceof MPartSashContainer)) |
| return; |
| |
| MPartSashContainer sash = (MPartSashContainer) event |
| .getProperty(UIEvents.EventTags.ELEMENT); |
| String type = (String) event.getProperty(UIEvents.EventTags.TYPE); |
| |
| // on move, we unrender an render the UI again |
| // |
| if (UIEvents.EventTypes.MOVE.equals(type)) { |
| // refreshSashContainer(sash); |
| } |
| } |
| }; |
| |
| @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); |
| |
| if (designerService != null && designerService.isDesignMode()) { |
| updateDesigner(designerService.isDesignMode(), |
| (MPartSashContainerElement) child); |
| } |
| } |
| |
| @Override |
| public void removeChildGui(MUIElement child, |
| MElementContainer<MUIElement> element) { |
| if (!(child instanceof MPartSashContainerElement) |
| || !((MElementContainer<?>) element instanceof MPartSashContainer)) |
| return; |
| |
| refreshSashContainer((MPartSashContainer) (MElementContainer<?>) element); |
| } |
| } |