blob: d39e39402dd66ed06b594a34a8c5beb3c136d5d2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012 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.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.UUID;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;
import org.eclipse.e4.core.contexts.ContextInjectionFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.core.di.annotations.Optional;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.e4.core.services.nls.Translation;
import org.eclipse.e4.core.services.translation.TranslationService;
import org.eclipse.e4.ui.internal.workbench.E4Workbench;
import org.eclipse.e4.ui.model.application.MApplication;
import org.eclipse.e4.ui.model.application.ui.basic.MPart;
import org.eclipse.e4.ui.model.application.ui.basic.MTrimBar;
import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
import org.eclipse.e4.ui.model.application.ui.basic.MWindowElement;
import org.eclipse.e4.ui.model.application.ui.menu.MMenu;
import org.eclipse.e4.ui.workbench.UIEvents;
import org.eclipse.e4.ui.workbench.modeling.ISaveHandler.Save;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.fx.core.log.Log;
import org.eclipse.fx.core.log.Logger;
import org.eclipse.fx.ui.controls.stage.Frame;
import org.eclipse.fx.ui.controls.stage.FrameEvent;
import org.eclipse.fx.ui.controls.stage.TrimmedWindow;
import org.eclipse.fx.ui.di.InjectingFXMLLoader;
import org.eclipse.fx.ui.dialogs.Dialog;
import org.eclipse.fx.ui.dialogs.MessageDialog;
import org.eclipse.fx.ui.dialogs.MessageDialog.QuestionCancelResult;
import org.eclipse.fx.ui.panes.FillLayoutPane;
import org.eclipse.fx.ui.services.Constants;
import org.eclipse.fx.ui.services.resources.GraphicsLoader;
import org.eclipse.fx.ui.services.theme.Theme;
import org.eclipse.fx.ui.services.theme.ThemeManager;
import org.eclipse.fx.ui.services.theme.ThemeManager.Registration;
import org.eclipse.fx.ui.workbench.fx.EMFUri;
import org.eclipse.fx.ui.workbench.fx.key.KeyBindingDispatcher;
import org.eclipse.fx.ui.workbench.renderers.base.BaseRenderer;
import org.eclipse.fx.ui.workbench.renderers.base.BaseWindowRenderer;
import org.eclipse.fx.ui.workbench.renderers.base.services.WindowTransitionService;
import org.eclipse.fx.ui.workbench.renderers.base.services.WindowTransitionService.AnimationDelegate;
import org.eclipse.fx.ui.workbench.renderers.base.widget.WCallback;
import org.eclipse.fx.ui.workbench.renderers.base.widget.WLayoutedWidget;
import org.eclipse.fx.ui.workbench.renderers.base.widget.WWidget;
import org.eclipse.fx.ui.workbench.renderers.base.widget.WWindow;
import org.eclipse.fx.ui.workbench.renderers.fx.internal.Messages;
import org.eclipse.fx.ui.workbench.renderers.fx.internal.MultiMessageDialog;
import org.eclipse.fx.ui.workbench.renderers.fx.internal.MultiMessageDialogContent;
import org.eclipse.fx.ui.workbench.renderers.fx.internal.Row;
import org.eclipse.fx.ui.workbench.renderers.fx.services.LightweightDialogTransitionService;
import org.eclipse.fx.ui.workbench.renderers.fx.widget.WLayoutedWidgetImpl;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.osgi.service.localization.BundleLocalization;
import org.osgi.framework.Bundle;
import com.sun.javafx.tk.Toolkit;
import javafx.application.ConditionalFeature;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Modality;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
/**
* Default implementation of a window renderer
*/
@SuppressWarnings("restriction")
public class DefWindowRenderer extends BaseWindowRenderer<Stage> {
private static final String CSS_TRIM_CONTAINER = "window-trim-container"; //$NON-NLS-1$
private static final String CSS_CONTENT_CONTAINER = "window-content-container"; //$NON-NLS-1$
@Inject
@Translation
@NonNull
Messages messages;
@SuppressWarnings("null")
@Override
@NonNull
protected List<@NonNull Save> promptToSave(@NonNull MWindow element, @NonNull Collection<MPart> dirtyParts, @NonNull WWindow<Stage> widget) {
Save[] response = new Save[dirtyParts.size()];
IEclipseContext modelContext = getModelContext(element);
if (modelContext == null) {
getLogger().error("Model context should not be null at this point"); //$NON-NLS-1$
Arrays.fill(response, Save.CANCEL);
return Arrays.asList(response);
}
GraphicsLoader graphicsLoader = modelContext.get(GraphicsLoader.class);
if( element.getTags().contains("efx-lightweight-dialogs") ) { //$NON-NLS-1$
Arrays.fill(response, Save.CANCEL);
MultiMessageDialogContent multiMessageDialogContent = new MultiMessageDialogContent(this.messages.DefWindowRenderer_MultiMessageDialog_Message, dirtyParts, graphicsLoader);
org.eclipse.fx.ui.controls.dialog.Dialog d = new org.eclipse.fx.ui.controls.dialog.Dialog(multiMessageDialogContent, this.messages.DefWindowRenderer_MultiMessageDialog_Title) {
@Override
protected void handleOk() {
List<MPart> parts = new ArrayList<MPart>();
for (Row r : multiMessageDialogContent.tabView.getItems()) {
if (r.isSelected()) {
parts.add(r.element.get());
}
}
Arrays.fill(response, Save.NO);
for (MPart p : parts) {
response[parts.indexOf(p)] = Save.YES;
}
super.handleOk();
}
};
d.getButtonList().addAll(d.createOKButton(), d.createCancelButton());
((WWindowImpl)element.getWidget()).setDialog(d);
String id = UUID.randomUUID().toString();
d.addEventHandler(FrameEvent.CLOSED, (e) -> Toolkit.getToolkit().exitNestedEventLoop(id, null));
Toolkit.getToolkit().enterNestedEventLoop(id);
((WWindowImpl)element.getWidget()).setDialog(null);
} else {
MultiMessageDialog d = new MultiMessageDialog((Stage) widget.getWidget(), dirtyParts, graphicsLoader, this.messages.DefWindowRenderer_MultiMessageDialog_Title, this.messages.DefWindowRenderer_MultiMessageDialog_Message);
if (d.open() == Dialog.OK_BUTTON) {
List<MPart> parts = d.getSelectedParts();
Arrays.fill(response, Save.NO);
for (MPart p : parts) {
response[parts.indexOf(p)] = Save.YES;
}
} else {
Arrays.fill(response, Save.CANCEL);
}
}
return Arrays.asList(response);
}
@Override
protected Save promptToSave(MWindow element, MPart dirtyPart, WWindow<Stage> widget) {
if( element.getTags().contains("efx-lightweight-dialogs") ) { //$NON-NLS-1$
org.eclipse.fx.ui.controls.dialog.MessageDialog.QuestionCancelResult r = org.eclipse.fx.ui.controls.dialog.MessageDialog.openQuestionCancelDialog(
this.messages.DefWindowRenderer_promptToSave_Title,
this.messages.DefWindowRenderer_promptToSave_Message(dirtyPart.getLocalizedLabel()),
(d) -> {
((WWindowImpl)element.getWidget()).setDialog(d);
});
((WWindowImpl)element.getWidget()).setDialog(null);
switch (r) {
case CANCEL:
return Save.CANCEL;
case NO:
return Save.NO;
case YES:
return Save.YES;
}
return Save.CANCEL;
} else {
QuestionCancelResult r = MessageDialog.openQuestionCancelDialog((Stage) widget.getWidget(), this.messages.DefWindowRenderer_promptToSave_Title, this.messages.DefWindowRenderer_promptToSave_Message(dirtyPart.getLocalizedLabel()));
switch (r) {
case CANCEL:
return Save.CANCEL;
case NO:
return Save.NO;
case YES:
return Save.YES;
}
return Save.CANCEL;
}
}
@Override
protected Class<? extends WWindow<Stage>> getWidgetClass(MWindow window) {
return WWindowImpl.class;
}
/**
* Default implementation of a window
*/
public static class WWindowImpl extends WLayoutedWidgetImpl<Stage, Pane, MWindow>implements WWindow<Stage> {
private boolean support3d;
private Pane rootPane;
private BorderPane trimPane;
private FillLayoutPane contentPane;
private KeyBindingDispatcher dispatcher;
private BorderPane decoratorPane;
WindowResizeButton windowResizeButton;
Stage stage;
@Inject
@Optional
ThemeManager themeManager;
private Registration sceneRegistration;
private String decorationFXML;
private String rootFXML;
private boolean fullscreen;
IEclipseContext modelContext;
@NonNull
MWindow mWindow;
@Inject
TranslationService translationService;
@Inject
@NonNull
BundleLocalization localizationService; // FIXME We should get rid of
// this
@Inject
@Optional
WindowTransitionService<Stage> windowTransitionService;
@Inject
@Optional
LightweightDialogTransitionService dialogTransitionService;
@Inject
private GraphicsLoader graphicsLoader;
@Inject
private IEventBroker eventBroker;
boolean initDone;
private boolean undecorated;
private StageStyle stageStyle;
IEclipseContext applicationContext;
private List<WWindow<?>> windows = new ArrayList<>();
WCallback<WWindow<Stage>, Boolean> onCloseCallback;
private boolean maximizedShell;
List<WWidget<?>> lastActivationTree = new ArrayList<WWidget<?>>();
List<WWidget<?>> queuedTree = new ArrayList<WWidget<?>>();
private static final String KEY_SCENE_3D_DEPRECATED = "fx.scene.3d"; //$NON-NLS-1$
private static final String KEY_SCENE_3D = "efx.window.scene.3d"; //$NON-NLS-1$
private static final String KEY_STAGE_DECORATION_DEPRECATED = "fx.stage.decoration"; //$NON-NLS-1$
private static final String KEY_STAGE_DECORATION = "efx.window.decoration.fxml"; //$NON-NLS-1$
private static final String KEY_STAGE_STYLE = "efx.window.stagestyle"; //$NON-NLS-1$
private static final String KEY_STAGE_MODALITY = "efx.window.stagemodality"; //$NON-NLS-1$
private static final String KEY_STAGE_UNDECORATED_DEPRECATED = "efx.window.undecorated"; //$NON-NLS-1$
private static final String KEY_STAGE_ROOT_CONTENT = "efx.window.root.fxml"; //$NON-NLS-1$
private StackPane overlayContainer;
/**
* Create a new window
*
* @param mWindow
* the window model element
* @param dispatcher
* the keybinding dispatcher
* @param application
* the application model element
* @param logger
* the logger
*/
@Inject
public WWindowImpl(@NonNull @Named(BaseRenderer.CONTEXT_DOM_ELEMENT) MWindow mWindow, @Optional KeyBindingDispatcher dispatcher, MApplication application, @Log Logger logger) {
this.mWindow = mWindow;
this.applicationContext = application.getContext();
if (mWindow.getPersistedState().get(KEY_SCENE_3D_DEPRECATED) != null) {
logger.warning("Usage of deprecated persisted state 'fx.scene.3d' please use 'efx.window.scene.3d' instead."); //$NON-NLS-1$
this.support3d = Boolean.parseBoolean(mWindow.getPersistedState().get(KEY_SCENE_3D_DEPRECATED));
} else {
this.support3d = Boolean.parseBoolean(mWindow.getPersistedState().get(KEY_SCENE_3D));
}
this.dispatcher = dispatcher;
this.modelContext = mWindow.getContext();
this.decorationFXML = mWindow.getPersistedState().get(KEY_STAGE_DECORATION_DEPRECATED);
if (this.decorationFXML == null) {
this.decorationFXML = mWindow.getPersistedState().get(KEY_STAGE_DECORATION);
} else {
logger.warning("Useage of deprecated persisted state 'fx.stage.decoration' please use 'efx.window.decoration.fxml' instead."); //$NON-NLS-1$
}
if (mWindow.getPersistedState().get(KEY_STAGE_STYLE) != null) {
this.stageStyle = StageStyle.valueOf(mWindow.getPersistedState().get(KEY_STAGE_STYLE));
} else if (mWindow.getPersistedState().get(KEY_STAGE_UNDECORATED_DEPRECATED) != null) {
logger.warning("Usage of deprecated persisted state 'efx.window.undecorated' please use 'efx.window.stagestyle'"); //$NON-NLS-1$
this.undecorated = Boolean.parseBoolean(mWindow.getPersistedState().get(KEY_STAGE_UNDECORATED_DEPRECATED));
}
this.rootFXML = mWindow.getPersistedState().get(KEY_STAGE_ROOT_CONTENT);
if (this.decorationFXML != null && this.rootFXML != null) {
logger.warning("You've specified a decorationXML and a rootFXML. Only rootFXML is used"); //$NON-NLS-1$
}
}
@Override
@PostConstruct
protected void init() {
this.initDone = true;
super.init();
}
@Override
protected void doCleanup() {
super.doCleanup();
this.sceneRegistration.dispose();
}
private static MWindow findParent(final EObject tmp) {
EObject e = tmp;
if (e.eContainer() instanceof MApplication) {
return null;
}
do {
e = e.eContainer();
if (e instanceof MWindow) {
return (MWindow) e;
}
} while (e.eContainer() != null);
return null;
}
@Override
public void setDialog(Object dialogNode) {
@NonNull
Pane staticLayoutNode = (@NonNull Pane) getStaticLayoutNode();
if (dialogNode == null) {
if (this.overlayContainer != null) {
if( this.dialogTransitionService != null ) {
this.dialogTransitionService.hideDialog(this.mWindow, staticLayoutNode, this.overlayContainer, this.overlayContainer, this.overlayContainer.getChildren().size() == 1 ? this.overlayContainer.getChildren().get(0) : null, () -> {
((Pane) staticLayoutNode).getChildren().remove(this.overlayContainer);
this.overlayContainer.getChildren().clear();
});
} else {
((Pane) staticLayoutNode).getChildren().remove(this.overlayContainer);
this.overlayContainer.getChildren().clear();
}
}
} else {
if (this.overlayContainer == null) {
this.overlayContainer = new StackPane() {
@Override
protected void layoutChildren() {
Insets insets = getInsets();
final double w = getWidth() - insets.getLeft() - insets.getRight();
final double h = getHeight() - insets.getTop() - insets.getBottom();
for( Node n : getManagedChildren() ) {
double x,y;
n.autosize();
if( n instanceof Region ) {
x = (w / 2) - (Math.min(w,((Region) n).getWidth()) / 2);
y = (h / 2) - (Math.min(h,((Region) n).getHeight()) / 2);
} else {
x = (w / 2) - (Math.min(w,n.prefWidth(-1)) / 2);
y = (h / 2) - (Math.min(h,n.prefHeight(-1)) / 2);
}
n.relocate(x, y);
}
}
};
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);
this.overlayContainer.layout();
((Pane) staticLayoutNode).getChildren().add(this.overlayContainer);
if( this.dialogTransitionService != null ) {
this.dialogTransitionService.showDialog(this.mWindow, staticLayoutNode, this.overlayContainer, this.overlayContainer, (Node)dialogNode, null);
}
}
}
@Override
protected Stage createWidget() {
Stage stage = new Stage();
this.stage = stage;
this.modelContext.set(E4Workbench.LOCAL_ACTIVE_SHELL, stage);
MWindow parent = findParent((EObject) this.mWindow);
if (parent != null) {
this.stage.initOwner((Window) ((WWindow<?>) parent.getWidget()).getWidget());
}
this.stage.setOnCloseRequest(this::handleOnCloseRequest);
this.stage.focusedProperty().addListener(this::handledFocus);
this.stage.setFullScreen(this.fullscreen);
this.stage.maximizedProperty().addListener((o) -> {
if( this.stage.isMaximized() ) {
this.mWindow.getTags().add(BaseWindowRenderer.TAG_SHELLMAXIMIZED);
} else {
this.mWindow.getTags().remove(BaseWindowRenderer.TAG_SHELLMAXIMIZED);
}
});
if( this.mWindow.getPersistedState().containsKey(KEY_STAGE_MODALITY) ) {
this.stage.initModality(Modality.valueOf(this.mWindow.getPersistedState().get(KEY_STAGE_MODALITY)));
}
this.stage.fullScreenProperty().addListener(this::handleFullscreen);
if (this.dispatcher != null) {
this.stage.addEventFilter(KeyEvent.KEY_PRESSED, this.dispatcher.getKeyHandler());
}
this.trimPane = new BorderPane();
this.trimPane.getStyleClass().add(CSS_TRIM_CONTAINER);
this.contentPane = new FillLayoutPane();
this.contentPane.getStyleClass().add(CSS_CONTENT_CONTAINER);
this.trimPane.setCenter(this.contentPane);
if (this.rootFXML != null) {
this.rootPane = createRootContainer(stage);
((org.eclipse.fx.ui.controls.stage.Frame) this.rootPane).setClientArea(this.trimPane);
} else {
BorderPane rootPane = new BorderPane() {
@Override
protected void layoutChildren() {
super.layoutChildren();
if (WWindowImpl.this.windowResizeButton != null) {
WWindowImpl.this.windowResizeButton.autosize();
WWindowImpl.this.windowResizeButton.setLayoutX(getWidth() - WWindowImpl.this.windowResizeButton.getLayoutBounds().getWidth());
WWindowImpl.this.windowResizeButton.setLayoutY(getHeight() - WWindowImpl.this.windowResizeButton.getLayoutBounds().getHeight());
}
}
};
this.rootPane = rootPane;
rootPane.setCenter(this.trimPane);
if (this.decorationFXML != null) {
this.windowResizeButton = new WindowResizeButton(this.stage, 50, 50);
this.decoratorPane = new BorderPane();
this.decoratorPane.setTop(createTopDecoration(this.stage));
rootPane.setTop(this.decoratorPane);
}
}
if (this.stageStyle != null) {
this.stage.initStyle(this.stageStyle);
} else if (this.undecorated) {
this.stage.initStyle(StageStyle.UNDECORATED);
}
// TODO Should we create the scene on show???
Scene s;
if (this.support3d && Platform.isSupported(ConditionalFeature.SCENE3D)) {
s = new Scene(this.rootPane, this.mWindow.getWidth(), this.mWindow.getHeight(), true);
s.setCamera(new PerspectiveCamera());
} else {
s = new Scene(this.rootPane, this.mWindow.getWidth(), this.mWindow.getHeight());
}
if( this.stage.getStyle() == StageStyle.TRANSPARENT ) {
s.setFill(Color.TRANSPARENT);
}
// Add a css which sets defaults
{
URL url = getClass().getClassLoader().getResource("css/efx-default.css"); //$NON-NLS-1$
if (url != null) {
s.getStylesheets().add(url.toExternalForm());
} else {
this.logger.error("Unable to load css 'css/efx-default.css'"); //$NON-NLS-1$
}
}
s.focusOwnerProperty().addListener(this::handleFocusOwner);
if (this.themeManager != null) {
Theme theme = this.themeManager.getCurrentTheme();
if (theme != null) {
List<String> sUrls = new ArrayList<String>();
for (URL url : theme.getStylesheetURL()) {
sUrls.add(url.toExternalForm());
}
s.getStylesheets().addAll(sUrls);
}
this.sceneRegistration = this.themeManager.registerScene(s);
}
if (this.windowResizeButton != null) {
this.rootPane.getChildren().add(this.windowResizeButton);
}
this.stage.setScene(s);
this.modelContext.set(Stage.class, this.stage);
this.modelContext.set(Scene.class, s);
return stage;
}
private void handleOnCloseRequest(WindowEvent event) {
if (this.onCloseCallback != null) {
if (!Boolean.TRUE.equals(this.onCloseCallback.call(this))) {
event.consume();
}
}
}
@SuppressWarnings("null")
@Override
public @NonNull Node getStaticLayoutNode() {
return this.rootPane;
}
private void handleFullscreen(ObservableValue<? extends Boolean> obs, Boolean oldValue, Boolean newValue) {
this.mWindow.getPersistedState().put(BaseWindowRenderer.KEY_FULL_SCREEN, newValue.toString());
}
private void handledFocus(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (newValue.booleanValue()) {
activateWindow();
} else {
deactivate();
}
}
private void activateWindow() {
if (this.stage.getScene() != null) {
this.applicationContext.set(Constants.APP_FOCUS_NODE, this.stage.getScene().getFocusOwner());
}
if (!isActive()) {
activate();
this.eventBroker.send(Constants.WINDOW_ACTIVATED, getDomElement());
}
}
@Override
public void activate() {
super.activate();
if( this.stage != null && ! this.stage.isFocused() ) {
this.stage.requestFocus();
}
}
private void handleFocusOwner(ObservableValue<? extends Node> observable, Node oldValue, Node _newValue) {
Node newValue = _newValue;
WWindowImpl.this.modelContext.set(Constants.WINDOW_FOCUS_NODE, newValue);
if (WWindowImpl.this.stage.isFocused()) {
WWindowImpl.this.applicationContext.set(Constants.APP_FOCUS_NODE, newValue);
}
if (newValue != null) {
final List<WWidget<?>> activationTree = new ArrayList<WWidget<?>>();
do {
if (newValue.getUserData() instanceof WWidget<?>) {
activationTree.add((WWidget<?>) newValue.getUserData());
}
} while ((newValue = newValue.getParent()) != null);
if (!this.lastActivationTree.equals(activationTree)) {
final List<WWidget<?>> oldTreeReversed = new ArrayList<WWidget<?>>(this.lastActivationTree);
final List<WWidget<?>> newTreeReversed = new ArrayList<WWidget<?>>(activationTree);
Collections.reverse(oldTreeReversed);
Collections.reverse(newTreeReversed);
Iterator<WWidget<?>> it = newTreeReversed.iterator();
while (it.hasNext()) {
if (!oldTreeReversed.isEmpty()) {
if (oldTreeReversed.get(0) == it.next()) {
oldTreeReversed.remove(0);
it.remove();
} else {
break;
}
} else {
break;
}
}
Collections.reverse(oldTreeReversed);
Collections.reverse(newTreeReversed);
this.queuedTree = activationTree;
// Delay the execution maybe there's an intermediate
// state we are not interested in
// http://javafx-jira.kenai.com/browse/RT-24069
Platform.runLater(new Runnable() {
@Override
public void run() {
if (WWindowImpl.this.queuedTree == activationTree) {
WWindowImpl.this.lastActivationTree = activationTree;
for (WWidget<?> w : oldTreeReversed) {
w.deactivate();
}
for (WWidget<?> w : newTreeReversed) {
w.activate();
}
}
}
});
}
}
}
private Pane createRootContainer(final Stage stage) {
URI uri = URI.createURI(this.rootFXML);
if (uri != null) {
stage.initStyle(StageStyle.UNDECORATED);
Bundle b = org.eclipse.core.runtime.Platform.getBundle(uri.segment(1));
if (b != null) {
try {
StringBuilder sb = new StringBuilder();
for (int i = 2; i < uri.segmentCount(); i++) {
if (sb.length() != 0) {
sb.append("/"); //$NON-NLS-1$
}
sb.append(uri.segment(i));
}
@SuppressWarnings("null")
InjectingFXMLLoader<Node> loader = InjectingFXMLLoader.create(this.modelContext, b, sb.toString());
ResourceBundle resourceBundle = this.localizationService.getLocalization(b, Locale.getDefault().toString());
if (resourceBundle != null) {
loader.resourceBundle(resourceBundle);
}
Pane load = (Pane) loader.load();
ContextInjectionFactory.inject(load, this.modelContext);
return load;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return null;
}
private Node createTopDecoration(final Stage stage) {
URI uri = URI.createURI(this.decorationFXML);
if (uri != null) {
stage.initStyle(StageStyle.UNDECORATED);
Bundle b = org.eclipse.core.runtime.Platform.getBundle(uri.segment(1));
if (b != null) {
try {
StringBuilder sb = new StringBuilder();
for (int i = 2; i < uri.segmentCount(); i++) {
if (sb.length() != 0) {
sb.append("/"); //$NON-NLS-1$
}
sb.append(uri.segment(i));
}
@SuppressWarnings("null")
InjectingFXMLLoader<Node> loader = InjectingFXMLLoader.create(this.modelContext, b, sb.toString());
ResourceBundle resourceBundle = this.localizationService.getLocalization(b, Locale.getDefault().toString());
if (resourceBundle != null) {
loader.resourceBundle(resourceBundle);
}
return (Node) loader.load();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return null;
}
@SuppressWarnings("null")
@Override
protected void bindProperties(Stage widget) {
super.bindProperties(widget);
bindProperty(UIEvents.Window.X, widget.xProperty());
bindProperty(UIEvents.Window.Y, widget.yProperty());
bindProperty(UIEvents.Window.WIDTH, widget.widthProperty());
bindProperty(UIEvents.Window.HEIGHT, widget.heightProperty());
}
@Override
public void setMainMenu(WLayoutedWidget<MMenu> menuWidget) {
if (this.decoratorPane == null) {
Node n = null;
if (menuWidget != null) {
n = (Node) menuWidget.getStaticLayoutNode();
}
if (this.rootPane instanceof org.eclipse.fx.ui.controls.stage.Window) {
((org.eclipse.fx.ui.controls.stage.Window) this.rootPane).setMenuBar(n);
} else {
((BorderPane) this.rootPane).setTop(n);
}
} else {
if (menuWidget == null) {
this.decoratorPane.setBottom(null);
} else {
this.decoratorPane.setBottom((Node) menuWidget.getStaticLayoutNode());
}
}
}
@Override
protected Pane getWidgetNode() {
return this.rootPane;
}
@Override
public void setOnCloseCallback(WCallback<WWindow<Stage>, Boolean> onCloseCallback) {
this.onCloseCallback = onCloseCallback;
}
/**
* set a new x coordinate
*
* @param _x
* the new x
*/
@SuppressWarnings("null")
@Inject
public void setX(@Named(UIEvents.Window.X) int _x) {
if (!isPropertyChangeInProgress(UIEvents.Window.X)) {
int x = _x;
if (x == -2147483648) {
x = 0;
}
getWidget().setX(x);
}
}
/**
* set a new y coordinate
*
* @param _y
* the new y
*/
@SuppressWarnings("null")
@Inject
public void setY(@Named(UIEvents.Window.Y) int _y) {
if (!isPropertyChangeInProgress(UIEvents.Window.Y)) {
int y = _y;
if (y == -2147483648) {
y = 0;
}
getWidget().setY(y);
}
}
/**
* set a new width
*
* @param w
* the new width
*/
@SuppressWarnings("null")
@Inject
public void setWidth(@Named(UIEvents.Window.WIDTH) int w) {
if (!isPropertyChangeInProgress(UIEvents.Window.WIDTH)) {
getWidget().setWidth(w);
}
}
/**
* set a new height
*
* @param h
* the new height
*/
@SuppressWarnings("null")
@Inject
public void setHeight(@Named(UIEvents.Window.HEIGHT) int h) {
if (!isPropertyChangeInProgress(UIEvents.Window.HEIGHT)) {
getWidget().setHeight(h);
}
}
/**
* update the visibility of the window
*
* @param visible
* the new visible value
*/
@Inject
public void setVisible(@Named(UIEvents.UIElement.VISIBLE) boolean visible) {
// Skip the init injection because the renderer will take care of
// showing the stage
if (!this.initDone) {
return;
}
if (visible) {
internalShow();
} else {
internalHide();
}
}
/**
* Set the new tags list
*
* @param tags
* the tags
*/
@Inject
public void setTags(@Optional @Named(UIEvents.ApplicationElement.TAGS) List<String> tags) {
if (tags != null) {
this.maximizedShell = tags.contains(BaseWindowRenderer.TAG_SHELLMAXIMIZED);
if (this.stage != null) {
this.stage.setMaximized(this.maximizedShell);
}
}
}
/**
* Make stage shown in fullscreen
*
* @param fullScreen
* the new state
*/
@Inject
public void setFullscreen(@Named(UIEvents.ApplicationElement.PERSISTEDSTATE + "_" + BaseWindowRenderer.KEY_FULL_SCREEN) @Optional String fullScreen) {
if (fullScreen != null) {
this.fullscreen = Boolean.parseBoolean(fullScreen);
if (this.stage != null) {
this.stage.setFullScreen(this.fullscreen);
}
} else {
this.fullscreen = false;
}
}
@Override
public void addStyleClasses(List<String> classnames) {
this.rootPane.getStyleClass().addAll(classnames);
}
@Override
public void setStyleId(String id) {
this.rootPane.setId(id);
}
@Override
public void show() {
internalShow();
getWidget().toFront();
}
@Override
public void close() {
if( this.rootPane instanceof Frame ) {
((Frame)this.rootPane).close();
} else {
getWidget().close();
}
}
@Override
public void addChildWindow(WWindow<?> widget) {
this.windows.add(widget);
if (this.initDone && this.stage.isShowing()) {
Stage s = (Stage) widget.getWidget();
s.show();
}
}
@Override
public void removeChildWindow(@NonNull WWindow<?> widget) {
Stage s = (Stage) widget.getWidget();
s.hide();
this.windows.remove(widget);
}
private void internalShow() {
if (getWidget().isShowing()) {
return;
}
if (this.windowTransitionService != null) {
AnimationDelegate<Stage> delegate = this.windowTransitionService.getShowDelegate(this.mWindow);
if (delegate != null) {
delegate.animate(getWidget(), () -> {
activateWindow();
// Need to delay bit else maximize different things
// don't operation appropiately!
// need to file FX-Bug for that
Platform.runLater(() -> {
this.stage.setMaximized(this.maximizedShell);
} );
this.eventBroker.send(Constants.WINDOW_SHOWN, this.mWindow);
} );
} else {
getWidget().show();
// force activation of the stage see 435273
activateWindow();
// Need to delay bit else maximize different things don't
// operation appropiately!
// need to file FX-Bug for that
Platform.runLater(() -> {
this.stage.setMaximized(this.maximizedShell);
} );
this.eventBroker.send(Constants.WINDOW_SHOWN, this.mWindow);
}
} else {
getWidget().show();
// force activation of the stage see 435273
activateWindow();
// Need to delay bit else maximize different things don't
// operation appropiately!
// need to file FX-Bug for that
Platform.runLater(() -> {
this.stage.setMaximized(this.maximizedShell);
} );
this.eventBroker.send(Constants.WINDOW_SHOWN, this.mWindow);
}
// I don't think sub-windows should be activated
for (WWindow<?> c : this.windows) {
c.show();
this.eventBroker.send(Constants.WINDOW_SHOWN, this.mWindow);
}
// Force the focus back on ourselves
if (this.windows.size() > 0) {
this.stage.requestFocus();
// force activation of the stage see 435273
activateWindow();
}
}
private void internalHide() {
// TODO Do we need to hide them recursively???
if (this.windowTransitionService != null) {
AnimationDelegate<Stage> delegate = this.windowTransitionService.getShowDelegate(this.mWindow);
if (delegate != null) {
delegate.animate(getWidget(), () -> {
this.eventBroker.send(Constants.WINDOW_HIDDEN, this.mWindow);
} );
} else {
getWidget().hide();
this.eventBroker.send(Constants.WINDOW_HIDDEN, this.mWindow);
}
} else {
getWidget().hide();
this.eventBroker.send(Constants.WINDOW_HIDDEN, this.mWindow);
}
}
/**
* Set a new window title
*
* @param title
* the new title
*/
@Inject
public void setTitle(@Named(UIEvents.UILabel.LOCALIZED_LABEL) String title) {
getWidget().setTitle(title);
if (this.rootPane instanceof org.eclipse.fx.ui.controls.stage.Window) {
((org.eclipse.fx.ui.controls.stage.Window) this.rootPane).setTitle(title);
}
}
/**
* Set a window icon. Icons of multiple sizes can be set by separating
* the urls using a ; (semicolon)
*
* @param iconUri
* the new icon
*/
@Inject
public void setImageUrl(@Named(UIEvents.UILabel.ICONURI) @Optional String iconUri) {
if (iconUri != null && !iconUri.isEmpty()) {
String[] split = iconUri.split(";"); //$NON-NLS-1$
List<Image> images = new ArrayList<>();
for (String uri : split) {
@SuppressWarnings("null")
Image img = this.graphicsLoader.getImage(new EMFUri(URI.createURI(uri)));
if (img != null) {
images.add(img);
}
}
getWidget().getIcons().setAll(images);
} else {
getWidget().getIcons().clear();
}
}
@Override
public void setBottomTrim(WLayoutedWidget<MTrimBar> trimBar) {
if (trimBar == null) {
if (this.rootPane instanceof TrimmedWindow) {
((TrimmedWindow) this.rootPane).setBottomTrim(null);
} else {
this.trimPane.setBottom(null);
}
} else {
if (this.rootPane instanceof TrimmedWindow) {
((TrimmedWindow) this.rootPane).setBottomTrim((Node) trimBar.getStaticLayoutNode());
} else {
this.trimPane.setBottom((Node) trimBar.getStaticLayoutNode());
}
}
}
@Override
public void setLeftTrim(WLayoutedWidget<MTrimBar> trimBar) {
if (trimBar == null) {
if (this.rootPane instanceof TrimmedWindow) {
((TrimmedWindow) this.rootPane).setLeftTrim(null);
} else {
this.trimPane.setLeft(null);
}
} else {
if (this.rootPane instanceof TrimmedWindow) {
((TrimmedWindow) this.rootPane).setLeftTrim((Node) trimBar.getStaticLayoutNode());
} else {
this.trimPane.setLeft((Node) trimBar.getStaticLayoutNode());
}
}
}
@Override
public void setRightTrim(WLayoutedWidget<MTrimBar> trimBar) {
if (trimBar == null) {
if (this.rootPane instanceof TrimmedWindow) {
((TrimmedWindow) this.rootPane).setRightTrim(null);
} else {
this.trimPane.setRight(null);
}
} else {
if (this.rootPane instanceof TrimmedWindow) {
((TrimmedWindow) this.rootPane).setRightTrim((Node) trimBar.getStaticLayoutNode());
} else {
this.trimPane.setRight((Node) trimBar.getStaticLayoutNode());
}
}
}
@Override
public void setTopTrim(WLayoutedWidget<MTrimBar> trimBar) {
if (trimBar == null) {
if (this.rootPane instanceof TrimmedWindow) {
((TrimmedWindow) this.rootPane).setTopTrim(null);
} else {
this.trimPane.setTop(null);
}
} else {
if (this.rootPane instanceof TrimmedWindow) {
((TrimmedWindow) this.rootPane).setTopTrim((Node) trimBar.getStaticLayoutNode());
} else {
this.trimPane.setTop((Node) trimBar.getStaticLayoutNode());
}
}
}
@Override
public void addChild(WLayoutedWidget<MWindowElement> widget) {
this.contentPane.getChildren().add((Node) widget.getStaticLayoutNode());
}
@Override
public void removeChild(WLayoutedWidget<MWindowElement> widget) {
this.contentPane.getChildren().remove((Node) widget.getStaticLayoutNode());
}
@Override
public void addChild(int idx, WLayoutedWidget<MWindowElement> widget) {
this.contentPane.getChildren().add(idx, (Node) widget.getStaticLayoutNode());
}
}
static class WindowResizeButton extends Region {
double dragOffsetX;
double dragOffsetY;
final Stage stage;
final double stageMinimumWidth;
final double stageMinimumHeight;
public WindowResizeButton(final Stage stage, final double stageMinimumWidth, final double stageMinimumHeight) {
this.stage = stage;
this.stageMinimumWidth = stageMinimumWidth;
this.stageMinimumHeight = stageMinimumHeight;
setId("window-resize-button"); //$NON-NLS-1$
setPrefSize(11, 11);
setOnMousePressed(this::handleMousePressed);
setOnMouseDragged(this::handleMouseDragged);
}
void handleMousePressed(MouseEvent e) {
this.dragOffsetX = (this.stage.getX() + this.stage.getWidth()) - e.getScreenX();
this.dragOffsetY = (this.stage.getY() + this.stage.getHeight()) - e.getScreenY();
e.consume();
}
void handleMouseDragged(MouseEvent e) {
ObservableList<Screen> screens = Screen.getScreensForRectangle(this.stage.getX(), this.stage.getY(), 1, 1);
final Screen screen;
if (screens.size() > 0) {
screen = Screen.getScreensForRectangle(this.stage.getX(), this.stage.getY(), 1, 1).get(0);
} else {
screen = Screen.getScreensForRectangle(0, 0, 1, 1).get(0);
}
Rectangle2D visualBounds = screen.getVisualBounds();
double maxX = Math.min(visualBounds.getMaxX(), e.getScreenX() + WindowResizeButton.this.dragOffsetX);
double maxY = Math.min(visualBounds.getMaxY(), e.getScreenY() - WindowResizeButton.this.dragOffsetY);
this.stage.setWidth(Math.max(this.stageMinimumWidth, maxX - this.stage.getX()));
this.stage.setHeight(Math.max(this.stageMinimumHeight, maxY - this.stage.getY()));
e.consume();
}
}
}