blob: 383734e28cf6ae7dc0beac9a0ccb7ee6cc706e3d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2010 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.gef;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import org.eclipse.swt.widgets.Control;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.gef.ui.parts.AbstractEditPartViewer;
/**
* Manages a viewer's selection model. Selection management includes
* representing a form of selection which is available to clients of a viewer as
* an ISelection. It also includes managing the notion of focus, which is
* closely tied to the current selection. The selection manager provides the
* mechanism for modifying the selection and any validation.
* <P>
* WARNING: Subclassing this class is considered experimental at this point.
*
* @since 3.2
*/
public class SelectionManager {
private EditPart focusPart;
private Runnable notifier;
private List selection;
private EditPartViewer viewer;
/**
* Default Constructor
*
* @since 3.2
*/
protected SelectionManager() {
}
/**
* Creates the default implementation for a selection manager.
*
* @return the default selection manager
* @since 3.2
*/
public static SelectionManager createDefault() {
return new SelectionManager();
}
/**
* Appends the <code>EditPart</code> to the current selection. The EditPart
* becomes the new primary selection. Fires selection changed to all
* {@link org.eclipse.jface.viewers.ISelectionChangedListener}s.
*
* @param editpart
* the EditPart to append
* @since 3.2
*/
public void appendSelection(EditPart editpart) {
if (editpart != getFocus()) {
// Fix for 458416: adjust the focus through the viewer only (to give
// AbstractEditPartViewer a change to update its focusPart field).
// AbstractEditPartViewer#setFocus() should call back setFocus(null)
// here, so both focus part values should stay in sync.
viewer.setFocus(null);
}
if (!selection.isEmpty()) {
EditPart primary = (EditPart) selection.get(selection.size() - 1);
primary.setSelected(EditPart.SELECTED);
}
// if the editpart is already in the list, re-order it to be the last
// one
selection.remove(editpart);
selection.add(editpart);
editpart.setSelected(EditPart.SELECTED_PRIMARY);
fireSelectionChanged();
}
/**
* Removes the <code>EditPart</code> from the current selection.
*
* @param editpart
* the editpart
* @since 3.2
*/
public void deselect(EditPart editpart) {
editpart.setSelected(EditPart.SELECTED_NONE);
selection.remove(editpart);
if (!selection.isEmpty()) {
// IMPORTANT: it may (temporarily) happen that the selection list
// contains edit parts, which are not selectable (any more) when
// this method gets called. Consider e.g. that the selectable state
// of an edit part may bound to its activation state (by overwriting
// isSelectable()); in this case, when deleting a selected edit part
// and its primary selected child simultaneously, the parent edit
// part may have already become non selectable, while not having
// been deselected yet (because deselection is performed within
// removeNotify() after deactivation), when the child edit part gets
// deselected. Therefore, we do not simply choose the last edit part
// in the list as the new primary selection, but reverse-search the
// list for the first that is (still) selectable.
for (int i = selection.size() - 1; i >= 0; i--) {
EditPart primaryCandidate = (EditPart) selection.get(i);
if (primaryCandidate.isSelectable()) {
primaryCandidate.setSelected(EditPart.SELECTED_PRIMARY);
break;
}
}
}
fireSelectionChanged();
}
/**
* Deselects everything.
*
* @since 3.2
*/
public void deselectAll() {
EditPart part;
// Fix for 458416: adjust the focus through the viewer only (to give
// AbstractEditPartViewer a change to update its focusPart field).
// AbstractEditPartViewer#setFocus() should call back setFocus(null)
// here, so both focus part values should stay in sync.
viewer.setFocus(null);
for (int i = 0; i < selection.size(); i++) {
part = (EditPart) selection.get(i);
part.setSelected(EditPart.SELECTED_NONE);
}
selection.clear();
fireSelectionChanged();
}
/**
* Causes the viewer to fire selection changed notification to all
* listeners.
*
* @since 3.2
*/
protected final void fireSelectionChanged() {
notifier.run();
}
/**
* Returns the focus editpart.
*
* @return the focus editpart
* @since 3.2
*/
protected EditPart getFocus() {
return focusPart;
}
/**
* Returns the current selection.
*
* @return the selection
* @since 3.2
*/
public ISelection getSelection() {
if (selection.isEmpty() && viewer.getContents() != null)
return new StructuredSelection(viewer.getContents());
return new StructuredSelection(selection);
}
/**
* Returns <code>null</code> or the viewer whose selection is managed.
*
* @return <code>null</code> or the viewer
* @since 3.2
*/
protected EditPartViewer getViewer() {
return viewer;
}
/**
* For internal use only. This API is subject to change.
*
* @param control
* the control
* @since 3.2
*/
public void internalHookControl(Control control) {
}
/**
* For internal use only. This API is subject to change.
*
* @since 3.2
*/
public void internalUninstall() {
}
/**
* Provides a hook for when the viewer has been set.
*
* @param viewer
* the viewer.
* @since 3.2
*/
protected void hookViewer(EditPartViewer viewer) {
}
/**
* For internal use only.
*
* @param viewer
* viewer
* @param selection
* selection
* @param notifier
* notifier
* @since 3.2
*/
public void internalInitialize(EditPartViewer viewer, List selection,
Runnable notifier) {
this.viewer = viewer;
this.selection = selection;
this.notifier = notifier;
hookViewer(viewer);
}
/**
* Sets the focus part.
*
* @param part
* the focus part
* @since 3.2
*/
public void setFocus(EditPart part) {
// Fix for 458416: AbstractEditPartViewer provides a protected,
// deprecated focusPart field, which might get out of sync with the
// focusPart here, if this method is called directly (and not though the
// EditPartViewer#setFocus() delegation). We need to make sure it stays
// synchronized even in this case, so we update the field value
// accordingly
if (viewer instanceof AbstractEditPartViewer) {
try {
Field focusPartField = AbstractEditPartViewer.class
.getDeclaredField("focusPart"); //$NON-NLS-1$
focusPartField.setAccessible(true);
if (focusPartField.get(viewer) != part) {
focusPartField.set(viewer, part);
}
} catch (Exception e) {
throw new IllegalStateException(
"Workaround for bug #458416 became ineffective"); //$NON-NLS-1$
}
}
if (focusPart == part)
return;
if (focusPart != null)
focusPart.setFocus(false);
focusPart = part;
if (focusPart != null)
focusPart.setFocus(true);
}
/**
* Sets the selection.
*
* @param newSelection
* the new selection
* @since 3.2
*/
public void setSelection(ISelection newSelection) {
if (!(newSelection instanceof IStructuredSelection))
return;
List orderedSelection = ((IStructuredSelection) newSelection).toList();
// Convert to HashSet to optimize performance.
Collection hashset = new HashSet(orderedSelection);
// Fix for 458416: adjust the focus through the viewer only (to give
// AbstractEditPartViewer a change to update its focusPart field).
// AbstractEditPartViewer#setFocus() should call back setFocus(null)
// here, so both focus part values should stay in sync.
viewer.setFocus(null);
for (int i = 0; i < selection.size(); i++) {
EditPart part = (EditPart) selection.get(i);
if (!hashset.contains(part))
part.setSelected(EditPart.SELECTED_NONE);
}
selection.clear();
if (!orderedSelection.isEmpty()) {
Iterator itr = orderedSelection.iterator();
while (true) {
EditPart part = (EditPart) itr.next();
selection.add(part);
if (!itr.hasNext()) {
part.setSelected(EditPart.SELECTED_PRIMARY);
break;
}
part.setSelected(EditPart.SELECTED);
}
}
fireSelectionChanged();
}
}