/******************************************************************************* * 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: * Alexander Nyßen (itemis AG) - initial API and implementation * *******************************************************************************/ package org.eclipse.gef.mvc.fx.handlers; import java.util.Collections; import java.util.List; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.gef.mvc.fx.models.FocusModel; import org.eclipse.gef.mvc.fx.models.SelectionModel; import org.eclipse.gef.mvc.fx.operations.ChangeFocusOperation; import org.eclipse.gef.mvc.fx.operations.ChangeSelectionOperation; import org.eclipse.gef.mvc.fx.operations.DeselectOperation; import org.eclipse.gef.mvc.fx.operations.ITransactionalOperation; import org.eclipse.gef.mvc.fx.operations.SelectOperation; import org.eclipse.gef.mvc.fx.parts.IContentPart; import org.eclipse.gef.mvc.fx.parts.IRootPart; import org.eclipse.gef.mvc.fx.parts.IVisualPart; import org.eclipse.gef.mvc.fx.viewer.IViewer; import com.google.common.reflect.TypeToken; import javafx.collections.ObservableList; import javafx.scene.Node; import javafx.scene.input.MouseEvent; /** * The {@link FocusAndSelectOnClickHandler} is an {@link IOnClickHandler} that * focuses and selects its {@link #getHost() host} by altering the * {@link FocusModel} and the {@link SelectionModel} when the {@link #getHost() * host} is clicked by the mouse. * * @author anyssen * */ public class FocusAndSelectOnClickHandler extends AbstractHandler implements IOnClickHandler { @SuppressWarnings("serial") @Override public void click(MouseEvent e) { // focus and select are only done on single click if (!isFocusAndSelect(e)) { return; } IVisualPart<? extends Node> host = getHost(); IViewer viewer = host.getRoot().getViewer(); SelectionModel selectionModel = viewer.getAdapter(SelectionModel.class); // determine if replacing or extending the selection boolean append = isAppend(e); // perform different changes depending on host type if (host instanceof IContentPart) { IContentPart<? extends Node> contentPart = (IContentPart<? extends Node>) host; // check if the host is the explicit event target if (isRegistered(e.getTarget()) && !isRegisteredForHost(e.getTarget())) { // do not process events for other parts return; } List<IContentPart<? extends Node>> singletonHostList = Collections .<IContentPart<? extends Node>> singletonList(contentPart); // create selection change operation(s) boolean wasDeselected = false; ITransactionalOperation selectionChangeOperation = null; if (selectionModel.isSelected(contentPart)) { if (append) { // deselect the host selectionChangeOperation = new DeselectOperation(viewer, singletonHostList); wasDeselected = true; } } else if (contentPart.isSelectable()) { if (append) { // prepend host to current selection (as new primary) selectionChangeOperation = new SelectOperation(viewer, singletonHostList); } else { // clear old selection, host becomes the only selected selectionChangeOperation = new ChangeSelectionOperation( viewer, singletonHostList); } } // execute selection changes if (selectionChangeOperation != null) { try { viewer.getDomain().execute(selectionChangeOperation, new NullProgressMonitor()); } catch (ExecutionException e1) { throw new IllegalStateException(e1); } } // change focus depending on selection changes ChangeFocusOperation changeFocusOperation = null; ObservableList<IContentPart<? extends Node>> selection = selectionModel .getSelectionUnmodifiable(); if (wasDeselected && selection.isEmpty()) { // unfocus when the only selected part was deselected changeFocusOperation = new ChangeFocusOperation(viewer, null); } else { // focus new primary selection IContentPart<? extends Node> primarySelection = selection .get(0); if (primarySelection.isFocusable()) { FocusModel focusModel = viewer .getAdapter(new TypeToken<FocusModel>() { }); if (focusModel.getFocus() == primarySelection) { primarySelection.getVisual().requestFocus(); } else { changeFocusOperation = new ChangeFocusOperation(viewer, primarySelection); } } } // execute focus change if (changeFocusOperation != null) { try { viewer.getDomain().execute(changeFocusOperation, new NullProgressMonitor()); } catch (ExecutionException e1) { throw new IllegalStateException(e1); } } } else if (host instanceof IRootPart) { // check if click on background (either one of the root visuals, or // an unregistered visual) if (!isRegistered(e.getTarget()) || isRegisteredForHost(e.getTarget())) { // check if append-modifier is pressed if (append) { // do nothing return; } // unset focus and clear selection try { FocusModel focusModel = viewer .getAdapter(new TypeToken<FocusModel>() { }); if (focusModel.getFocus() == null) { // no focus change needed, only update feedback viewer.getRootPart().getVisual().requestFocus(); } else { // change focus, will update feedback via behavior viewer.getDomain().execute( new ChangeFocusOperation(viewer, null), new NullProgressMonitor()); } viewer.getDomain() .execute( new DeselectOperation(viewer, selectionModel .getSelectionUnmodifiable()), new NullProgressMonitor()); } catch (ExecutionException e1) { throw new IllegalStateException(e1); } } } } /** * Returns <code>true</code> if the selection should be extended according * to the given {@link MouseEvent}, <code>false</code> if it should be * replaced. * * @param e * The {@link MouseEvent} for which to determine if the selection * is to be replaced or extended. * @return <code>true</code> if the selection should be extended according * to the given {@link MouseEvent}, <code>false</code> if it should * be replaced. */ protected boolean isAppend(MouseEvent e) { return e.isShortcutDown(); } /** * Returns <code>true</code> if the given {@link MouseEvent} should trigger * focus and select. Otherwise returns <code>false</code>. Per default * returns <code>true</code> if a single mouse click is performed. * * @param event * The {@link MouseEvent} in question. * @return <code>true</code> if the given {@link MouseEvent} should trigger * focus and select, otherwise <code>false</code>. */ protected boolean isFocusAndSelect(MouseEvent event) { return event.getClickCount() <= 1; } }