/*******************************************************************************
* Copyright (c) 2014, 2016 itemis AG 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:
* Matthias Wienand (itemis AG) - initial API and implementation
* Alexander Nyßen (itemis AG) - contribution for Bugzilla #450231
*
*******************************************************************************/
package org.eclipse.gef.mvc.fx.behaviors;
import java.util.Collection;
import java.util.List;
import org.eclipse.gef.fx.nodes.InfiniteCanvas;
import org.eclipse.gef.mvc.fx.models.FocusModel;
import org.eclipse.gef.mvc.fx.parts.IContentPart;
import org.eclipse.gef.mvc.fx.parts.IFeedbackPartFactory;
import org.eclipse.gef.mvc.fx.parts.IVisualPart;
import org.eclipse.gef.mvc.fx.viewer.IViewer;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Node;
/**
* The {@link FocusBehavior} can be registered on an {@link IVisualPart} to
* transfer the focus information from the {@link FocusModel} to the part's
* visualization. It will assign keyboard focus to the visualization if the part
* is focused, and it will display focus feedback around the visualization to
* indicate that the part has focus.
*
* @author mwienand
* @author anyssen
*
*/
// TODO: viewer focus needs to be handled inside viewer directly and should be
// moved out of here
public class FocusBehavior extends AbstractBehavior {
/**
* Defines the default CSS styling for the {@link InfiniteCanvas}: no
* background, no border.
*/
public static final String UNFOCUSED_STYLE = "-fx-background-insets: 0; -fx-padding: 0; -fx-background-color: rgba(0,0,0,0);";
/**
* Defines the CSS styling that is used to highlight a focused viewer.
*/
public static final String FOCUSED_STYLE = "-fx-background-insets: 0; -fx-padding: 0; -fx-background-color: rgba(0,0,0,0); -fx-border-color: #8ec0fc; -fx-border-width: 2;";
/**
* The adapter role for the "focus" {@link IFeedbackPartFactory}.
*/
public static final String FOCUS_FEEDBACK_PART_FACTORY = "FOCUS_FEEDBACK_PART_FACTORY";
private IContentPart<? extends Node> focusPart;
private boolean isViewerFocused;
private ChangeListener<IContentPart<? extends Node>> focusObserver = new ChangeListener<IContentPart<? extends Node>>() {
@Override
public void changed(
ObservableValue<? extends IContentPart<? extends Node>> observable,
IContentPart<? extends Node> oldValue,
IContentPart<? extends Node> newValue) {
if (oldValue != null && hasFeedback(oldValue)) {
removeFeedback(oldValue);
}
focusPart = newValue;
applyFocusToVisual();
refreshFocusFeedback();
}
};
private FocusModel focusModel;
private ChangeListener<? super Boolean> viewerFocusedListener = new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable,
Boolean oldValue, Boolean newValue) {
isViewerFocused = newValue;
refreshFocusFeedback();
}
};
private IViewer viewer;
private boolean hasViewerFocusedFeedback;
@Override
protected void addAnchoreds(
Collection<? extends IVisualPart<? extends Node>> targets,
List<? extends IVisualPart<? extends Node>> anchoreds) {
super.addAnchoreds(targets, anchoreds, 0);
}
/**
* Adds viewer focused feedback.
*/
protected void addViewerFocusedFeedback() {
viewer.getCanvas().setStyle(FOCUSED_STYLE);
hasViewerFocusedFeedback = true;
}
/**
* Transfers the keyboard focus to JavaFX, i.e. calls
* {@link Node#requestFocus()} on the visual of the focus part, or on the
* root visual if no part is focused.
*/
protected void applyFocusToVisual() {
if (focusPart == null) {
viewer.getRootPart().getVisual().requestFocus();
} else {
focusPart.getVisual().requestFocus();
}
}
@Override
protected void doActivate() {
super.doActivate();
viewer = getHost().getRoot().getViewer();
focusModel = viewer.getAdapter(FocusModel.class);
if (focusModel == null) {
throw new IllegalStateException(
"Unable to retrieve FocusModel viewer adapter. Please check your adapter bindings.");
}
viewer.viewerFocusedProperty().addListener(viewerFocusedListener);
focusModel.focusProperty().addListener(focusObserver);
focusPart = focusModel.getFocus();
isViewerFocused = viewer.isViewerFocused();
refreshFocusFeedback();
}
@Override
protected void doDeactivate() {
focusModel.focusProperty().removeListener(focusObserver);
viewer.viewerFocusedProperty().removeListener(viewerFocusedListener);
focusPart = null;
isViewerFocused = false;
refreshFocusFeedback();
super.doDeactivate();
}
@Override
protected IFeedbackPartFactory getFeedbackPartFactory(IViewer viewer) {
return getFeedbackPartFactory(viewer, FOCUS_FEEDBACK_PART_FACTORY);
}
/**
* Returns the {@link FocusModel} at which this {@link FocusBehavior} is
* registered for changes.
*
* @return The {@link FocusModel} at which this {@link FocusBehavior} is
* registered for changes.
*/
protected FocusModel getFocusModel() {
return focusModel;
}
/**
* Refreshes focus feedback, i.e. adds or removes feedback.
*/
protected void refreshFocusFeedback() {
// refresh viewer focus feedback
boolean showViewerFocusFeedback = isViewerFocused && focusPart == null;
if (hasViewerFocusedFeedback && !showViewerFocusFeedback) {
removeViewerFocusedFeedback();
} else if (!hasViewerFocusedFeedback && showViewerFocusFeedback) {
addViewerFocusedFeedback();
}
// refresh focused part focus feedback
if (focusPart != null) {
boolean hasFeedback = hasFeedback(focusPart);
if (hasFeedback && !isViewerFocused) {
removeFeedback(focusPart);
} else if (!hasFeedback && isViewerFocused) {
addFeedback(focusPart);
}
}
}
/**
* Removes viewer focused feedback.
*/
protected void removeViewerFocusedFeedback() {
viewer.getCanvas().setStyle(UNFOCUSED_STYLE);
hasViewerFocusedFeedback = false;
}
}