001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 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.jface.action;
011:
012: import java.text.MessageFormat; // Not using ICU to support standalone JFace scenario
013: import java.util.HashMap;
014: import java.util.HashSet;
015: import java.util.Iterator;
016: import java.util.Map;
017: import java.util.ResourceBundle;
018: import java.util.Set;
019:
020: import org.eclipse.core.commands.Command;
021: import org.eclipse.core.commands.CommandEvent;
022: import org.eclipse.core.commands.CommandManager;
023: import org.eclipse.core.commands.ICommandListener;
024: import org.eclipse.core.commands.ParameterizedCommand;
025: import org.eclipse.core.runtime.IStatus;
026: import org.eclipse.core.runtime.Status;
027: import org.eclipse.jface.bindings.BindingManager;
028: import org.eclipse.jface.bindings.BindingManagerEvent;
029: import org.eclipse.jface.bindings.IBindingManagerListener;
030: import org.eclipse.jface.bindings.Trigger;
031: import org.eclipse.jface.bindings.TriggerSequence;
032: import org.eclipse.jface.bindings.keys.KeySequence;
033: import org.eclipse.jface.bindings.keys.KeyStroke;
034: import org.eclipse.jface.bindings.keys.SWTKeySupport;
035: import org.eclipse.jface.util.IPropertyChangeListener;
036: import org.eclipse.jface.util.Policy;
037: import org.eclipse.jface.util.PropertyChangeEvent;
038: import org.eclipse.jface.util.Util;
039:
040: /**
041: * <p>
042: * A manager for a callback facility which is capable of querying external
043: * interfaces for additional information about actions and action contribution
044: * items. This information typically includes things like accelerators and
045: * textual representations.
046: * </p>
047: * <p>
048: * <em>It is only necessary to use this mechanism if you will be using a mix of
049: * actions and commands, and wish the interactions to work properly.</em>
050: * </p>
051: * <p>
052: * For example, in the Eclipse workbench, this mechanism is used to allow the
053: * command architecture to override certain values in action contribution items.
054: * </p>
055: * <p>
056: * This class is not intended to be called or extended by any external clients.
057: * </p>
058: *
059: * @since 3.0
060: */
061: public final class ExternalActionManager {
062:
063: /**
064: * A simple implementation of the <code>ICallback</code> mechanism that
065: * simply takes a <code>BindingManager</code> and a
066: * <code>CommandManager</code>.
067: *
068: * @since 3.1
069: */
070: public static final class CommandCallback implements
071: IBindingManagerListener, IBindingManagerCallback {
072:
073: /**
074: * The internationalization bundle for text produced by this class.
075: */
076: private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle
077: .getBundle(ExternalActionManager.class.getName());
078:
079: /**
080: * The callback capable of responding to whether a command is active.
081: */
082: private final IActiveChecker activeChecker;
083:
084: /**
085: * The binding manager for your application. Must not be
086: * <code>null</code>.
087: */
088: private final BindingManager bindingManager;
089:
090: /**
091: * Whether a listener has been attached to the binding manager yet.
092: */
093: private boolean bindingManagerListenerAttached = false;
094:
095: /**
096: * The command manager for your application. Must not be
097: * <code>null</code>.
098: */
099: private final CommandManager commandManager;
100:
101: /**
102: * A set of all the command identifiers that have been logged as broken
103: * so far. For each of these, there will be a listener on the
104: * corresponding command. If the command ever becomes defined, the item
105: * will be removed from this set and the listener removed. This value
106: * may be empty, but never <code>null</code>.
107: */
108: private final Set loggedCommandIds = new HashSet();
109:
110: /**
111: * The list of listeners that have registered for property change
112: * notification. This is a map of command identifiers (<code>String</code>)
113: * to listeners (<code>IPropertyChangeListener</code>).
114: */
115: private final Map registeredListeners = new HashMap();
116:
117: /**
118: * Constructs a new instance of <code>CommandCallback</code> with the
119: * workbench it should be using. All commands will be considered active.
120: *
121: * @param bindingManager
122: * The binding manager which will provide the callback; must
123: * not be <code>null</code>.
124: * @param commandManager
125: * The command manager which will provide the callback; must
126: * not be <code>null</code>.
127: *
128: * @since 3.1
129: */
130: public CommandCallback(final BindingManager bindingManager,
131: final CommandManager commandManager) {
132: this (bindingManager, commandManager, new IActiveChecker() {
133: public boolean isActive(String commandId) {
134: return true;
135: }
136:
137: });
138: }
139:
140: /**
141: * Constructs a new instance of <code>CommandCallback</code> with the
142: * workbench it should be using.
143: *
144: * @param bindingManager
145: * The binding manager which will provide the callback; must
146: * not be <code>null</code>.
147: * @param commandManager
148: * The command manager which will provide the callback; must
149: * not be <code>null</code>.
150: * @param activeChecker
151: * The callback mechanism for checking whether a command is
152: * active; must not be <code>null</code>.
153: *
154: * @since 3.1
155: */
156: public CommandCallback(final BindingManager bindingManager,
157: final CommandManager commandManager,
158: final IActiveChecker activeChecker) {
159: if (bindingManager == null) {
160: throw new NullPointerException(
161: "The callback needs a binding manager"); //$NON-NLS-1$
162: }
163:
164: if (commandManager == null) {
165: throw new NullPointerException(
166: "The callback needs a command manager"); //$NON-NLS-1$
167: }
168:
169: if (activeChecker == null) {
170: throw new NullPointerException(
171: "The callback needs an active callback"); //$NON-NLS-1$
172: }
173:
174: this .activeChecker = activeChecker;
175: this .bindingManager = bindingManager;
176: this .commandManager = commandManager;
177: }
178:
179: /**
180: * @see org.eclipse.jface.action.ExternalActionManager.ICallback#addPropertyChangeListener(String,
181: * IPropertyChangeListener)
182: */
183: public final void addPropertyChangeListener(
184: final String commandId,
185: final IPropertyChangeListener listener) {
186: registeredListeners.put(commandId, listener);
187: if (!bindingManagerListenerAttached) {
188: bindingManager.addBindingManagerListener(this );
189: bindingManagerListenerAttached = true;
190: }
191: }
192:
193: public final void bindingManagerChanged(
194: final BindingManagerEvent event) {
195: if (event.isActiveBindingsChanged()) {
196: final Iterator listenerItr = registeredListeners
197: .entrySet().iterator();
198: while (listenerItr.hasNext()) {
199: final Map.Entry entry = (Map.Entry) listenerItr
200: .next();
201: final String commandId = (String) entry.getKey();
202: final Command command = commandManager
203: .getCommand(commandId);
204: final ParameterizedCommand parameterizedCommand = new ParameterizedCommand(
205: command, null);
206: if (event
207: .isActiveBindingsChangedFor(parameterizedCommand)) {
208: final IPropertyChangeListener listener = (IPropertyChangeListener) entry
209: .getValue();
210: listener
211: .propertyChange(new PropertyChangeEvent(
212: event.getManager(),
213: IAction.TEXT, null, null));
214: }
215: }
216: }
217: }
218:
219: /**
220: * @see org.eclipse.jface.action.ExternalActionManager.ICallback#getAccelerator(String)
221: */
222: public final Integer getAccelerator(final String commandId) {
223: final TriggerSequence triggerSequence = bindingManager
224: .getBestActiveBindingFor(commandId);
225: if (triggerSequence != null) {
226: final Trigger[] triggers = triggerSequence
227: .getTriggers();
228: if (triggers.length == 1) {
229: final Trigger trigger = triggers[0];
230: if (trigger instanceof KeyStroke) {
231: final KeyStroke keyStroke = (KeyStroke) trigger;
232: final int accelerator = SWTKeySupport
233: .convertKeyStrokeToAccelerator(keyStroke);
234: return new Integer(accelerator);
235: }
236: }
237: }
238:
239: return null;
240: }
241:
242: /**
243: * @see org.eclipse.jface.action.ExternalActionManager.ICallback#getAcceleratorText(String)
244: */
245: public final String getAcceleratorText(final String commandId) {
246: final TriggerSequence triggerSequence = bindingManager
247: .getBestActiveBindingFor(commandId);
248: if (triggerSequence == null) {
249: return null;
250: }
251:
252: return triggerSequence.format();
253: }
254:
255: /**
256: * Returns the active bindings for a particular command identifier.
257: *
258: * @param commandId
259: * The identifier of the command whose bindings are
260: * requested. This argument may be <code>null</code>. It
261: * is assumed that the command has no parameters.
262: * @return The array of active triggers (<code>TriggerSequence</code>)
263: * for a particular command identifier. This value is guaranteed
264: * not to be <code>null</code>, but it may be empty.
265: * @since 3.2
266: */
267: public final TriggerSequence[] getActiveBindingsFor(
268: final String commandId) {
269: return bindingManager.getActiveBindingsFor(commandId);
270: }
271:
272: /**
273: * @see org.eclipse.jface.action.ExternalActionManager.ICallback#isAcceleratorInUse(int)
274: */
275: public final boolean isAcceleratorInUse(final int accelerator) {
276: final KeySequence keySequence = KeySequence
277: .getInstance(SWTKeySupport
278: .convertAcceleratorToKeyStroke(accelerator));
279: return bindingManager.isPerfectMatch(keySequence)
280: || bindingManager.isPartialMatch(keySequence);
281: }
282:
283: /**
284: * {@inheritDoc}
285: *
286: * Calling this method with an undefined command id will generate a log
287: * message.
288: */
289: public final boolean isActive(final String commandId) {
290: if (commandId != null) {
291: final Command command = commandManager
292: .getCommand(commandId);
293:
294: if (!command.isDefined()
295: && (!loggedCommandIds.contains(commandId))) {
296: // The command is not yet defined, so we should log this.
297: final String message = MessageFormat
298: .format(
299: Util
300: .translateString(
301: RESOURCE_BUNDLE,
302: "undefinedCommand.WarningMessage", null), //$NON-NLS-1$
303: new String[] { command.getId() });
304: IStatus status = new Status(IStatus.ERROR,
305: "org.eclipse.jface", //$NON-NLS-1$
306: 0, message, new Exception());
307: Policy.getLog().log(status);
308:
309: // And remember this item so we don't log it again.
310: loggedCommandIds.add(commandId);
311: command.addCommandListener(new ICommandListener() {
312: /*
313: * (non-Javadoc)
314: *
315: * @see org.eclipse.ui.commands.ICommandListener#commandChanged(org.eclipse.ui.commands.CommandEvent)
316: */
317: public final void commandChanged(
318: final CommandEvent commandEvent) {
319: if (command.isDefined()) {
320: command.removeCommandListener(this );
321: loggedCommandIds.remove(commandId);
322: }
323: }
324: });
325:
326: return true;
327: }
328:
329: return activeChecker.isActive(commandId);
330: }
331:
332: return true;
333: }
334:
335: /**
336: * @see org.eclipse.jface.action.ExternalActionManager.ICallback#removePropertyChangeListener(String,
337: * IPropertyChangeListener)
338: */
339: public final void removePropertyChangeListener(
340: final String commandId,
341: final IPropertyChangeListener listener) {
342: final IPropertyChangeListener existingListener = (IPropertyChangeListener) registeredListeners
343: .get(commandId);
344: if (existingListener == listener) {
345: registeredListeners.remove(commandId);
346: if (registeredListeners.isEmpty()) {
347: bindingManager.removeBindingManagerListener(this );
348: bindingManagerListenerAttached = false;
349: }
350: }
351: }
352: }
353:
354: /**
355: * Defines a callback mechanism for developer who wish to further control
356: * the visibility of legacy action-based contribution items.
357: *
358: * @since 3.1
359: */
360: public static interface IActiveChecker {
361: /**
362: * Checks whether the command with the given identifier should be
363: * considered active. This can be used in systems using some kind of
364: * user interface filtering (e.g., activities in the Eclipse workbench).
365: *
366: * @param commandId
367: * The identifier for the command; must not be
368: * <code>null</code>
369: * @return <code>true</code> if the command is active;
370: * <code>false</code> otherwise.
371: */
372: public boolean isActive(String commandId);
373: }
374:
375: /**
376: * <p>
377: * A callback which communicates with the applications binding manager. This
378: * interface provides more information from the binding manager, which
379: * allows greater integration. Implementing this interface is preferred over
380: * {@link ExternalActionManager.ICallback}.
381: * </p>
382: * <p>
383: * Clients may implement this interface, but must not extend.
384: * </p>
385: *
386: * @since 3.2
387: */
388: public static interface IBindingManagerCallback extends ICallback {
389:
390: /**
391: * <p>
392: * Returns the active bindings for a particular command identifier.
393: * </p>
394: *
395: * @param commandId
396: * The identifier of the command whose bindings are
397: * requested. This argument may be <code>null</code>. It
398: * is assumed that the command has no parameters.
399: * @return The array of active triggers (<code>TriggerSequence</code>)
400: * for a particular command identifier. This value is guaranteed
401: * not to be <code>null</code>, but it may be empty.
402: */
403: public TriggerSequence[] getActiveBindingsFor(String commandId);
404: }
405:
406: /**
407: * A callback mechanism for some external tool to communicate extra
408: * information to actions and action contribution items.
409: *
410: * @since 3.0
411: */
412: public static interface ICallback {
413:
414: /**
415: * <p>
416: * Adds a listener to the object referenced by <code>identifier</code>.
417: * This listener will be notified if a property of the item is to be
418: * changed. This identifier is specific to mechanism being used. In the
419: * case of the Eclipse workbench, this is the command identifier.
420: * </p>
421: * <p>
422: * A single instance of the listener may only ever be associated with
423: * one identifier. Attempts to add the listener twice (without a removal
424: * in between) has undefined behaviour.
425: * </p>
426: *
427: * @param identifier
428: * The identifier of the item to which the listener should be
429: * attached; must not be <code>null</code>.
430: * @param listener
431: * The listener to be added; must not be <code>null</code>.
432: */
433: public void addPropertyChangeListener(String identifier,
434: IPropertyChangeListener listener);
435:
436: /**
437: * An accessor for the accelerator associated with the item indicated by
438: * the identifier. This identifier is specific to mechanism being used.
439: * In the case of the Eclipse workbench, this is the command identifier.
440: *
441: * @param identifier
442: * The identifier of the item from which the accelerator
443: * should be obtained ; must not be <code>null</code>.
444: * @return An integer representation of the accelerator. This is the
445: * same accelerator format used by SWT.
446: */
447: public Integer getAccelerator(String identifier);
448:
449: /**
450: * An accessor for the accelerator text associated with the item
451: * indicated by the identifier. This identifier is specific to mechanism
452: * being used. In the case of the Eclipse workbench, this is the command
453: * identifier.
454: *
455: * @param identifier
456: * The identifier of the item from which the accelerator text
457: * should be obtained ; must not be <code>null</code>.
458: * @return A string representation of the accelerator. This is the
459: * string representation that should be displayed to the user.
460: */
461: public String getAcceleratorText(String identifier);
462:
463: /**
464: * Checks to see whether the given accelerator is being used by some
465: * other mechanism (outside of the menus controlled by JFace). This is
466: * used to keep JFace from trying to grab accelerators away from someone
467: * else.
468: *
469: * @param accelerator
470: * The accelerator to check -- in SWT's internal accelerator
471: * format.
472: * @return <code>true</code> if the accelerator is already being used
473: * and shouldn't be used again; <code>false</code> otherwise.
474: */
475: public boolean isAcceleratorInUse(int accelerator);
476:
477: /**
478: * Checks whether the item matching this identifier is active. This is
479: * used to decide whether a contribution item with this identifier
480: * should be made visible. An inactive item is not visible.
481: *
482: * @param identifier
483: * The identifier of the item from which the active state
484: * should be retrieved; must not be <code>null</code>.
485: * @return <code>true</code> if the item is active; <code>false</code>
486: * otherwise.
487: */
488: public boolean isActive(String identifier);
489:
490: /**
491: * Removes a listener from the object referenced by
492: * <code>identifier</code>. This identifier is specific to mechanism
493: * being used. In the case of the Eclipse workbench, this is the command
494: * identifier.
495: *
496: * @param identifier
497: * The identifier of the item to from the listener should be
498: * removed; must not be <code>null</code>.
499: * @param listener
500: * The listener to be removed; must not be <code>null</code>.
501: */
502: public void removePropertyChangeListener(String identifier,
503: IPropertyChangeListener listener);
504: }
505:
506: /**
507: * The singleton instance of this class. This value may be <code>null</code>--
508: * if it has not yet been initialized.
509: */
510: private static ExternalActionManager instance;
511:
512: /**
513: * Retrieves the current singleton instance of this class.
514: *
515: * @return The singleton instance; this value is never <code>null</code>.
516: */
517: public static ExternalActionManager getInstance() {
518: if (instance == null) {
519: instance = new ExternalActionManager();
520: }
521:
522: return instance;
523: }
524:
525: /**
526: * The callback mechanism to use to retrieve extra information.
527: */
528: private ICallback callback;
529:
530: /**
531: * Constructs a new instance of <code>ExternalActionManager</code>.
532: */
533: private ExternalActionManager() {
534: // This is a singleton class. Only this class should create an instance.
535: }
536:
537: /**
538: * An accessor for the current call back.
539: *
540: * @return The current callback mechanism being used. This is the callback
541: * that should be queried for extra information about actions and
542: * action contribution items. This value may be <code>null</code>
543: * if there is no extra information.
544: */
545: public ICallback getCallback() {
546: return callback;
547: }
548:
549: /**
550: * A mutator for the current call back
551: *
552: * @param callbackToUse
553: * The new callback mechanism to use; this value may be
554: * <code>null</code> if the default is acceptable (i.e., no
555: * extra information will provided to actions).
556: */
557: public void setCallback(ICallback callbackToUse) {
558: callback = callbackToUse;
559: }
560: }
|