001: /*******************************************************************************
002: * Copyright (c) 2000, 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: * Dan Rubel (dan_rubel@instantiations.com) - accessor to get menu id
011: *******************************************************************************/package org.eclipse.ui.internal;
012:
013: import java.util.ArrayList;
014: import java.util.Collections;
015: import java.util.HashMap;
016: import java.util.Iterator;
017: import java.util.Map;
018: import java.util.Set;
019:
020: import org.eclipse.core.runtime.IConfigurationElement;
021: import org.eclipse.core.runtime.IExtensionDelta;
022: import org.eclipse.core.runtime.IExtensionPoint;
023: import org.eclipse.core.runtime.IRegistryChangeEvent;
024: import org.eclipse.core.runtime.IRegistryChangeListener;
025: import org.eclipse.core.runtime.Platform;
026: import org.eclipse.jface.action.ContributionManager;
027: import org.eclipse.jface.action.IContributionItem;
028: import org.eclipse.jface.action.IMenuListener2;
029: import org.eclipse.jface.action.IMenuManager;
030: import org.eclipse.jface.action.MenuManager;
031: import org.eclipse.jface.action.Separator;
032: import org.eclipse.jface.action.SubMenuManager;
033: import org.eclipse.jface.viewers.ISelection;
034: import org.eclipse.jface.viewers.ISelectionChangedListener;
035: import org.eclipse.jface.viewers.ISelectionProvider;
036: import org.eclipse.jface.viewers.StructuredSelection;
037: import org.eclipse.swt.widgets.Display;
038: import org.eclipse.ui.IEditorPart;
039: import org.eclipse.ui.IWorkbench;
040: import org.eclipse.ui.IWorkbenchActionConstants;
041: import org.eclipse.ui.IWorkbenchPart;
042: import org.eclipse.ui.IWorkbenchPartSite;
043: import org.eclipse.ui.internal.menus.WindowMenuService;
044: import org.eclipse.ui.internal.registry.IWorkbenchRegistryConstants;
045: import org.eclipse.ui.menus.IMenuService;
046: import org.eclipse.ui.menus.MenuUtil;
047:
048: /**
049: * This class extends a single popup menu
050: */
051: public class PopupMenuExtender implements IMenuListener2,
052: IRegistryChangeListener {
053:
054: /**
055: * The bit in <code>bitSet</code> that stores whether the static actions
056: * have been read from the registry.
057: */
058: private static final int STATIC_ACTION_READ = 1;
059:
060: /**
061: * The bit in <code>bitSet</code> that stores whether the editor input
062: * should be included for the sake of object contributions.
063: */
064: private static final int INCLUDE_EDITOR_INPUT = 1 << 1;
065:
066: private final MenuManager menu;
067:
068: private SubMenuManager menuWrapper;
069:
070: private final ISelectionProvider selProvider;
071:
072: private final IWorkbenchPart part;
073:
074: private Map staticActionBuilders = null;
075:
076: /**
077: * The boolean properties maintained by this extender. A bit set is used to
078: * save memory.
079: */
080: private int bitSet = 0;
081:
082: private ArrayList contributionCache = new ArrayList();
083:
084: /**
085: * Construct a new menu extender.
086: *
087: * @param id
088: * the menu id
089: * @param menu
090: * the menu to extend
091: * @param prov
092: * the selection provider
093: * @param part
094: * the part to extend
095: */
096: public PopupMenuExtender(String id, MenuManager menu,
097: ISelectionProvider prov, IWorkbenchPart part) {
098: this (id, menu, prov, part, true);
099: }
100:
101: /**
102: * Construct a new menu extender.
103: *
104: * @param id
105: * the menu id
106: * @param menu
107: * the menu to extend
108: * @param prov
109: * the selection provider
110: * @param part
111: * the part to extend
112: * @param includeEditorInput
113: * Whether the editor input should be included when adding object
114: * contributions to this context menu.
115: */
116: public PopupMenuExtender(final String id, final MenuManager menu,
117: final ISelectionProvider prov, final IWorkbenchPart part,
118: final boolean includeEditorInput) {
119: super ();
120: this .menu = menu;
121: this .selProvider = prov;
122: this .part = part;
123: if (includeEditorInput) {
124: bitSet |= INCLUDE_EDITOR_INPUT;
125: }
126: menu.addMenuListener(this );
127: if (!menu.getRemoveAllWhenShown()) {
128: menuWrapper = new SubMenuManager(menu);
129: menuWrapper.setVisible(true);
130: }
131: readStaticActionsFor(id);
132:
133: Platform.getExtensionRegistry().addRegistryChangeListener(this );
134: }
135:
136: // getMenuId() added by Dan Rubel (dan_rubel@instantiations.com)
137: /**
138: * Return the menu identifiers for this extender.
139: *
140: * @return The set of all identifiers that represent this extender.
141: */
142: public Set getMenuIds() {
143: if (staticActionBuilders == null) {
144: return Collections.EMPTY_SET;
145: }
146:
147: return staticActionBuilders.keySet();
148: }
149:
150: /**
151: * <p>
152: * Adds another menu identifier to this extender. An extender can represent
153: * many menu identifiers. These identifiers should represent the same menu
154: * manager, selection provider and part. Duplicate identifiers are
155: * automatically ignored.
156: * </p>
157: * <p>
158: * For example, it is necessary to filter out duplicate identifiers for
159: * <code>CompilationUnitEditor</code> instances, as these define both
160: * <code>"#CompilationUnitEditorContext"</code> and
161: * <code>"org.eclipse.jdt.ui.CompilationUnitEditor.EditorContext"</code>
162: * as menu identifier for the same pop-up menu. We don't want to contribute
163: * duplicate items in this case.
164: * </p>
165: *
166: * @param menuId
167: * The menu identifier to add to this extender; should not be
168: * <code>null</code>.
169: */
170: public final void addMenuId(final String menuId) {
171: bitSet &= ~STATIC_ACTION_READ;
172: readStaticActionsFor(menuId);
173: }
174:
175: /**
176: * Determines whether this extender would be the same as another extender
177: * created with the given values. Two extenders are equivalent if they have
178: * the same menu manager, selection provider and part (i.e., if the menu
179: * they represent is about to show, they would populate it with duplicate
180: * values).
181: *
182: * @param menuManager
183: * The menu manager with which to compare; may be
184: * <code>null</code>.
185: * @param selectionProvider
186: * The selection provider with which to compare; may be
187: * <code>null</code>.
188: * @param part
189: * The part with which to compare; may be <code>null</code>.
190: * @return <code>true</code> if the menu manager, selection provider and
191: * part are all the same.
192: */
193: public final boolean matches(final MenuManager menuManager,
194: final ISelectionProvider selectionProvider,
195: final IWorkbenchPart part) {
196: return (this .menu == menuManager)
197: && (this .selProvider == selectionProvider)
198: && (this .part == part);
199: }
200:
201: /**
202: * Contributes items registered for the currently active editor.
203: */
204: private void addEditorActions(IMenuManager mgr) {
205: ISelectionProvider activeEditor = new ISelectionProvider() {
206:
207: /* (non-Javadoc)
208: * @see org.eclipse.jface.viewers.ISelectionProvider#addSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener)
209: */
210: public void addSelectionChangedListener(
211: ISelectionChangedListener listener) {
212: throw new UnsupportedOperationException(
213: "This ISelectionProvider is static, and cannot be modified."); //$NON-NLS-1$
214: }
215:
216: /* (non-Javadoc)
217: * @see org.eclipse.jface.viewers.ISelectionProvider#getSelection()
218: */
219: public ISelection getSelection() {
220: if (part instanceof IEditorPart) {
221: final IEditorPart editorPart = (IEditorPart) part;
222: return new StructuredSelection(
223: new Object[] { editorPart.getEditorInput() });
224: }
225:
226: return new StructuredSelection(new Object[0]);
227: }
228:
229: /* (non-Javadoc)
230: * @see org.eclipse.jface.viewers.ISelectionProvider#removeSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener)
231: */
232: public void removeSelectionChangedListener(
233: ISelectionChangedListener listener) {
234: throw new UnsupportedOperationException(
235: "This ISelectionProvider is static, and cannot be modified."); //$NON-NLS-1$
236: }
237:
238: /* (non-Javadoc)
239: * @see org.eclipse.jface.viewers.ISelectionProvider#setSelection(org.eclipse.jface.viewers.ISelection)
240: */
241: public void setSelection(ISelection selection) {
242: throw new UnsupportedOperationException(
243: "This ISelectionProvider is static, and cannot be modified."); //$NON-NLS-1$
244: }
245: };
246:
247: if (ObjectActionContributorManager.getManager()
248: .contributeObjectActions(part, mgr, activeEditor)) {
249: mgr.add(new Separator());
250: }
251: }
252:
253: /**
254: * Contributes items registered for the object type(s) in
255: * the current selection.
256: */
257: private void addObjectActions(IMenuManager mgr) {
258: if (selProvider != null) {
259: if (ObjectActionContributorManager.getManager()
260: .contributeObjectActions(part, mgr, selProvider)) {
261: mgr.add(new Separator());
262: }
263: }
264: }
265:
266: /**
267: * Disposes all of the static actions.
268: */
269: private final void clearStaticActions() {
270: bitSet &= ~STATIC_ACTION_READ;
271: if (staticActionBuilders != null) {
272: final Iterator staticActionBuilderItr = staticActionBuilders
273: .values().iterator();
274: while (staticActionBuilderItr.hasNext()) {
275: final Object staticActionBuilder = staticActionBuilderItr
276: .next();
277: if (staticActionBuilder instanceof ViewerActionBuilder) {
278: ((ViewerActionBuilder) staticActionBuilder)
279: .dispose();
280: }
281: }
282: }
283: }
284:
285: /**
286: * Adds static items to the context menu.
287: */
288: private void addStaticActions(IMenuManager mgr) {
289: if (staticActionBuilders != null) {
290: final Iterator staticActionBuilderItr = staticActionBuilders
291: .values().iterator();
292: while (staticActionBuilderItr.hasNext()) {
293: final ViewerActionBuilder staticActionBuilder = (ViewerActionBuilder) staticActionBuilderItr
294: .next();
295: staticActionBuilder.contribute(mgr, null, true);
296: }
297: }
298: }
299:
300: /**
301: * Notifies the listener that the menu is about to be shown.
302: */
303: public void menuAboutToShow(IMenuManager mgr) {
304: IMenuManager originalManager = mgr;
305:
306: // Add this menu as a visible menu.
307: final IWorkbenchPartSite site = part.getSite();
308: if (site != null) {
309: final IWorkbench workbench = site.getWorkbenchWindow()
310: .getWorkbench();
311: if (workbench instanceof Workbench) {
312: final Workbench realWorkbench = (Workbench) workbench;
313: ISelection input = null;
314: if ((bitSet & INCLUDE_EDITOR_INPUT) != 0) {
315: if (part instanceof IEditorPart) {
316: final IEditorPart editorPart = (IEditorPart) part;
317: input = new StructuredSelection(
318: new Object[] { editorPart
319: .getEditorInput() });
320: }
321: }
322: ISelection s = (selProvider == null ? null
323: : selProvider.getSelection());
324: realWorkbench.addShowingMenus(getMenuIds(), s, input);
325: }
326: }
327:
328: readStaticActions();
329: testForAdditions();
330: if (menuWrapper != null) {
331: mgr = menuWrapper;
332: menuWrapper.removeAll();
333: }
334: addMenuContributions(originalManager);
335: if ((bitSet & INCLUDE_EDITOR_INPUT) != 0) {
336: addEditorActions(mgr);
337: }
338: addObjectActions(mgr);
339: addStaticActions(mgr);
340: cleanUpContributionCache();
341: }
342:
343: private boolean contributionsPopulated = false;
344:
345: private void addMenuContributions(IMenuManager mgr) {
346: final IMenuService menuService = (IMenuService) part.getSite()
347: .getService(IMenuService.class);
348: if (menuService == null) {
349: return;
350: }
351: if ((mgr.getRemoveAllWhenShown() || !contributionsPopulated)
352: && mgr instanceof ContributionManager) {
353: ContributionManager manager = (ContributionManager) mgr;
354: contributionsPopulated = true;
355: menuService.populateContributionManager(manager,
356: MenuUtil.ANY_POPUP);
357: Iterator i = getMenuIds().iterator();
358: WindowMenuService realService = (WindowMenuService) menuService;
359: while (i.hasNext()) {
360: String id = "popup:" + i.next(); //$NON-NLS-1$
361: realService.populateContributionManager(manager, id,
362: false);
363: }
364: }
365: }
366:
367: /**
368: * Notifies the listener that the menu is about to be hidden.
369: */
370: public final void menuAboutToHide(final IMenuManager mgr) {
371: gatherContributions(mgr);
372: // Remove this menu as a visible menu.
373: final IWorkbenchPartSite site = part.getSite();
374: if (site != null) {
375: final IWorkbench workbench = site.getWorkbenchWindow()
376: .getWorkbench();
377: if (workbench instanceof Workbench) {
378: // try delaying this until after the selection event
379: // has been fired.
380: // This is less threatening if the popup: menu
381: // contributions aren't tied to the evaluation service
382: workbench.getDisplay().asyncExec(new Runnable() {
383: public void run() {
384: final Workbench realWorkbench = (Workbench) workbench;
385: realWorkbench.removeShowingMenus(getMenuIds(),
386: null, null);
387: }
388: });
389: }
390: }
391: }
392:
393: /**
394: * @param mgr
395: */
396: private void gatherContributions(final IMenuManager mgr) {
397: final IContributionItem[] items = mgr.getItems();
398: for (int i = 0; i < items.length; i++) {
399: if (items[i] instanceof PluginActionContributionItem) {
400: contributionCache.add(items[i]);
401: } else if (items[i] instanceof IMenuManager) {
402: gatherContributions(((IMenuManager) items[i]));
403: }
404: }
405: }
406:
407: private void cleanUpContributionCache() {
408: PluginActionContributionItem[] items = (PluginActionContributionItem[]) contributionCache
409: .toArray(new PluginActionContributionItem[contributionCache
410: .size()]);
411: contributionCache.clear();
412: for (int i = 0; i < items.length; i++) {
413: items[i].dispose();
414: }
415: }
416:
417: /**
418: * Read all of the static items for the content menu.
419: */
420: private final void readStaticActions() {
421: if (staticActionBuilders != null) {
422: final Iterator menuIdItr = staticActionBuilders.keySet()
423: .iterator();
424: while (menuIdItr.hasNext()) {
425: final String menuId = (String) menuIdItr.next();
426: readStaticActionsFor(menuId);
427: }
428: }
429: }
430:
431: /**
432: * Read static items for a particular menu id, into the context menu.
433: */
434: private void readStaticActionsFor(final String menuId) {
435: if ((bitSet & STATIC_ACTION_READ) != 0) {
436: return;
437: }
438:
439: bitSet |= STATIC_ACTION_READ;
440:
441: // If no menu id provided, then there is no contributions
442: // to add. Fix for bug #33140.
443: if ((menuId == null) || (menuId.length() < 1)) {
444: return;
445: }
446:
447: if (staticActionBuilders == null) {
448: staticActionBuilders = new HashMap();
449: }
450:
451: Object object = staticActionBuilders.get(menuId);
452: if (!(object instanceof ViewerActionBuilder)) {
453: object = new ViewerActionBuilder();
454: staticActionBuilders.put(menuId, object);
455: }
456: final ViewerActionBuilder staticActionBuilder = (ViewerActionBuilder) object;
457: staticActionBuilder.readViewerContributions(menuId,
458: selProvider, part);
459: }
460:
461: /**
462: * Checks for the existance of an MB_ADDITIONS group.
463: */
464: private void testForAdditions() {
465: IContributionItem item = menu
466: .find(IWorkbenchActionConstants.MB_ADDITIONS);
467: if (item == null) {
468: WorkbenchPlugin
469: .log("Context menu missing standard group 'org.eclipse.ui.IWorkbenchActionConstants.MB_ADDITIONS'. (menu ids = " //$NON-NLS-1$
470: + getMenuIds().toString()
471: + ") part id = " //$NON-NLS-1$
472: + (part == null ? "???" : part.getSite().getId()) //$NON-NLS-1$
473: + ")"); //$NON-NLS-1$
474: }
475: }
476:
477: /**
478: * Dispose of the menu extender. Should only be called when the part
479: * is disposed.
480: */
481: public void dispose() {
482: clearStaticActions();
483: final IMenuService menuService = (IMenuService) part.getSite()
484: .getService(IMenuService.class);
485: if (menuService != null) {
486: menuService.releaseContributions(menu);
487: }
488: Platform.getExtensionRegistry().removeRegistryChangeListener(
489: this );
490: menu.removeMenuListener(this );
491: }
492:
493: /*
494: * (non-Javadoc)
495: *
496: * @see org.eclipse.core.runtime.IRegistryChangeListener#registryChanged(org.eclipse.core.runtime.IRegistryChangeEvent)
497: */
498: public void registryChanged(final IRegistryChangeEvent event) {
499: Display display = Display.getDefault();
500: if (part != null) {
501: display = part.getSite().getPage().getWorkbenchWindow()
502: .getWorkbench().getDisplay();
503: }
504: //check the delta to see if there are any viewer contribution changes. if so, null our builder to cause reparsing on the next menu show
505: IExtensionDelta[] deltas = event.getExtensionDeltas();
506: for (int i = 0; i < deltas.length; i++) {
507: IExtensionDelta delta = deltas[i];
508: IExtensionPoint extensionPoint = delta.getExtensionPoint();
509: if (extensionPoint.getNamespace().equals(
510: WorkbenchPlugin.PI_WORKBENCH)
511: && extensionPoint.getSimpleIdentifier().equals(
512: IWorkbenchRegistryConstants.PL_POPUP_MENU)) {
513:
514: boolean clearPopups = false;
515: IConfigurationElement[] elements = delta.getExtension()
516: .getConfigurationElements();
517: for (int j = 0; j < elements.length; j++) {
518: IConfigurationElement element = elements[j];
519: if (element
520: .getName()
521: .equals(
522: IWorkbenchRegistryConstants.TAG_VIEWER_CONTRIBUTION)) {
523: clearPopups = true;
524: break;
525: }
526: }
527:
528: if (clearPopups) {
529: display.syncExec(new Runnable() {
530: public void run() {
531: clearStaticActions();
532: }
533: });
534: }
535: }
536: }
537: }
538:
539: public MenuManager getManager() {
540: return menu;
541: }
542: }
|