001: /*******************************************************************************
002: * Copyright (c) 2005, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal.menus;
011:
012: import org.eclipse.core.commands.ParameterizedCommand;
013: import org.eclipse.core.commands.common.CommandException;
014: import org.eclipse.core.runtime.CoreException;
015: import org.eclipse.core.runtime.IConfigurationElement;
016: import org.eclipse.core.runtime.ISafeRunnable;
017: import org.eclipse.core.runtime.IStatus;
018: import org.eclipse.core.runtime.SafeRunner;
019: import org.eclipse.core.runtime.Status;
020: import org.eclipse.jface.menus.IWidget;
021: import org.eclipse.swt.SWT;
022: import org.eclipse.swt.events.DisposeEvent;
023: import org.eclipse.swt.events.DisposeListener;
024: import org.eclipse.swt.graphics.Point;
025: import org.eclipse.swt.graphics.Rectangle;
026: import org.eclipse.swt.widgets.Composite;
027: import org.eclipse.swt.widgets.Control;
028: import org.eclipse.swt.widgets.CoolBar;
029: import org.eclipse.swt.widgets.Event;
030: import org.eclipse.swt.widgets.Listener;
031: import org.eclipse.swt.widgets.Menu;
032: import org.eclipse.swt.widgets.MenuItem;
033: import org.eclipse.swt.widgets.ToolBar;
034: import org.eclipse.swt.widgets.ToolItem;
035: import org.eclipse.swt.widgets.Widget;
036: import org.eclipse.ui.IWorkbenchWindowPulldownDelegate;
037: import org.eclipse.ui.IWorkbenchWindowPulldownDelegate2;
038: import org.eclipse.ui.handlers.IHandlerService;
039: import org.eclipse.ui.internal.WorkbenchPlugin;
040: import org.eclipse.ui.services.IServiceLocator;
041:
042: /**
043: * <p>
044: * A proxy for a {@link IWorkbenchWindowPulldownDelegate} on a pulldown action
045: * set action. This delays the class loading until the delegate is really asked
046: * for information. Asking a proxy for anything (except disposing) will cause
047: * the proxy to instantiate the proxied delegate.
048: * </p>
049: * <p>
050: * This class is not intended for use outside of the
051: * <code>org.eclipse.ui.workbench</code> plug-in.
052: * </p>
053: *
054: * @since 3.2
055: */
056: final class PulldownDelegateWidgetProxy implements IWidget {
057:
058: /**
059: * A wrapper for loading the menu that defends against possible exceptions
060: * triggered outside of the workbench.
061: */
062: private static final class MenuLoader implements ISafeRunnable {
063:
064: /**
065: * The parent for the menu to be created. This value is
066: * <code>null</code> if the parent is a menu.
067: */
068: private final Control control;
069:
070: /**
071: * The delegate from which to load the menu.
072: */
073: private final IWorkbenchWindowPulldownDelegate delegate;
074:
075: /**
076: * The loaded menu. This value is <code>null</code> if the load
077: * failed, or if it hasn't been loaded yet.
078: */
079: private Menu menu = null;
080:
081: /**
082: * The parent for the menu to be created. This value is
083: * <code>null</code> if the parent is a control.
084: */
085: private final Menu parent;
086:
087: /**
088: * Constructs a new instance of <code>MenuLoader</code>
089: *
090: * @param delegate
091: * The delegate from which the menu will be loaded; this
092: * value must not be <code>null</code>.
093: * @param parent
094: * The parent of the menu to be loaded; this value must not
095: * be <code>null</code>.
096: */
097: private MenuLoader(
098: final IWorkbenchWindowPulldownDelegate delegate,
099: final Control parent) {
100: this .delegate = delegate;
101: this .parent = null;
102: this .control = parent;
103: }
104:
105: /**
106: * Constructs a new instance of <code>MenuLoader</code>
107: *
108: * @param delegate
109: * The delegate from which the menu will be loaded; this
110: * value must not be <code>null</code>.
111: * @param parent
112: * The parent of the menu to be loaded; this value must not
113: * be <code>null</code>.
114: */
115: private MenuLoader(
116: final IWorkbenchWindowPulldownDelegate2 delegate,
117: final Menu parent) {
118: this .delegate = delegate;
119: this .parent = parent;
120: this .control = null;
121: }
122:
123: /**
124: * Returns the menu loaded, if any.
125: *
126: * @return the loaded menu, or <code>null</code> if none.
127: */
128: private Menu getMenu() {
129: return menu;
130: }
131:
132: /**
133: * @see ISafeRunnable#handleException(java.lang.Throwable)
134: */
135: public void handleException(Throwable exception) {
136: // Do nothing
137: }
138:
139: /**
140: * @see ISafeRunnable#run()
141: */
142: public void run() throws Exception {
143: if (parent == null) {
144: menu = delegate.getMenu(control);
145: } else {
146: menu = ((IWorkbenchWindowPulldownDelegate2) delegate)
147: .getMenu(parent);
148: }
149: }
150: }
151:
152: /**
153: * The command to execute when the pulldown delegate appears in a tool bar,
154: * and the arrow is <em>not</em> clicked. This also carries a help context
155: * identifier. This value must be <code>null</code>.
156: */
157: private final ParameterizedCommand command;
158:
159: /**
160: * The configuration element from which the delegate can be created. This
161: * value will exist until the element is converted into a real class -- at
162: * which point this value will be set to <code>null</code>.
163: */
164: private IConfigurationElement configurationElement;
165:
166: /**
167: * The real delegate. This value is <code>null</code> until the proxy is
168: * forced to load the real delegate. At this point, the configuration
169: * element is converted, nulled out, and this delegate gains a reference.
170: */
171: private IWorkbenchWindowPulldownDelegate delegate = null;
172:
173: /**
174: * The name of the configuration element attribute which contains the
175: * information necessary to instantiate the real delegate.
176: */
177: private final String delegateAttributeName;
178:
179: private final DisposeListener disposeListener = new DisposeListener() {
180: public void widgetDisposed(DisposeEvent e) {
181: if (e.widget == widget) {
182: dispose();
183: widget = null;
184:
185: // TODO Is this necessary?
186: // disposeOldImages();
187: }
188: }
189: };
190:
191: /**
192: * The service locator from which a handler service can be retrieved. This
193: * is needed if the pulldown appears in the tool bar, and the drop-down
194: * arrow is <em>not</em> clicked. This value must not be <code>null</code>.
195: */
196: private final IServiceLocator locator;
197:
198: private final Listener selectionListener = new Listener() {
199: public final void handleEvent(final Event event) {
200: final Widget item = event.widget;
201: if (item == null) {
202: return;
203: }
204:
205: final int style = item.getStyle();
206: if (((style & SWT.DROP_DOWN) != 0)
207: && (event.detail == SWT.ARROW)
208: && (item instanceof ToolItem)) {
209: // Create the submenu.
210: final ToolItem toolItem = (ToolItem) item;
211: final ToolBar toolBar = toolItem.getParent();
212: if (loadDelegate()
213: && (delegate instanceof IWorkbenchWindowPulldownDelegate2)) {
214: final IWorkbenchWindowPulldownDelegate2 delegate2 = (IWorkbenchWindowPulldownDelegate2) delegate;
215: final MenuLoader loader = new MenuLoader(delegate2,
216: toolBar);
217: SafeRunner.run(loader);
218: final Menu subMenu = loader.getMenu();
219: if (subMenu != null) {
220: // position the menu below the drop down item
221: final Rectangle bounds = toolItem.getBounds();
222: final Point location = toolBar
223: .toDisplay(new Point(bounds.x, bounds.y
224: + bounds.height));
225: subMenu.setLocation(location);
226: subMenu.setVisible(true);
227: return; // we don't fire the command
228: }
229: }
230: }
231:
232: final IHandlerService service = (IHandlerService) locator
233: .getService(IHandlerService.class);
234: try {
235: service.executeCommand(command, event);
236: } catch (final CommandException e) {
237: /*
238: * TODO There should be an API on IHandlerService that handles
239: * the exceptions.
240: */
241: }
242: }
243:
244: };
245:
246: /**
247: * The widget created for this pulldown delegate. If this proxy has not been
248: * asked to fill or it has been disposed, then this value is
249: * <code>null</code>.
250: */
251: private Widget widget = null;
252:
253: /**
254: * Constructs a new instance of <code>PulldownDelegateWidgetProxy</code>
255: * with all the information it needs to try to avoid loading until it is
256: * needed.
257: *
258: * @param configurationElement
259: * The configuration element from which the real class can be
260: * loaded at run-time; must not be <code>null</code>.
261: * @param delegateAttributeName
262: * The name of the attibute or element containing the delegate;
263: * must not be <code>null</code>.
264: * @param command
265: * The command to execute if this the pulldown is not shown; must
266: * not be <code>null</code>.
267: * @param locator
268: * A service locator from which a handler service can be
269: * retrieved; must not be <code>null</code>.
270: */
271: public PulldownDelegateWidgetProxy(
272: final IConfigurationElement configurationElement,
273: final String delegateAttributeName,
274: final ParameterizedCommand command,
275: final IServiceLocator locator) {
276: if (configurationElement == null) {
277: throw new NullPointerException(
278: "The configuration element backing a handler proxy cannot be null"); //$NON-NLS-1$
279: }
280:
281: if (delegateAttributeName == null) {
282: throw new NullPointerException(
283: "The attribute containing the handler class must be known"); //$NON-NLS-1$
284: }
285:
286: if (command == null) {
287: throw new NullPointerException("The command cannot be null"); //$NON-NLS-1$
288: }
289:
290: this .configurationElement = configurationElement;
291: this .delegateAttributeName = delegateAttributeName;
292: this .command = command;
293: this .locator = locator;
294: }
295:
296: /**
297: * Passes the dipose on to the proxied handler, if it has been loaded.
298: */
299: public final void dispose() {
300: if (delegate != null) {
301: delegate.dispose();
302: }
303: }
304:
305: public final void fill(final Composite parent) {
306: // This does not need to be supported.
307: }
308:
309: public final void fill(CoolBar parent, final int index) {
310: // This does not need to be supported.
311: }
312:
313: public final void fill(final Menu parent, final int index) {
314: if ((widget != null) || (parent == null)) {
315: return;
316: }
317:
318: // Create the menu item.
319: final MenuItem menuItem;
320: if (index >= 0) {
321: menuItem = new MenuItem(parent, SWT.CASCADE, index);
322: } else {
323: menuItem = new MenuItem(parent, SWT.CASCADE);
324: }
325: menuItem.setData(this );
326: widget = menuItem;
327:
328: // Create the submenu.
329: if (loadDelegate()
330: && (delegate instanceof IWorkbenchWindowPulldownDelegate2)) {
331: final IWorkbenchWindowPulldownDelegate2 delegate2 = (IWorkbenchWindowPulldownDelegate2) delegate;
332: final MenuLoader loader = new MenuLoader(delegate2, parent);
333: SafeRunner.run(loader);
334: final Menu subMenu = loader.getMenu();
335: if (subMenu != null) {
336: menuItem.setMenu(subMenu);
337: }
338: }
339:
340: menuItem.addDisposeListener(disposeListener);
341: menuItem.addListener(SWT.Selection, selectionListener);
342:
343: // TODO Needs a way to be linked to a command.
344: // if (action.getHelpListener() != null)
345: // menuItem.addHelpListener(action.getHelpListener());
346:
347: // TODO Needs a way of updating itself
348: // update(null);
349: }
350:
351: public final void fill(final ToolBar parent, final int index) {
352: if ((widget != null) && (parent == null)) {
353: return;
354: }
355:
356: final ToolItem toolItem;
357: if (index >= 0) {
358: toolItem = new ToolItem(parent, SWT.DROP_DOWN, index);
359: } else {
360: toolItem = new ToolItem(parent, SWT.DROP_DOWN);
361: }
362: toolItem.setData(this );
363: widget = toolItem;
364:
365: // Attach some listeners.
366: toolItem.addDisposeListener(disposeListener);
367: toolItem.addListener(SWT.Selection, selectionListener);
368:
369: // TODO Needs a way to be linked to a command.
370: // toolItem.addListener(SWT.Selection, getToolItemListener());
371: // action.addPropertyChangeListener(propertyListener);
372: // if (action != null) {
373: // String commandId = action.getActionDefinitionId();
374: // ExternalActionManager.ICallback callback = ExternalActionManager
375: // .getInstance().getCallback();
376: //
377: // if ((callback != null) && (commandId != null)) {
378: // callback.addPropertyChangeListener(commandId,
379: // actionTextListener);
380: // }
381: // }
382:
383: // TODO Needs a way of updating itself
384: // update(null);
385: }
386:
387: /**
388: * Loads the delegate, if possible. If the delegate is loaded, then the
389: * member variables are updated accordingly.
390: *
391: * @return <code>true</code> if the delegate is now non-null;
392: * <code>false</code> otherwise.
393: */
394: private final boolean loadDelegate() {
395: if (delegate == null) {
396: // Load the handler.
397: try {
398: delegate = (IWorkbenchWindowPulldownDelegate) configurationElement
399: .createExecutableExtension(delegateAttributeName);
400: configurationElement = null;
401: return true;
402:
403: } catch (final ClassCastException e) {
404: final String message = "The proxied delegate was the wrong class"; //$NON-NLS-1$
405: final IStatus status = new Status(IStatus.ERROR,
406: WorkbenchPlugin.PI_WORKBENCH, 0, message, e);
407: WorkbenchPlugin.log(message, status);
408: return false;
409:
410: } catch (final CoreException e) {
411: final String message = "The proxied delegate for '" + configurationElement.getAttribute(delegateAttributeName) //$NON-NLS-1$
412: + "' could not be loaded"; //$NON-NLS-1$
413: IStatus status = new Status(IStatus.ERROR,
414: WorkbenchPlugin.PI_WORKBENCH, 0, message, e);
415: WorkbenchPlugin.log(message, status);
416: return false;
417: }
418: }
419:
420: return true;
421: }
422:
423: public final String toString() {
424: if (delegate == null) {
425: return configurationElement
426: .getAttribute(delegateAttributeName);
427: }
428:
429: return delegate.toString();
430: }
431: }
|