| /******************************************************************************* |
| * Copyright (c) 2005, 2006 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.ui.internal.menus; |
| |
| import org.eclipse.core.commands.ParameterizedCommand; |
| import org.eclipse.core.commands.common.CommandException; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IConfigurationElement; |
| import org.eclipse.core.runtime.ISafeRunnable; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.SafeRunner; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jface.menus.IWidget; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.CoolBar; |
| 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.swt.widgets.ToolBar; |
| import org.eclipse.swt.widgets.ToolItem; |
| import org.eclipse.swt.widgets.Widget; |
| import org.eclipse.ui.IWorkbenchWindowPulldownDelegate; |
| import org.eclipse.ui.IWorkbenchWindowPulldownDelegate2; |
| import org.eclipse.ui.handlers.IHandlerService; |
| import org.eclipse.ui.internal.WorkbenchPlugin; |
| import org.eclipse.ui.services.IServiceLocator; |
| |
| /** |
| * <p> |
| * A proxy for a {@link IWorkbenchWindowPulldownDelegate} on a pulldown action |
| * set action. This delays the class loading until the delegate is really asked |
| * for information. Asking a proxy for anything (except disposing) will cause |
| * the proxy to instantiate the proxied delegate. |
| * </p> |
| * <p> |
| * This class is not intended for use outside of the |
| * <code>org.eclipse.ui.workbench</code> plug-in. |
| * </p> |
| * |
| * @since 3.2 |
| */ |
| final class PulldownDelegateWidgetProxy implements IWidget { |
| |
| /** |
| * A wrapper for loading the menu that defends against possible exceptions |
| * triggered outside of the workbench. |
| */ |
| private static final class MenuLoader implements ISafeRunnable { |
| |
| /** |
| * The parent for the menu to be created. This value is |
| * <code>null</code> if the parent is a menu. |
| */ |
| private final Control control; |
| |
| /** |
| * The delegate from which to load the menu. |
| */ |
| private final IWorkbenchWindowPulldownDelegate delegate; |
| |
| /** |
| * The loaded menu. This value is <code>null</code> if the load |
| * failed, or if it hasn't been loaded yet. |
| */ |
| private Menu menu = null; |
| |
| /** |
| * The parent for the menu to be created. This value is |
| * <code>null</code> if the parent is a control. |
| */ |
| private final Menu parent; |
| |
| /** |
| * Constructs a new instance of <code>MenuLoader</code> |
| * |
| * @param delegate |
| * The delegate from which the menu will be loaded; this |
| * value must not be <code>null</code>. |
| * @param parent |
| * The parent of the menu to be loaded; this value must not |
| * be <code>null</code>. |
| */ |
| private MenuLoader(final IWorkbenchWindowPulldownDelegate delegate, |
| final Control parent) { |
| this.delegate = delegate; |
| this.parent = null; |
| this.control = parent; |
| } |
| |
| /** |
| * Constructs a new instance of <code>MenuLoader</code> |
| * |
| * @param delegate |
| * The delegate from which the menu will be loaded; this |
| * value must not be <code>null</code>. |
| * @param parent |
| * The parent of the menu to be loaded; this value must not |
| * be <code>null</code>. |
| */ |
| private MenuLoader(final IWorkbenchWindowPulldownDelegate2 delegate, |
| final Menu parent) { |
| this.delegate = delegate; |
| this.parent = parent; |
| this.control = null; |
| } |
| |
| /** |
| * Returns the menu loaded, if any. |
| * |
| * @return the loaded menu, or <code>null</code> if none. |
| */ |
| private Menu getMenu() { |
| return menu; |
| } |
| |
| /** |
| * @see ISafeRunnable#handleException(java.lang.Throwable) |
| */ |
| public void handleException(Throwable exception) { |
| // Do nothing |
| } |
| |
| /** |
| * @see ISafeRunnable#run() |
| */ |
| public void run() throws Exception { |
| if (parent == null) { |
| menu = delegate.getMenu(control); |
| } else { |
| menu = ((IWorkbenchWindowPulldownDelegate2) delegate) |
| .getMenu(parent); |
| } |
| } |
| } |
| |
| /** |
| * The command to execute when the pulldown delegate appears in a tool bar, |
| * and the arrow is <em>not</em> clicked. This also carries a help context |
| * identifier. This value must be <code>null</code>. |
| */ |
| private final ParameterizedCommand command; |
| |
| /** |
| * The configuration element from which the delegate can be created. This |
| * value will exist until the element is converted into a real class -- at |
| * which point this value will be set to <code>null</code>. |
| */ |
| private IConfigurationElement configurationElement; |
| |
| /** |
| * The real delegate. This value is <code>null</code> until the proxy is |
| * forced to load the real delegate. At this point, the configuration |
| * element is converted, nulled out, and this delegate gains a reference. |
| */ |
| private IWorkbenchWindowPulldownDelegate delegate = null; |
| |
| /** |
| * The name of the configuration element attribute which contains the |
| * information necessary to instantiate the real delegate. |
| */ |
| private final String delegateAttributeName; |
| |
| private final DisposeListener disposeListener = new DisposeListener() { |
| public void widgetDisposed(DisposeEvent e) { |
| if (e.widget == widget) { |
| dispose(); |
| widget = null; |
| |
| // TODO Is this necessary? |
| // disposeOldImages(); |
| } |
| } |
| }; |
| |
| /** |
| * The service locator from which a handler service can be retrieved. This |
| * is needed if the pulldown appears in the tool bar, and the drop-down |
| * arrow is <em>not</em> clicked. This value must not be <code>null</code>. |
| */ |
| private final IServiceLocator locator; |
| |
| private final Listener selectionListener = new Listener() { |
| public final void handleEvent(final Event event) { |
| final Widget item = event.widget; |
| if (item == null) { |
| return; |
| } |
| |
| final int style = item.getStyle(); |
| if (((style & SWT.DROP_DOWN) != 0) && (event.detail == SWT.ARROW) |
| && (item instanceof ToolItem)) { |
| // Create the submenu. |
| final ToolItem toolItem = (ToolItem) item; |
| final ToolBar toolBar = toolItem.getParent(); |
| if (loadDelegate() |
| && (delegate instanceof IWorkbenchWindowPulldownDelegate2)) { |
| final IWorkbenchWindowPulldownDelegate2 delegate2 = (IWorkbenchWindowPulldownDelegate2) delegate; |
| final MenuLoader loader = new MenuLoader(delegate2, toolBar); |
| SafeRunner.run(loader); |
| final Menu subMenu = loader.getMenu(); |
| if (subMenu != null) { |
| // position the menu below the drop down item |
| final Rectangle bounds = toolItem.getBounds(); |
| final Point location = toolBar.toDisplay(new Point( |
| bounds.x, bounds.y + bounds.height)); |
| subMenu.setLocation(location); |
| subMenu.setVisible(true); |
| return; // we don't fire the command |
| } |
| } |
| } |
| |
| final IHandlerService service = (IHandlerService) locator |
| .getService(IHandlerService.class); |
| try { |
| service.executeCommand(command, event); |
| } catch (final CommandException e) { |
| /* |
| * TODO There should be an API on IHandlerService that handles |
| * the exceptions. |
| */ |
| } |
| } |
| |
| }; |
| |
| /** |
| * The widget created for this pulldown delegate. If this proxy has not been |
| * asked to fill or it has been disposed, then this value is |
| * <code>null</code>. |
| */ |
| private Widget widget = null; |
| |
| /** |
| * Constructs a new instance of <code>PulldownDelegateWidgetProxy</code> |
| * with all the information it needs to try to avoid loading until it is |
| * needed. |
| * |
| * @param configurationElement |
| * The configuration element from which the real class can be |
| * loaded at run-time; must not be <code>null</code>. |
| * @param delegateAttributeName |
| * The name of the attibute or element containing the delegate; |
| * must not be <code>null</code>. |
| * @param command |
| * The command to execute if this the pulldown is not shown; must |
| * not be <code>null</code>. |
| * @param locator |
| * A service locator from which a handler service can be |
| * retrieved; must not be <code>null</code>. |
| */ |
| public PulldownDelegateWidgetProxy( |
| final IConfigurationElement configurationElement, |
| final String delegateAttributeName, |
| final ParameterizedCommand command, final IServiceLocator locator) { |
| if (configurationElement == null) { |
| throw new NullPointerException( |
| "The configuration element backing a handler proxy cannot be null"); //$NON-NLS-1$ |
| } |
| |
| if (delegateAttributeName == null) { |
| throw new NullPointerException( |
| "The attribute containing the handler class must be known"); //$NON-NLS-1$ |
| } |
| |
| if (command == null) { |
| throw new NullPointerException("The command cannot be null"); //$NON-NLS-1$ |
| } |
| |
| this.configurationElement = configurationElement; |
| this.delegateAttributeName = delegateAttributeName; |
| this.command = command; |
| this.locator = locator; |
| } |
| |
| /** |
| * Passes the dipose on to the proxied handler, if it has been loaded. |
| */ |
| public final void dispose() { |
| if (delegate != null) { |
| delegate.dispose(); |
| } |
| } |
| |
| public final void fill(final Composite parent) { |
| // This does not need to be supported. |
| } |
| |
| public final void fill(CoolBar parent, final int index) { |
| // This does not need to be supported. |
| } |
| |
| public final void fill(final Menu parent, final int index) { |
| if ((widget != null) || (parent == null)) { |
| return; |
| } |
| |
| // Create the menu item. |
| final MenuItem menuItem; |
| if (index >= 0) { |
| menuItem = new MenuItem(parent, SWT.CASCADE, index); |
| } else { |
| menuItem = new MenuItem(parent, SWT.CASCADE); |
| } |
| menuItem.setData(this); |
| widget = menuItem; |
| |
| // Create the submenu. |
| if (loadDelegate() |
| && (delegate instanceof IWorkbenchWindowPulldownDelegate2)) { |
| final IWorkbenchWindowPulldownDelegate2 delegate2 = (IWorkbenchWindowPulldownDelegate2) delegate; |
| final MenuLoader loader = new MenuLoader(delegate2, parent); |
| SafeRunner.run(loader); |
| final Menu subMenu = loader.getMenu(); |
| if (subMenu != null) { |
| menuItem.setMenu(subMenu); |
| } |
| } |
| |
| menuItem.addDisposeListener(disposeListener); |
| menuItem.addListener(SWT.Selection, selectionListener); |
| |
| // TODO Needs a way to be linked to a command. |
| // if (action.getHelpListener() != null) |
| // menuItem.addHelpListener(action.getHelpListener()); |
| |
| // TODO Needs a way of updating itself |
| // update(null); |
| } |
| |
| public final void fill(final ToolBar parent, final int index) { |
| if ((widget != null) && (parent == null)) { |
| return; |
| } |
| |
| final ToolItem toolItem; |
| if (index >= 0) { |
| toolItem = new ToolItem(parent, SWT.DROP_DOWN, index); |
| } else { |
| toolItem = new ToolItem(parent, SWT.DROP_DOWN); |
| } |
| toolItem.setData(this); |
| widget = toolItem; |
| |
| // Attach some listeners. |
| toolItem.addDisposeListener(disposeListener); |
| toolItem.addListener(SWT.Selection, selectionListener); |
| |
| // TODO Needs a way to be linked to a command. |
| // toolItem.addListener(SWT.Selection, getToolItemListener()); |
| // action.addPropertyChangeListener(propertyListener); |
| // if (action != null) { |
| // String commandId = action.getActionDefinitionId(); |
| // ExternalActionManager.ICallback callback = ExternalActionManager |
| // .getInstance().getCallback(); |
| // |
| // if ((callback != null) && (commandId != null)) { |
| // callback.addPropertyChangeListener(commandId, |
| // actionTextListener); |
| // } |
| // } |
| |
| // TODO Needs a way of updating itself |
| // update(null); |
| } |
| |
| /** |
| * Loads the delegate, if possible. If the delegate is loaded, then the |
| * member variables are updated accordingly. |
| * |
| * @return <code>true</code> if the delegate is now non-null; |
| * <code>false</code> otherwise. |
| */ |
| private final boolean loadDelegate() { |
| if (delegate == null) { |
| // Load the handler. |
| try { |
| delegate = (IWorkbenchWindowPulldownDelegate) configurationElement |
| .createExecutableExtension(delegateAttributeName); |
| configurationElement = null; |
| return true; |
| |
| } catch (final ClassCastException e) { |
| final String message = "The proxied delegate was the wrong class"; //$NON-NLS-1$ |
| final IStatus status = new Status(IStatus.ERROR, |
| WorkbenchPlugin.PI_WORKBENCH, 0, message, e); |
| WorkbenchPlugin.log(message, status); |
| return false; |
| |
| } catch (final CoreException e) { |
| final String message = "The proxied delegate for '" + configurationElement.getAttribute(delegateAttributeName) //$NON-NLS-1$ |
| + "' could not be loaded"; //$NON-NLS-1$ |
| IStatus status = new Status(IStatus.ERROR, |
| WorkbenchPlugin.PI_WORKBENCH, 0, message, e); |
| WorkbenchPlugin.log(message, status); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| public final String toString() { |
| if (delegate == null) { |
| return configurationElement.getAttribute(delegateAttributeName); |
| } |
| |
| return delegate.toString(); |
| } |
| } |