blob: 5a6ed1e3b04ab6586bb8376be8c868e595f9a2ae [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014 BestSolution.at 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:
* Tom Schindl<tom.schindl@bestsolution.at> - initial API and implementation
*******************************************************************************/
package org.eclipse.fx.ui.workbench.renderers.fx;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.event.EventHandler;
import javafx.geometry.Orientation;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.control.SplitPane;
import javafx.scene.control.SplitPane.Divider;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.e4.ui.model.application.ui.MUIElement;
import org.eclipse.e4.ui.model.application.ui.basic.MCompositePart;
import org.eclipse.e4.ui.model.application.ui.basic.MPart;
import org.eclipse.e4.ui.model.application.ui.basic.MPartSashContainerElement;
import org.eclipse.e4.ui.workbench.UIEvents;
import org.eclipse.fx.ui.panes.GridData;
import org.eclipse.fx.ui.panes.GridData.Alignment;
import org.eclipse.fx.ui.panes.GridLayoutPane;
import org.eclipse.fx.ui.workbench.renderers.base.BaseCompositePartRenderer;
import org.eclipse.fx.ui.workbench.renderers.base.BaseRenderer;
import org.eclipse.fx.ui.workbench.renderers.base.widget.WCompositePart;
import org.eclipse.fx.ui.workbench.renderers.base.widget.WLayoutedWidget;
import org.eclipse.fx.ui.workbench.renderers.base.widget.WMenu;
import org.eclipse.fx.ui.workbench.renderers.base.widget.WSash;
import org.eclipse.fx.ui.workbench.renderers.base.widget.WToolBar;
import org.eclipse.fx.ui.workbench.renderers.fx.DefPartRenderer.HandleGroup;
import org.eclipse.fx.ui.workbench.renderers.fx.widget.WLayoutedWidgetImpl;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
/**
* Renderer for a composite part
*/
public class DefCompositePartRenderer extends BaseCompositePartRenderer<Node> {
@Override
protected Class<? extends WCompositePart<Node>> getWidgetClass(MCompositePart container) {
if (container.getTags().contains(WCompositePart.TAG_FIXED_LAYOUT)) {
return WFixedSashImpl.class;
} else {
return WResizableSashImpl.class;
}
}
/**
* The {@link AbstractCompositePartImpl} provides a basic "template" for the {@link MCompositePart}.
* <p>
* A rendered {@link MCompositePart} can consist of a toolbar, menu and the main content (parts). The
* {@link AbstractCompositePartImpl} takes care of rendering the toolbar and the menu.
*
* @param <T>
* the business control
*/
static abstract class AbstractCompositePartImpl<T extends Node> extends WLayoutedWidgetImpl<T, Node, MCompositePart> implements WCompositePart<Node> {
AnchorPane contentArea;
BorderPane dataArea;
StackPane toolbarGroup;
Group menuGroup;
@Override
public void setToolbar(WToolBar<Node> toolbar) {
if (toolbar == null) {
clearToolBar();
} else {
initToolbarMenu();
Node n = (Node) toolbar.getWidget();
n.addEventHandler(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
MPart element = getDomElement();
if (element != null && element.getTags().contains(RendererConstants.TOOLBAR_MENU_FLOAT_TAG)) {
AbstractCompositePartImpl.this.toolbarGroup.getParent().setVisible(false);
}
}
});
n.getStyleClass().add(RendererConstants.CSS_CLASS_VIEW_TOOLBAR);
this.toolbarGroup.getChildren().setAll(n);
}
}
@Override
public void setMenu(WMenu<Node> menu) {
if (menu == null) {
clearMenu();
} else {
this.menuGroup.setVisible(true);
this.menuGroup.getChildren().setAll((Node) menu.getWidget());
this.contentArea.requestLayout();
}
}
@Override
public AnchorPane getWidgetNode() {
if (this.contentArea == null) {
this.menuGroup = new Group();
this.menuGroup.setVisible(false);
this.menuGroup.setManaged(false);
Node handler = new HandleGroup();
handler.setOnMousePressed(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
AbstractCompositePartImpl.this.toolbarGroup.getParent().setVisible(true);
}
});
this.contentArea = new AnchorPane() {
@Override
protected void layoutChildren() {
super.layoutChildren();
if (AbstractCompositePartImpl.this.menuGroup.isVisible()) {
AbstractCompositePartImpl.this.menuGroup.relocate(AbstractCompositePartImpl.this.contentArea.getWidth() - 20, 0);
}
}
};
this.dataArea = new BorderPane();
AnchorPane.setTopAnchor(this.dataArea, Double.valueOf(0.0));
AnchorPane.setLeftAnchor(this.dataArea, Double.valueOf(0.0));
AnchorPane.setBottomAnchor(this.dataArea, Double.valueOf(0.0));
AnchorPane.setRightAnchor(this.dataArea, Double.valueOf(0.0));
this.contentArea.getChildren().addAll(this.dataArea, this.menuGroup);
Node n = getWidget();
n.getStyleClass().add(RendererConstants.CSS_CLASS_PART_CONTENT);
this.dataArea.setCenter(n);
}
return this.contentArea;
}
private void clearToolBar() {
if (this.toolbarGroup != null) {
this.toolbarGroup.getChildren().clear();
this.dataArea.setTop(null);
this.dataArea.setBottom(null);
this.toolbarGroup = null;
}
}
private void clearMenu() {
if (this.menuGroup != null) {
this.menuGroup.setVisible(false);
this.menuGroup.getChildren().clear();
}
}
private void initToolbarMenu() {
if (this.toolbarGroup == null) {
// Ensure that everything is initialized!!!
getStaticLayoutNode();
this.toolbarGroup = new StackPane();
MPart element = getDomElement();
if (element != null && element.getTags().contains(RendererConstants.TOOL_BAR_FULL_SPAN_TAG)) {
final BorderPane p = new BorderPane();
p.setCenter(this.toolbarGroup);
p.getStyleClass().add(RendererConstants.CSS_CLASS_VIEW_TOOLBAR_CONTAINER);
if (element.getTags().contains(RendererConstants.TOOLBAR_MENU_FLOAT_TAG)) {
AnchorPane.setLeftAnchor(p, Double.valueOf(0.0));
AnchorPane.setRightAnchor(p, Double.valueOf(0.0));
AnchorPane.setTopAnchor(p, Double.valueOf(0.0));
this.contentArea.getChildren().add(p);
p.setVisible(false);
p.setOnMousePressed(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
p.setVisible(false);
}
});
} else {
if (element.getTags().contains(RendererConstants.TOOLBAR_MENU_BOTTOM_TAG)) {
this.dataArea.setBottom(p);
} else {
this.dataArea.setTop(p);
}
}
} else {
BorderPane p = new BorderPane();
p.setRight(this.toolbarGroup);
p.getStyleClass().add(RendererConstants.CSS_CLASS_VIEW_TOOLBAR_CONTAINER);
if (element != null && element.getTags().contains(RendererConstants.TOOLBAR_MENU_BOTTOM_TAG)) {
this.dataArea.setBottom(p);
} else {
this.dataArea.setTop(p);
}
}
}
}
}
static class WFixedSashImpl extends AbstractCompositePartImpl<GridLayoutPane> {
private StackPane overlayContainer;
@Override
public void setDialog(Object dialogNode) {
@NonNull
Pane staticLayoutNode = (@NonNull Pane) getStaticLayoutNode();
if (dialogNode == null) {
if (this.overlayContainer != null) {
((Pane) staticLayoutNode).getChildren().remove(this.overlayContainer);
this.overlayContainer.getChildren().clear();
}
} else {
if (this.overlayContainer == null) {
this.overlayContainer = new StackPane();
this.overlayContainer.getStyleClass().add("overlay-container"); //$NON-NLS-1$
this.overlayContainer.setManaged(false);
this.overlayContainer.setMouseTransparent(false);
staticLayoutNode.layoutBoundsProperty().addListener( o -> {
staticLayoutNode.layoutBoundsProperty().get();
this.overlayContainer.resize(staticLayoutNode.getWidth(), staticLayoutNode.getHeight());
});
}
this.overlayContainer.resize(staticLayoutNode.getWidth(), staticLayoutNode.getHeight());
this.overlayContainer.getChildren().setAll((Node)dialogNode);
((Pane) staticLayoutNode).getChildren().add(this.overlayContainer);
}
}
private static @NonNull GridData toGridData(Map<String, String> dataMap) {
GridData gd = new GridData();
if (dataMap.containsKey(WCompositePart.FIXED_LAYOUT_WIDTH)) {
gd.widthHintProperty().set(Integer.parseInt(dataMap.get(WCompositePart.FIXED_LAYOUT_WIDTH)));
if (!dataMap.containsKey(WCompositePart.FIXED_LAYOUT_HEIGHT)) {
gd.grabExcessVerticalSpaceProperty().set(true);
gd.verticalAlignmentProperty().set(Alignment.FILL);
}
} else {
gd.horizontalAlignmentProperty().set(Alignment.FILL);
}
if (dataMap.containsKey(WCompositePart.FIXED_LAYOUT_HEIGHT)) {
gd.heightHintProperty().set(Integer.parseInt(dataMap.get(WCompositePart.FIXED_LAYOUT_HEIGHT)));
if (!dataMap.containsKey(WCompositePart.FIXED_LAYOUT_WIDTH)) {
gd.grabExcessHorizontalSpaceProperty().set(true);
gd.horizontalAlignmentProperty().set(Alignment.FILL);
}
} else {
gd.verticalAlignmentProperty().set(Alignment.FILL);
}
if (dataMap.containsKey(WCompositePart.FIXED_LAYOUT_GRAB_HORIZONTAL)) {
gd.grabExcessHorizontalSpaceProperty().set(Boolean.parseBoolean(dataMap.get(WCompositePart.FIXED_LAYOUT_GRAB_HORIZONTAL)));
gd.horizontalAlignmentProperty().set(Alignment.FILL);
}
if (dataMap.containsKey(WCompositePart.FIXED_LAYOUT_GRAB_VERTICAL)) {
gd.grabExcessVerticalSpaceProperty().set(Boolean.parseBoolean(dataMap.get(WCompositePart.FIXED_LAYOUT_GRAB_VERTICAL)));
gd.verticalAlignmentProperty().set(Alignment.FILL);
}
return gd;
}
@Override
public void addItem(WLayoutedWidget<MPartSashContainerElement> widget) {
Node n = (Node) widget.getStaticLayoutNode();
GridLayoutPane p = getWidget();
MCompositePart element = getDomElement();
if ( element != null && element.isHorizontal()) {
p.setNumColumns(p.getNumColumns() + 1);
}
MPartSashContainerElement domElement2 = widget.getDomElement();
if( domElement2 != null ) {
GridData gd = toGridData(domElement2.getPersistedState());
GridLayoutPane.setConstraint(n, gd);
}
p.getChildren().add(n);
}
@Override
public int getItemCount() {
return getWidget().getChildren().size();
}
@Override
public void addItems(List<WLayoutedWidget<MPartSashContainerElement>> list) {
List<Node> nodeList = new ArrayList<Node>();
GridLayoutPane p = getWidget();
for (WLayoutedWidget<MPartSashContainerElement> w : list) {
Node n = (Node) w.getStaticLayoutNode();
MPartSashContainerElement element = w.getDomElement();
if( element != null ) {
GridData gd = toGridData(element.getPersistedState());
GridLayoutPane.setConstraint(n, gd);
}
nodeList.add(n);
}
MCompositePart element = getDomElement();
if (element != null && element.isHorizontal()) {
p.setNumColumns(p.getNumColumns() + nodeList.size());
}
p.getChildren().addAll(nodeList);
}
@Override
public void addItems(int index, List<WLayoutedWidget<MPartSashContainerElement>> list) {
List<Node> nodeList = new ArrayList<Node>();
GridLayoutPane p = getWidget();
for (WLayoutedWidget<MPartSashContainerElement> w : list) {
Node n = (Node) w.getStaticLayoutNode();
MPartSashContainerElement element = w.getDomElement();
if( element != null ) {
GridData gd = toGridData(element.getPersistedState());
GridLayoutPane.setConstraint(n, gd);
}
nodeList.add(n);
}
MCompositePart element = getDomElement();
if (element != null && element.isHorizontal()) {
p.setNumColumns(p.getNumColumns() + nodeList.size());
}
p.getChildren().addAll(index, nodeList);
}
@Override
public void removeItem(WLayoutedWidget<MPartSashContainerElement> widget) {
Node n = (Node) widget.getStaticLayoutNode();
GridLayoutPane p = getWidget();
p.setNumColumns(p.getNumColumns() - 1);
p.getChildren().remove(n);
}
@Override
protected GridLayoutPane createWidget() {
GridLayoutPane p = new GridLayoutPane();
p.setMarginWidth(0);
p.setMarginHeight(0);
p.setHorizontalSpacing(0);
p.setVerticalSpacing(0);
p.setNumColumns(0);
return p;
}
@Override
public void updateLayout() {
// TODO Auto-generated method stub
}
}
static class WResizableSashImpl extends AbstractCompositePartImpl<SplitPane> {
private List<WLayoutedWidget<MPartSashContainerElement>> items = new ArrayList<WLayoutedWidget<MPartSashContainerElement>>();
private StackPane overlayContainer;
@Override
public void setDialog(Object dialogNode) {
@NonNull
Pane staticLayoutNode = (@NonNull Pane) getStaticLayoutNode();
if (dialogNode == null) {
if (this.overlayContainer != null) {
((Pane) staticLayoutNode).getChildren().remove(this.overlayContainer);
this.overlayContainer.getChildren().clear();
}
} else {
if (this.overlayContainer == null) {
this.overlayContainer = new StackPane();
this.overlayContainer.getStyleClass().add("overlay-container"); //$NON-NLS-1$
this.overlayContainer.setManaged(false);
this.overlayContainer.setMouseTransparent(false);
staticLayoutNode.layoutBoundsProperty().addListener( o -> {
staticLayoutNode.layoutBoundsProperty().get();
this.overlayContainer.resize(staticLayoutNode.getWidth(), staticLayoutNode.getHeight());
});
}
this.overlayContainer.resize(staticLayoutNode.getWidth(), staticLayoutNode.getHeight());
this.overlayContainer.getChildren().setAll((Node)dialogNode);
((Pane) staticLayoutNode).getChildren().add(this.overlayContainer);
}
}
ChangeListener<Number> listener = new ChangeListener<Number>() {
boolean queueing;
@Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if (!this.queueing) {
this.queueing = true;
Platform.runLater(new Runnable() {
@SuppressWarnings("unqualified-field-access")
@Override
public void run() {
recalcWeight();
queueing = false;
}
});
}
}
};
@Override
public void updateLayout() {
updateDividers();
}
void recalcWeight() {
@Nullable
MCompositePart domElement = getDomElement();
if( domElement != null ) {
BaseRenderer<?, ?> r = (BaseRenderer<?, ?>) domElement.getRenderer();
r.syncUIModifications(domElement, this::doRecalcWeight);
} else {
this.logger.error("The domain object should not be null at this point"); //$NON-NLS-1$
}
}
void doRecalcWeight() {
if (this.state != WidgetState.CREATED) {
return;
}
// FIXME We should not do recalcs when we are in teardown of the
// widget
double prev = 0;
int idx = 0;
// No items nothing to recalculate
if (this.items.isEmpty()) {
return;
}
for (double d : getWidget().getDividerPositions()) {
MPartSashContainerElement element = this.items.get(idx++).getDomElement();
if( element != null ) {
element.setContainerData((d - prev) * 10 + ""); //$NON-NLS-1$
}
prev = d;
}
MPartSashContainerElement element = this.items.get(this.items.size() - 1).getDomElement();
if( element != null ) {
element.setContainerData((1.0 - prev) * 10 + ""); //$NON-NLS-1$
}
}
@Override
protected void bindProperties(final SplitPane widget) {
super.bindProperties(widget);
widget.getDividers().addListener(new ListChangeListener<Divider>() {
@Override
public void onChanged(javafx.collections.ListChangeListener.Change<? extends Divider> c) {
while (c.next()) {
for (Divider d : c.getAddedSubList()) {
if( d != null ) {
d.positionProperty().addListener(WResizableSashImpl.this.listener);
}
}
for (Divider d : c.getRemoved()) {
if( d != null ) {
d.positionProperty().removeListener(WResizableSashImpl.this.listener);
}
}
}
}
});
for (Divider d : widget.getDividers()) {
d.positionProperty().addListener(this.listener);
}
}
@Override
protected SplitPane createWidget() {
SplitPane p = new SplitPane();
return p;
}
@Inject
void setOrientation(@Named(UIEvents.GenericTile.HORIZONTAL) boolean horizontal) {
getWidget().setOrientation(horizontal ? Orientation.HORIZONTAL : Orientation.VERTICAL);
}
private static void setResizableWithParent(WLayoutedWidget widget) {
MUIElement domElement = widget.getDomElement();
if( domElement != null ) {
SplitPane.setResizableWithParent((Node)widget.getStaticLayoutNode(), ! domElement.getTags().contains(WSash.TAG_NO_AUTO_RESIZE));
}
}
@Override
public void addItem(WLayoutedWidget<MPartSashContainerElement> widget) {
SplitPane p = getWidget();
setResizableWithParent(widget);
p.getItems().add((Node) widget.getStaticLayoutNode());
this.items.add(widget);
updateDividers();
}
@Override
public void addItems(int index, List<WLayoutedWidget<MPartSashContainerElement>> list) {
SplitPane p = getWidget();
List<Node> l = new ArrayList<Node>();
for (WLayoutedWidget<MPartSashContainerElement> i : list) {
setResizableWithParent(i);
l.add((Node) i.getStaticLayoutNode());
}
p.getItems().addAll(index, l);
this.items.addAll(index, list);
updateDividers();
}
@Override
public void addItems(List<WLayoutedWidget<MPartSashContainerElement>> list) {
SplitPane p = getWidget();
List<Node> l = new ArrayList<Node>();
for (WLayoutedWidget<MPartSashContainerElement> i : list) {
setResizableWithParent(i);
l.add((Node) i.getStaticLayoutNode());
}
p.getItems().addAll(l);
this.items.addAll(list);
updateDividers();
}
@Override
public void setWidgetState(WidgetState state) {
super.setWidgetState(state);
if (state == WidgetState.CREATED) {
updateDividers();
}
}
@Override
public void removeItem(WLayoutedWidget<MPartSashContainerElement> widget) {
SplitPane p = getWidget();
p.getItems().remove(widget.getStaticLayoutNode());
this.items.remove(widget);
updateDividers();
}
@Override
protected void doCleanup() {
super.doCleanup();
this.items.clear();
}
private void updateDividers() {
if (this.items.size() <= 1) {
return;
}
if (this.state != WidgetState.CREATED) {
return;
}
double total = 0;
for (WLayoutedWidget<MPartSashContainerElement> w : this.items) {
total += w.getWeight();
}
double[] deviders = new double[this.items.size() - 1];
for (int i = 0; i < this.items.size() - 1; i++) {
deviders[i] = (i == 0 ? 0 : deviders[i - 1]) + (this.items.get(i).getWeight() / total);
}
getWidget().setDividerPositions(deviders);
}
@Override
public int getItemCount() {
return getWidget().getItems().size();
}
}
}