/******************************************************************************* * Copyright (c) 2006-2013 The RCP Company 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: * The RCP Company - initial API and implementation *******************************************************************************/ package com.rcpcompany.uibindings.internal.utils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.action.ContributionItem; import org.eclipse.jface.action.IContributionItem; import org.eclipse.jface.viewers.ISelection; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.ui.ISourceProvider; import org.eclipse.ui.ISourceProviderListener; import org.eclipse.ui.IViewPart; import org.eclipse.ui.IViewSite; import org.eclipse.ui.IWindowListener; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.handlers.HandlerUtil; import org.eclipse.ui.menus.ExtensionContributionFactory; import org.eclipse.ui.menus.IContributionRoot; import org.eclipse.ui.part.ISetSelectionTarget; import org.eclipse.ui.services.IServiceLocator; import org.eclipse.ui.services.ISourceProviderService; import com.rcpcompany.uibindings.BindingState; import com.rcpcompany.uibindings.Constants; import com.rcpcompany.uibindings.IDisposable; import com.rcpcompany.uibindings.IManager; import com.rcpcompany.uibindings.IValueBinding; import com.rcpcompany.uibindings.IValueBindingCell; import com.rcpcompany.uibindings.internal.Activator; import com.rcpcompany.uibindings.model.utils.BasicUtils; import com.rcpcompany.uibindings.utils.IBindingObjectLongName; import com.rcpcompany.uibindings.utils.IGlobalNavigationManager; import com.rcpcompany.uibindings.utils.IManagerRunnable; import com.rcpcompany.utils.logging.LogUtils; /** * Implementation of {@link IGlobalNavigationManager}. * * @author Tonny Madsen, The RCP Company */ public final class GlobalNavigationManager implements IGlobalNavigationManager { /** * Returns the manager for the specified window. * * @param window the window in question * @param create true if the manager should be created if missing * @return the manager */ public static GlobalNavigationManager getManager(IWorkbenchWindow window, boolean create) { GlobalNavigationManager manager = MANAGERS.get(window); if (manager == null && create) { manager = new GlobalNavigationManager(window); } return manager; } /** * The number of kept locations in the location stack. */ private int myNoKeptLocations = 20; /** * The window of this manager. */ private final IWorkbenchWindow myWindow; /** * <code>true</code> while doing forward or backward. Ignores selection changes while this is * <code>true</code>. */ private boolean inMovement = false; /** * The location stack. */ private final List<Location> myLocationStack = new ArrayList<Location>(); /** * Returns the location stack. * * @return the stack */ List<Location> getLocationStack() { return myLocationStack; } /** * The current location - a pointer into {@link #myLocationStack} to the location that was last * shown or added. * <p> * So it is typically {@link #myLocationStack}<code>.size()-1</code> */ private int myCurrentLocationIndex = 0; /** * Listener for {@link Constants#SOURCES_ACTIVE_BINDING}. */ private final MySourceProviderListener myBindingSourceProviderListener; /** * Listener used to dispose the manager when the window is closed. */ private final IWindowListener myWindowListener = new IWindowListener() { @Override public void windowOpened(IWorkbenchWindow window) { } @Override public void windowDeactivated(IWorkbenchWindow window) { } @Override public void windowClosed(IWorkbenchWindow window) { dispose(); } @Override public void windowActivated(IWorkbenchWindow window) { } }; /** * Map of all managers. */ private static final Map<IWorkbenchWindow, GlobalNavigationManager> MANAGERS = new HashMap<IWorkbenchWindow, GlobalNavigationManager>(); /** * Constructs and returns a new navigation manager for the specified window. * * @param window the window for the manager */ private GlobalNavigationManager(IWorkbenchWindow window) { Assert.isNotNull(window); myWindow = window; MANAGERS.put(window, this); final ISourceProviderService sps = (ISourceProviderService) window.getService(ISourceProviderService.class); myBindingSourceProviderListener = new MySourceProviderListener(sps, Constants.SOURCES_ACTIVE_BINDING); IManager.Factory.getManager().registerService(this); PlatformUI.getWorkbench().addWindowListener(myWindowListener); addLocation(); } /** * Disposes this manager. */ @Override public void dispose() { myBindingSourceProviderListener.dispose(); PlatformUI.getWorkbench().removeWindowListener(myWindowListener); IManager.Factory.getManager().unregisterService(this); MANAGERS.remove(myWindow); } /** * Returns the window of this manager. * * @return the window */ public IWorkbenchWindow getWindow() { return myWindow; } private void updateHandlers() { if (theBackwardHandler != null) { theBackwardHandler.setBaseEnabled(isBackwardHistoryEnabled()); } if (theForwardHandler != null) { theForwardHandler.setBaseEnabled(isForwardHistoryEnabled()); } } @Override public void addLocation() { if (inMovement) return; /* * Find the new previous location */ final Location newLocation = new Location(); if (Activator.getDefault().TRACE_NAVIGATION_GLOBAL) { LogUtils.debug(this, "added " + newLocation); } /* * Clear the stack of any elements after the current stack pointer */ if (myLocationStack.size() > 0) { myLocationStack.subList(getCurrentLocationIndex() + 1, myLocationStack.size()).clear(); } /* * Add the new location */ myLocationStack.add(newLocation); /* * Remove any locations over the max number */ while (getNoKeptLocations() > 0 && myLocationStack.size() > getNoKeptLocations()) { myLocationStack.remove(0); } /* * Set the new location pointer */ setCurrentLocationIndex(myLocationStack.size() - 1); updateLocation(); } private final Runnable updateLocationRunnable = new Runnable() { @Override public void run() { getCurrentLocation().update(); } }; @Override public void updateLocation() { if (inMovement) return; IManagerRunnable.Factory.asyncExec("location", this, updateLocationRunnable); } public void gotoLocation(int index) { try { inMovement = true; setCurrentLocationIndex(index); getCurrentLocation().show(); } finally { inMovement = false; } updateHandlers(); } /** * Goes back in history. */ @Override public void backwardHistory() { if (!isBackwardHistoryEnabled()) return; gotoLocation(getCurrentLocationIndex() - 1); } /** * Returns whether the backward history method is active right now. * * @return <code>true</code> if active */ public boolean isBackwardHistoryEnabled() { return getCurrentLocationIndex() > 0; } /** * Goes forward in history. */ @Override public void forwardHistory() { if (!isForwardHistoryEnabled()) return; gotoLocation(getCurrentLocationIndex() + 1); } /** * Returns whether the forward history method is active right now. * * @return <code>true</code> if active */ public boolean isForwardHistoryEnabled() { return getCurrentLocationIndex() < myLocationStack.size() - 1; } @Override public void setNoKeptLocations(int noKeptLocations) { myNoKeptLocations = noKeptLocations; } @Override public int getNoKeptLocations() { return myNoKeptLocations; } /** * Returns the location at the specific index. * * @param i the index * @return the location */ Location getLocation(int i) { Assert.isTrue(0 <= i && i < myLocationStack.size(), "Location index " + i + " must [0; " + myLocationStack.size() + "["); return myLocationStack.get(i); } /** * Returns the current location. * * @return the location */ Location getCurrentLocation() { return getLocation(getCurrentLocationIndex()); } /** * @param currentLocation the currentLocation to set */ public void setCurrentLocationIndex(int currentLocation) { myCurrentLocationIndex = currentLocation; } /** * @return the currentLocation */ public int getCurrentLocationIndex() { return myCurrentLocationIndex; } /** * A single global navigation position. */ protected class Location { private String myId; private String mySecondaryId; private ISelection mySelection; private String myTitle; private Image myImage; private IValueBinding myBinding; /** * Constructs and returns a new location. */ protected Location() { } public void update() { final IWorkbenchPage activePage = myWindow.getActivePage(); /* * The active page will be null when closing a window... */ if (activePage == null) return; final IWorkbenchPart activePart = activePage.getActivePart(); if (!(activePart instanceof IViewPart)) { if (getId() == null) { LogUtils.debug(this, "null Location not updated. activePart=" + activePart); } return; } final Object value = myBindingSourceProviderListener.getValue(); if (value instanceof IValueBinding) { myBinding = (IValueBinding) value; /* * If the binding is associated with a specific cell, then use the label binding for * the cell. This is useful when the cell is being edited and we get the editor * binding as this have gone away when we get back to the location later using * show(). */ final IValueBindingCell cell = myBinding.getCell(); if (cell != null) { myBinding = cell.getLabelBinding(); } } final IViewPart vp = (IViewPart) activePart; final IViewSite viewSite = vp.getViewSite(); myId = viewSite.getId(); mySecondaryId = viewSite.getSecondaryId(); myTitle = vp.getTitle(); myImage = vp.getTitleImage(); if (vp instanceof IGetSelectionTarget) { mySelection = ((IGetSelectionTarget) vp).getCurrentSelection(); } else { mySelection = null; } updateHandlers(); } /** * Returns the id of the view. * * @return the view id */ public String getId() { return myId; } /** * Returns the secondary id of the view. * * @return the view secondary id - possibly <code>null</code> */ public String getSecondaryId() { return mySecondaryId; } /** * Returns the active selection in the view. * * @return the selection */ public ISelection getSelection() { return mySelection; } /** * Returns the active binding in the view. * * @return the binding */ public IValueBinding getBinding() { return myBinding; } public String getTitle() { return myTitle; } public Image getImage() { return myImage; } /** * Shows the specified position. */ public void show() { if (Activator.getDefault().TRACE_NAVIGATION_GLOBAL) { LogUtils.debug(this, "show " + this); } IViewPart view = null; try { Assert.isNotNull(getId()); view = getWindow().getActivePage().showView(getId(), getSecondaryId(), IWorkbenchPage.VIEW_ACTIVATE); } catch (final PartInitException ex) { LogUtils.error(this, ex); return; } if (view instanceof ISetSelectionTarget) { final ISetSelectionTarget t = (ISetSelectionTarget) view; t.selectReveal(getSelection()); } final IValueBinding b = getBinding(); if (b != null && b.getState() == BindingState.OK) { b.setFocus(); } } @Override public String toString() { return "Location[" + getId() + "]=" + getSelection() + "/" + getBinding(); } private GlobalNavigationManager getOuterType() { return GlobalNavigationManager.this; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + getOuterType().hashCode(); result = prime * result + ((myBinding == null) ? 0 : myBinding.hashCode()); result = prime * result + ((myId == null) ? 0 : myId.hashCode()); result = prime * result + ((mySecondaryId == null) ? 0 : mySecondaryId.hashCode()); result = prime * result + ((mySelection == null) ? 0 : mySelection.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof Location)) return false; final Location other = (Location) obj; return BasicUtils.equals(this.getId(), other.getId()) && BasicUtils.equals(this.getSecondaryId(), other.getSecondaryId()) && BasicUtils.equals(this.getSelection(), other.getSelection()) && BasicUtils.equals(this.getBinding(), other.getBinding()); } } public static BackwardHandler theBackwardHandler = null; /** * <code>org.eclipse.ui.navigate.backwardHistory</code> handler. */ public static class BackwardHandler extends AbstractHandler { public BackwardHandler() { theBackwardHandler = this; setBaseEnabled(false); } @Override public Object execute(ExecutionEvent event) throws ExecutionException { final IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindowChecked(event); final GlobalNavigationManager manager = GlobalNavigationManager.getManager(window, false); if (manager != null) { manager.backwardHistory(); } return null; } /* * To gain access from GNM */ @Override public void setBaseEnabled(boolean state) { super.setBaseEnabled(state); } /* * To provoke an early load */ @Override public void setEnabled(Object evaluationContext) { super.setEnabled(evaluationContext); } } /** * Menu item contributor for the backward history menu. */ public static class BackwardHistoryMenuContributor extends ExtensionContributionFactory { @Override public void createContributionItems(IServiceLocator serviceLocator, IContributionRoot additions) { final IWorkbenchWindow window = (IWorkbenchWindow) serviceLocator.getService(IWorkbenchWindow.class); final GlobalNavigationManager manager = getManager(window, false); for (int i = manager.getCurrentLocationIndex() - 1; i >= 0; i--) { additions.addContributionItem(new LocationContributionItem(manager, i), null); } } } /** * Menu item contributor for the forward history menu. */ public static class ForwardHistoryMenuContributor extends ExtensionContributionFactory { @Override public void createContributionItems(IServiceLocator serviceLocator, IContributionRoot additions) { final IWorkbenchWindow window = (IWorkbenchWindow) serviceLocator.getService(IWorkbenchWindow.class); final GlobalNavigationManager manager = getManager(window, false); for (int i = manager.getCurrentLocationIndex() + 1; i < manager.myLocationStack.size(); i++) { additions.addContributionItem(new LocationContributionItem(manager, i), null); } } } /** * {@link IContributionItem} based on a {@link Location}. */ public static class LocationContributionItem extends ContributionItem implements Listener { private final GlobalNavigationManager myManager; private final int myIndex; private final Location myLocation; public LocationContributionItem(GlobalNavigationManager manager, int index) { myManager = manager; myIndex = index; myLocation = myManager.getLocation(myIndex); } @Override public void fill(Menu menu, int index) { MenuItem item = null; if (index >= 0) { item = new MenuItem(menu, SWT.PUSH, index); } else { item = new MenuItem(menu, SWT.PUSH); } item.setData(this); item.addListener(SWT.Selection, this); /* * Modify the title based on the current selection */ String t = myLocation.getTitle(); final ISelection selection = myLocation.getSelection(); if (selection != null && !selection.isEmpty()) { final String s = IBindingObjectLongName.Factory.getLongName(selection); if (s != null && s.length() > 0) { t += " (" + s + ")"; } } item.setText(t); item.setImage(myLocation.getImage()); } @Override public void handleEvent(Event event) { switch (event.type) { case SWT.Selection: myManager.gotoLocation(myIndex); } } } public static ForwardHandler theForwardHandler = null; /** * <code>org.eclipse.ui.navigate.forwardHistory</code> handler. */ public static class ForwardHandler extends AbstractHandler { public ForwardHandler() { theForwardHandler = this; setBaseEnabled(false); } @Override public Object execute(ExecutionEvent event) throws ExecutionException { final IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindowChecked(event); final GlobalNavigationManager manager = GlobalNavigationManager.getManager(window, false); if (manager != null) { manager.forwardHistory(); } return null; } /* * To gain access from GNM */ @Override public void setBaseEnabled(boolean state) { super.setBaseEnabled(state); } /* * To provoke an early load */ @Override public void setEnabled(Object evaluationContext) { super.setEnabled(evaluationContext); } } public class MySourceProviderListener implements ISourceProviderListener, IDisposable { /** * The source name. */ private final String mySourceName; /** * The {@link ISourceProvider} that provides {@link #mySourceName}. */ private final ISourceProvider mySourceProvider; /** * The latest value for {@link #mySourceName}. */ private Object myValue; /** * Constructs and returns a new listsner for the specified source. * * @param sps the {@link ISourceProvider} service * @param sourceName the name of the source */ public MySourceProviderListener(ISourceProviderService sps, String sourceName) { mySourceName = sourceName; mySourceProvider = sps.getSourceProvider(sourceName); mySourceProvider.addSourceProviderListener(this); myValue = mySourceProvider.getCurrentState().get(mySourceName); } @Override public void dispose() { mySourceProvider.removeSourceProviderListener(this); } @Override public void sourceChanged(int sourcePriority, @SuppressWarnings("unchecked") Map sourceValuesByName) { if (sourceValuesByName != null && sourceValuesByName.containsKey(mySourceName)) { myValue = sourceValuesByName.get(mySourceName); // LogUtils.debug(this, mySourceName + "=" + myValue); updateLocation(); } } @Override public void sourceChanged(int sourcePriority, String sourceName, Object sourceValue) { if (mySourceName.equals(sourceName)) { myValue = sourceValue; // LogUtils.debug(this, mySourceName + "=" + myValue); updateLocation(); } } /** * @return the value */ public Object getValue() { return myValue; } } }