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: *******************************************************************************/package org.eclipse.ui;
011:
012: import java.util.HashMap;
013: import java.util.Iterator;
014: import java.util.Map;
015: import java.util.WeakHashMap;
016:
017: import org.eclipse.core.commands.IHandler;
018: import org.eclipse.core.commands.common.EventManager;
019: import org.eclipse.core.expressions.EvaluationResult;
020: import org.eclipse.core.expressions.Expression;
021: import org.eclipse.core.expressions.ExpressionInfo;
022: import org.eclipse.core.expressions.IEvaluationContext;
023: import org.eclipse.jface.action.IAction;
024: import org.eclipse.jface.action.IMenuManager;
025: import org.eclipse.jface.action.IStatusLineManager;
026: import org.eclipse.jface.action.IToolBarManager;
027: import org.eclipse.jface.action.SubMenuManager;
028: import org.eclipse.jface.action.SubStatusLineManager;
029: import org.eclipse.jface.action.SubToolBarManager;
030: import org.eclipse.jface.commands.ActionHandler;
031: import org.eclipse.jface.util.IPropertyChangeListener;
032: import org.eclipse.jface.util.PropertyChangeEvent;
033: import org.eclipse.ui.handlers.IHandlerActivation;
034: import org.eclipse.ui.handlers.IHandlerService;
035: import org.eclipse.ui.internal.EditorActionBars;
036: import org.eclipse.ui.internal.WorkbenchPlugin;
037: import org.eclipse.ui.internal.actions.CommandAction;
038: import org.eclipse.ui.internal.handlers.CommandLegacyActionWrapper;
039: import org.eclipse.ui.internal.handlers.IActionCommandMappingService;
040: import org.eclipse.ui.internal.services.SourcePriorityNameMapping;
041: import org.eclipse.ui.services.IServiceLocator;
042:
043: /**
044: * Generic implementation of the <code>IActionBars</code> interface.
045: */
046: public class SubActionBars extends EventManager implements IActionBars {
047:
048: /**
049: * The expression to use when contributing handlers through
050: * {@link #setGlobalActionHandler(String, IAction)}}. This ensures that
051: * handlers contributed through {@link SubActionBars} are given priority
052: * over handlers contributed to the {@link IHandlerService}.
053: */
054: private static final Expression EXPRESSION = new Expression() {
055: public final EvaluationResult evaluate(
056: final IEvaluationContext context) {
057: return EvaluationResult.TRUE;
058: }
059:
060: public final void collectExpressionInfo(
061: final ExpressionInfo info) {
062: info
063: .addVariableNameAccess(SourcePriorityNameMapping.LEGACY_LEGACY_NAME);
064: }
065: };
066:
067: /**
068: * Property constant for changes to action handlers.
069: */
070: public static final String P_ACTION_HANDLERS = "org.eclipse.ui.internal.actionHandlers"; //$NON-NLS-1$
071:
072: private Map actionHandlers;
073:
074: private boolean actionHandlersChanged;
075:
076: /**
077: * A map of handler activations ({@link IHandlerActivation} indexed by
078: * action id ({@link String}) indexed by service locator ({@link IServiceLocator}).
079: * This value is <code>null</code> if there are no activations.
080: */
081: private Map activationsByActionIdByServiceLocator;
082:
083: private boolean active = false;
084:
085: private SubMenuManager menuMgr;
086:
087: private IActionBars parent;
088:
089: /**
090: * A service locator appropriate for this action bar. This value is never
091: * <code>null</code>. It must be capable of providing a
092: * {@link IHandlerService}.
093: */
094: private IServiceLocator serviceLocator;
095:
096: private SubStatusLineManager statusLineMgr;
097:
098: private SubToolBarManager toolBarMgr;
099:
100: private Map actionIdByCommandId = new HashMap();
101:
102: /**
103: * Construct a new <code>SubActionBars</code> object. The service locator
104: * will simply be the service locator of the parent.
105: *
106: * @param parent
107: * The parent of this action bar; must not be <code>null</code>.
108: */
109: public SubActionBars(final IActionBars parent) {
110: this (parent, null);
111: }
112:
113: /**
114: * Constructs a new instance of <code>SubActionBars</code>.
115: *
116: * @param parent
117: * The parent of this action bar; must not be <code>null</code>.
118: * @param serviceLocator
119: * The service locator for this action bar; should not be
120: * <code>null</code>.
121: *
122: * @since 3.2
123: */
124: public SubActionBars(final IActionBars parent,
125: final IServiceLocator serviceLocator) {
126: if (parent == null) {
127: throw new NullPointerException("The parent cannot be null"); //$NON-NLS-1$
128: }
129:
130: this .parent = parent;
131: this .serviceLocator = serviceLocator;
132: }
133:
134: /**
135: * Activate the contributions.
136: */
137: public void activate() {
138: activate(true);
139: }
140:
141: /**
142: * Activate the contributions.
143: * <p>
144: * Workaround for toolbar layout flashing when editors contribute large
145: * amounts of items. In this case we want to force the items to be
146: * visible/hidden only when required, otherwise just change the enablement
147: * state.
148: * </p>
149: */
150: public void activate(boolean forceVisibility) {
151: setActive(true);
152: }
153:
154: /**
155: * Adds a property change listener. Has no effect if an identical listener
156: * is already registered.
157: *
158: * @param listener
159: * a property change listener
160: */
161: public void addPropertyChangeListener(
162: IPropertyChangeListener listener) {
163: addListenerObject(listener);
164: }
165:
166: /**
167: * Sets the active flag. Clients should not call this method directly unless
168: * they are overriding the setActive() method.
169: */
170: protected final void basicSetActive(boolean active) {
171: this .active = active;
172: }
173:
174: /**
175: * Clear the global action handlers.
176: */
177: public void clearGlobalActionHandlers() {
178: if (actionHandlers != null) {
179: actionHandlers.clear();
180: actionHandlersChanged = true;
181: }
182:
183: if (activationsByActionIdByServiceLocator != null) {
184: // Clean up the activations.
185: final Iterator activationItr = activationsByActionIdByServiceLocator
186: .entrySet().iterator();
187: while (activationItr.hasNext()) {
188: final Map.Entry value = (Map.Entry) activationItr
189: .next();
190: final IServiceLocator locator = (IServiceLocator) value
191: .getKey();
192: final IHandlerService service = (IHandlerService) locator
193: .getService(IHandlerService.class);
194: final Map activationsByActionId = (Map) value
195: .getValue();
196: final Iterator iterator = activationsByActionId
197: .values().iterator();
198: while (iterator.hasNext()) {
199: final IHandlerActivation activation = (IHandlerActivation) iterator
200: .next();
201: service.deactivateHandler(activation);
202: activation.getHandler().dispose();
203: }
204: }
205: activationsByActionIdByServiceLocator.clear();
206: }
207: }
208:
209: /**
210: * Returns a new sub menu manager.
211: *
212: * @param parent
213: * the parent menu manager
214: * @return the menu manager
215: */
216: protected SubMenuManager createSubMenuManager(IMenuManager parent) {
217: return new SubMenuManager(parent);
218: }
219:
220: /**
221: * Returns a new sub toolbar manager.
222: *
223: * @param parent
224: * the parent toolbar manager
225: * @return the tool bar manager
226: */
227: protected SubToolBarManager createSubToolBarManager(
228: IToolBarManager parent) {
229: return new SubToolBarManager(parent);
230: }
231:
232: /**
233: * Deactivate the contributions.
234: */
235: public void deactivate() {
236: deactivate(true);
237: }
238:
239: /**
240: * Deactivate the contributions.
241: * <p>
242: * Workaround for menubar/toolbar layout flashing when editors have many
243: * contributions. In this case we want to force the contributions to be
244: * visible/hidden only when required, otherwise just change the enablement
245: * state.
246: * </p>
247: */
248: public void deactivate(boolean forceHide) {
249: setActive(false);
250: }
251:
252: /**
253: * Dispose the contributions.
254: */
255: public void dispose() {
256: clearGlobalActionHandlers();
257: if (menuMgr != null) {
258: menuMgr.dispose();
259: menuMgr.disposeManager();
260: }
261: if (statusLineMgr != null) {
262: statusLineMgr.disposeManager();
263: }
264: if (toolBarMgr != null) {
265: toolBarMgr.disposeManager();
266: }
267: clearListeners();
268: }
269:
270: /**
271: * Notifies any property change listeners if the global action handlers have
272: * changed
273: */
274: protected void fireActionHandlersChanged() {
275: if (actionHandlersChanged) {
276: // Doesn't actually pass the old and new values
277: firePropertyChange(new PropertyChangeEvent(this ,
278: P_ACTION_HANDLERS, null, null));
279: actionHandlersChanged = false;
280: }
281: }
282:
283: /**
284: * Notifies any property change listeners that a property has changed. Only
285: * listeners registered at the time this method is called are notified.
286: *
287: * @param event
288: * the property change event
289: *
290: * @see IPropertyChangeListener#propertyChange
291: */
292: protected void firePropertyChange(PropertyChangeEvent event) {
293: Object[] listeners = getListeners();
294: for (int i = 0; i < listeners.length; ++i) {
295: ((IPropertyChangeListener) listeners[i])
296: .propertyChange(event);
297: }
298: }
299:
300: /**
301: * Return whether the manager is currently active or not.
302: */
303: protected final boolean getActive() {
304: return active;
305: }
306:
307: /**
308: * Get the handler for a window action.
309: *
310: * @param actionID
311: * an action ID declared in the registry
312: * @return an action handler which implements the action ID, or
313: * <code>null</code> if none is registered.
314: */
315: public IAction getGlobalActionHandler(String actionID) {
316: if (actionHandlers == null) {
317: return null;
318: }
319: return (IAction) actionHandlers.get(actionID);
320: }
321:
322: /**
323: * Returns the complete list of active global action handlers. If there are
324: * no global action handlers registered return null.
325: */
326: public Map getGlobalActionHandlers() {
327: return actionHandlers;
328: }
329:
330: /**
331: * Returns the abstract menu manager. If items are added or removed from the
332: * manager be sure to call <code>updateActionBars</code>.
333: *
334: * @return the menu manager
335: */
336: public IMenuManager getMenuManager() {
337: if (menuMgr == null) {
338: menuMgr = createSubMenuManager(parent.getMenuManager());
339: menuMgr.setVisible(active);
340: }
341: return menuMgr;
342: }
343:
344: /**
345: * Return the parent action bar manager.
346: */
347: protected final IActionBars getParent() {
348: return parent;
349: }
350:
351: /**
352: * Answer the service locator for this action bar.
353: *
354: * @return an <code>IServiceLocater</code> or the parents if
355: * the receiver does not have one
356: *
357: * @since 3.2
358: */
359: public final IServiceLocator getServiceLocator() {
360: if (serviceLocator != null) {
361: return serviceLocator;
362: }
363:
364: return parent.getServiceLocator();
365: }
366:
367: /**
368: * Returns the status line manager. If items are added or removed from the
369: * manager be sure to call <code>updateActionBars</code>.
370: *
371: * @return the status line manager
372: */
373: public IStatusLineManager getStatusLineManager() {
374: if (statusLineMgr == null) {
375: statusLineMgr = new SubStatusLineManager(parent
376: .getStatusLineManager());
377: statusLineMgr.setVisible(active);
378: }
379: return statusLineMgr;
380: }
381:
382: /**
383: * Returns the tool bar manager. If items are added or removed from the
384: * manager be sure to call <code>updateActionBars</code>.
385: *
386: * @return the tool bar manager
387: */
388: public IToolBarManager getToolBarManager() {
389: if (toolBarMgr == null) {
390: toolBarMgr = createSubToolBarManager(parent
391: .getToolBarManager());
392: toolBarMgr.setVisible(active);
393: }
394: return toolBarMgr;
395: }
396:
397: /**
398: * Return whether the sub menu manager has been created yet.
399: */
400: protected final boolean isSubMenuManagerCreated() {
401: return menuMgr != null;
402: }
403:
404: /**
405: * Return whether the sub status line manager has been created yet.
406: */
407: protected final boolean isSubStatusLineManagerCreated() {
408: return statusLineMgr != null;
409: }
410:
411: /**
412: * Return whether the sub toolbar manager has been created yet.
413: */
414: protected final boolean isSubToolBarManagerCreated() {
415: return toolBarMgr != null;
416: }
417:
418: /**
419: * Notification that the target part for the action bars has changed.
420: */
421: public void partChanged(IWorkbenchPart part) {
422: }
423:
424: /**
425: * Removes the given property change listener. Has no effect if an identical
426: * listener is not registered.
427: *
428: * @param listener
429: * a property change listener
430: */
431: public void removePropertyChangeListener(
432: IPropertyChangeListener listener) {
433: removeListenerObject(listener);
434: }
435:
436: /**
437: * Activate / deactivate the contributions.
438: */
439: protected void setActive(boolean set) {
440: active = set;
441: if (menuMgr != null) {
442: menuMgr.setVisible(set);
443: }
444:
445: if (statusLineMgr != null) {
446: statusLineMgr.setVisible(set);
447: }
448:
449: if (toolBarMgr != null) {
450: toolBarMgr.setVisible(set);
451: }
452: }
453:
454: /**
455: * Add a handler for a window action.
456: *
457: * @param actionID
458: * an action ID declared in the registry
459: * @param handler
460: * an action which implements the action ID. <code>null</code>
461: * may be passed to deregister a handler.
462: */
463: public void setGlobalActionHandler(String actionID, IAction handler) {
464: if (actionID == null) {
465: /*
466: * Bug 124061. It used to be invalid to pass null as an action id,
467: * but some people still did it. Handle this case by trapping the
468: * exception and logging it.
469: */
470: WorkbenchPlugin
471: .log("Cannot set the global action handler for a null action id"); //$NON-NLS-1$
472: return;
473: }
474:
475: if (handler instanceof CommandLegacyActionWrapper) {
476: // this is a registration of a fake action for an already
477: // registered handler
478: WorkbenchPlugin
479: .log("Cannot feed a CommandLegacyActionWrapper back into the system"); //$NON-NLS-1$
480: return;
481: }
482:
483: if (handler instanceof CommandAction) {
484: // we unfortunately had to allow these out into the wild, but they
485: // still must not feed back into the system
486: return;
487: }
488:
489: if (handler != null) {
490: // Update the action handlers.
491: if (actionHandlers == null) {
492: actionHandlers = new HashMap(11);
493: }
494: actionHandlers.put(actionID, handler);
495:
496: // Add a mapping from this action id to the command id.
497: if (serviceLocator != null) {
498: final IActionCommandMappingService mappingService = (IActionCommandMappingService) serviceLocator
499: .getService(IActionCommandMappingService.class);
500: final String commandId = mappingService
501: .getCommandId(actionID);
502:
503: // Update the handler activations.
504: final IHandlerService service = (IHandlerService) serviceLocator
505: .getService(IHandlerService.class);
506: Map activationsByActionId = null;
507: if (activationsByActionIdByServiceLocator == null) {
508: activationsByActionIdByServiceLocator = new WeakHashMap();
509: activationsByActionId = new HashMap();
510: activationsByActionIdByServiceLocator.put(
511: serviceLocator, activationsByActionId);
512: } else {
513: activationsByActionId = (Map) activationsByActionIdByServiceLocator
514: .get(serviceLocator);
515: if (activationsByActionId == null) {
516: activationsByActionId = new HashMap();
517: activationsByActionIdByServiceLocator.put(
518: serviceLocator, activationsByActionId);
519: } else if (activationsByActionId
520: .containsKey(actionID)) {
521: final Object value = activationsByActionId
522: .remove(actionID);
523: if (value instanceof IHandlerActivation) {
524: final IHandlerActivation activation = (IHandlerActivation) value;
525: actionIdByCommandId.remove(activation
526: .getCommandId());
527: service.deactivateHandler(activation);
528: activation.getHandler().dispose();
529: }
530: } else if (commandId != null
531: && actionIdByCommandId
532: .containsKey(commandId)) {
533: final Object value = activationsByActionId
534: .remove(actionIdByCommandId
535: .remove(commandId));
536: if (value instanceof IHandlerActivation) {
537: final IHandlerActivation activation = (IHandlerActivation) value;
538: service.deactivateHandler(activation);
539: activation.getHandler().dispose();
540: }
541: }
542: }
543:
544: if (commandId != null) {
545: actionIdByCommandId.put(commandId, actionID);
546: // Register this as a handler with the given definition id.
547: // the expression gives the setGlobalActionHandler() a
548: // priority.
549: final IHandler actionHandler = new ActionHandler(
550: handler);
551: Expression handlerExpression = EXPRESSION;
552: //XXX add new API in next release to avoid down-casting (bug 137091)
553: if (this instanceof EditorActionBars) {
554: handlerExpression = ((EditorActionBars) this )
555: .getHandlerExpression();
556: }
557: final IHandlerActivation activation = service
558: .activateHandler(commandId, actionHandler,
559: handlerExpression);
560: activationsByActionId.put(actionID, activation);
561: }
562: }
563:
564: } else {
565: if (actionHandlers != null) {
566: actionHandlers.remove(actionID);
567: }
568:
569: // Remove the handler activation.
570: if (serviceLocator != null) {
571: final IHandlerService service = (IHandlerService) serviceLocator
572: .getService(IHandlerService.class);
573: if (activationsByActionIdByServiceLocator != null) {
574: final Map activationsByActionId = (Map) activationsByActionIdByServiceLocator
575: .get(serviceLocator);
576: if ((activationsByActionId != null)
577: && (activationsByActionId
578: .containsKey(actionID))) {
579: final Object value = activationsByActionId
580: .remove(actionID);
581: if (value instanceof IHandlerActivation) {
582: final IHandlerActivation activation = (IHandlerActivation) value;
583: actionIdByCommandId.remove(activation
584: .getCommandId());
585: service.deactivateHandler(activation);
586: activation.getHandler().dispose();
587: }
588: }
589: }
590: }
591: }
592: actionHandlersChanged = true;
593: }
594:
595: /**
596: * Sets the service locator for this action bar.
597: *
598: * @param locator
599: * The new locator; must not be <code>null</code>.
600: *
601: * @since 3.2
602: */
603: protected final void setServiceLocator(final IServiceLocator locator) {
604: if (locator == null) {
605: throw new NullPointerException(
606: "The service locator cannot be null"); //$NON-NLS-1$
607: }
608: this .serviceLocator = locator;
609: }
610:
611: /**
612: * Commits all UI changes. This should be called after additions or
613: * subtractions have been made to a menu, status line, or toolbar.
614: */
615: public void updateActionBars() {
616: parent.updateActionBars();
617: fireActionHandlersChanged();
618: }
619: }
|