Mercurial > projects > dwt-addons
diff dwtx/jface/action/ExternalActionManager.d @ 16:e0f0aaf75edd
PopupDialog, bindings and actions
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Tue, 01 Apr 2008 08:00:31 +0200 |
parents | |
children | e10d9c2648be |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/action/ExternalActionManager.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,564 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ + +module dwtx.jface.action.ExternalActionManager; + +import dwtx.jface.action.IAction; + +import tango.util.collection.HashMap; +import tango.util.collection.HashSet; +import tango.util.collection.model.Map; +import tango.util.collection.model.Set; + +import dwtx.core.commands.Command; +import dwtx.core.commands.CommandEvent; +import dwtx.core.commands.CommandManager; +import dwtx.core.commands.ICommandListener; +import dwtx.core.commands.ParameterizedCommand; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.Status; +import dwtx.jface.bindings.BindingManager; +import dwtx.jface.bindings.BindingManagerEvent; +import dwtx.jface.bindings.IBindingManagerListener; +import dwtx.jface.bindings.Trigger; +import dwtx.jface.bindings.TriggerSequence; +import dwtx.jface.bindings.keys.KeySequence; +import dwtx.jface.bindings.keys.KeyStroke; +import dwtx.jface.bindings.keys.SWTKeySupport; +import dwtx.jface.util.IPropertyChangeListener; +import dwtx.jface.util.Policy; +import dwtx.jface.util.PropertyChangeEvent; +import dwtx.jface.util.Util; + +import dwt.dwthelper.utils; +import dwt.dwthelper.ResourceBundle; +import dwt.dwthelper.Integer; +import tango.text.convert.Format; + +/** + * <p> + * A manager for a callback facility which is capable of querying external + * interfaces for additional information about actions and action contribution + * items. This information typically includes things like accelerators and + * textual representations. + * </p> + * <p> + * <em>It is only necessary to use this mechanism if you will be using a mix of + * actions and commands, and wish the interactions to work properly.</em> + * </p> + * <p> + * For example, in the Eclipse workbench, this mechanism is used to allow the + * command architecture to override certain values in action contribution items. + * </p> + * <p> + * This class is not intended to be called or extended by any external clients. + * </p> + * + * @since 3.0 + */ +public final class ExternalActionManager { + + /** + * A simple implementation of the <code>ICallback</code> mechanism that + * simply takes a <code>BindingManager</code> and a + * <code>CommandManager</code>. + * + * @since 3.1 + */ + public static final class CommandCallback : + IBindingManagerListener, IBindingManagerCallback { + + /** + * The internationalization bundle for text produced by this class. + */ + private static const ResourceBundle RESOURCE_BUNDLE; + + /** + * The callback capable of responding to whether a command is active. + */ + private const IActiveChecker activeChecker; + + /** + * The binding manager for your application. Must not be + * <code>null</code>. + */ + private const BindingManager bindingManager; + + /** + * Whether a listener has been attached to the binding manager yet. + */ + private bool bindingManagerListenerAttached = false; + + /** + * The command manager for your application. Must not be + * <code>null</code>. + */ + private const CommandManager commandManager; + + /** + * A set of all the command identifiers that have been logged as broken + * so far. For each of these, there will be a listener on the + * corresponding command. If the command ever becomes defined, the item + * will be removed from this set and the listener removed. This value + * may be empty, but never <code>null</code>. + */ + private const Set!(String) loggedCommandIds; + + /** + * The list of listeners that have registered for property change + * notification. This is a map of command identifiers (<code>String</code>) + * to listeners (<code>IPropertyChangeListener</code>). + */ + private const Map!(String,IPropertyChangeListener) registeredListeners; + + static this(){ + RESOURCE_BUNDLE = ResourceBundle.getBundle(ExternalActionManager.classinfo.name); + } + /** + * Constructs a new instance of <code>CommandCallback</code> with the + * workbench it should be using. All commands will be considered active. + * + * @param bindingManager + * The binding manager which will provide the callback; must + * not be <code>null</code>. + * @param commandManager + * The command manager which will provide the callback; must + * not be <code>null</code>. + * + * @since 3.1 + */ + public this(BindingManager bindingManager, + CommandManager commandManager) { + this(bindingManager, commandManager, new class IActiveChecker { + public bool isActive(String commandId) { + return true; + } + + }); + } + + /** + * Constructs a new instance of <code>CommandCallback</code> with the + * workbench it should be using. + * + * @param bindingManager + * The binding manager which will provide the callback; must + * not be <code>null</code>. + * @param commandManager + * The command manager which will provide the callback; must + * not be <code>null</code>. + * @param activeChecker + * The callback mechanism for checking whether a command is + * active; must not be <code>null</code>. + * + * @since 3.1 + */ + public this(BindingManager bindingManager, + CommandManager commandManager, + IActiveChecker activeChecker) { + loggedCommandIds = new HashSet!(String); + registeredListeners = new HashMap!(String,IPropertyChangeListener); + if (bindingManager is null) { + throw new NullPointerException( + "The callback needs a binding manager"); //$NON-NLS-1$ + } + + if (commandManager is null) { + throw new NullPointerException( + "The callback needs a command manager"); //$NON-NLS-1$ + } + + if (activeChecker is null) { + throw new NullPointerException( + "The callback needs an active callback"); //$NON-NLS-1$ + } + + this.activeChecker = activeChecker; + this.bindingManager = bindingManager; + this.commandManager = commandManager; + } + + /** + * @see dwtx.jface.action.ExternalActionManager.ICallback#addPropertyChangeListener(String, + * IPropertyChangeListener) + */ + public final void addPropertyChangeListener(String commandId, + IPropertyChangeListener listener) { + registeredListeners.add(commandId, listener); + if (!bindingManagerListenerAttached) { + bindingManager.addBindingManagerListener(this); + bindingManagerListenerAttached = true; + } + } + + public final void bindingManagerChanged(BindingManagerEvent event) { + if (event.isActiveBindingsChanged()) { + foreach( k,v; registeredListeners ){ +// Iterator listenerItr = registeredListeners.entrySet() +// .iterator(); +// while (listenerItr.hasNext()) { +// Map.Entry entry = cast(Map.Entry) listenerItr.next(); + String commandId = k;//stringcast(k);// entry.getKey(); + Command command = commandManager.getCommand(commandId); + ParameterizedCommand parameterizedCommand = new ParameterizedCommand( + command, null); + if (event.isActiveBindingsChangedFor(parameterizedCommand)) { + IPropertyChangeListener listener = cast(IPropertyChangeListener) v; + listener.propertyChange(new PropertyChangeEvent(event + .getManager(), IAction.TEXT, null, null)); + } + } + } + } + + /** + * @see dwtx.jface.action.ExternalActionManager.ICallback#getAccelerator(String) + */ + public ValueWrapperInt getAccelerator(String commandId) { + TriggerSequence triggerSequence = bindingManager + .getBestActiveBindingFor(commandId); + if (triggerSequence !is null) { + Trigger[] triggers = triggerSequence.getTriggers(); + if (triggers.length is 1) { + Trigger trigger = triggers[0]; + if ( auto keyStroke = cast(KeyStroke) trigger ) { + int accelerator = SWTKeySupport + .convertKeyStrokeToAccelerator(keyStroke); + return new ValueWrapperInt(accelerator); + } + } + } + + return null; + } + + /** + * @see dwtx.jface.action.ExternalActionManager.ICallback#getAcceleratorText(String) + */ + public final String getAcceleratorText(String commandId) { + TriggerSequence triggerSequence = bindingManager + .getBestActiveBindingFor(commandId); + if (triggerSequence is null) { + return null; + } + + return triggerSequence.format(); + } + + /** + * Returns the active bindings for a particular command identifier. + * + * @param commandId + * The identifier of the command whose bindings are + * requested. This argument may be <code>null</code>. It + * is assumed that the command has no parameters. + * @return The array of active triggers (<code>TriggerSequence</code>) + * for a particular command identifier. This value is guaranteed + * not to be <code>null</code>, but it may be empty. + * @since 3.2 + */ + public final TriggerSequence[] getActiveBindingsFor( + String commandId) { + return bindingManager.getActiveBindingsFor(commandId); + } + + /** + * @see dwtx.jface.action.ExternalActionManager.ICallback#isAcceleratorInUse(int) + */ + public final bool isAcceleratorInUse(int accelerator) { + KeySequence keySequence = KeySequence + .getInstance(SWTKeySupport + .convertAcceleratorToKeyStroke(accelerator)); + return bindingManager.isPerfectMatch(keySequence) + || bindingManager.isPartialMatch(keySequence); + } + + /** + * {@inheritDoc} + * + * Calling this method with an undefined command id will generate a log + * message. + */ + public final bool isActive(String commandId) { + if (commandId !is null) { + Command command = commandManager.getCommand(commandId); + + if (!command.isDefined() + && (!loggedCommandIds.contains(commandId))) { + // The command is not yet defined, so we should log this. + String message = Format(Util + .translateString(RESOURCE_BUNDLE, + "undefinedCommand.WarningMessage", null), //$NON-NLS-1$ + [ command.getId() ]); + IStatus status = new Status(IStatus.ERROR, + "dwtx.jface", //$NON-NLS-1$ + 0, message, new Exception(null)); + Policy.getLog().log(status); + + // And remember this item so we don't log it again. + loggedCommandIds.add(commandId); + command.addCommandListener(new class ICommandListener { + Command command_; + String commandId_; + this(){ + command_=command; + commandId_=commandId; + } + /* + * (non-Javadoc) + * + * @see dwtx.ui.commands.ICommandListener#commandChanged(dwtx.ui.commands.CommandEvent) + */ + public final void commandChanged( + CommandEvent commandEvent) { + if (command_.isDefined()) { + command_.removeCommandListener(this); + loggedCommandIds.remove(commandId_); + } + } + }); + + return true; + } + + return activeChecker.isActive(commandId); + } + + return true; + } + + /** + * @see dwtx.jface.action.ExternalActionManager.ICallback#removePropertyChangeListener(String, + * IPropertyChangeListener) + */ + public final void removePropertyChangeListener(String commandId, + IPropertyChangeListener listener) { + IPropertyChangeListener existingListener = cast(IPropertyChangeListener) registeredListeners + .get(commandId); + if (existingListener is listener) { + registeredListeners.removeKey(commandId); + if (registeredListeners.drained()) { + bindingManager.removeBindingManagerListener(this); + bindingManagerListenerAttached = false; + } + } + } + } + + /** + * Defines a callback mechanism for developer who wish to further control + * the visibility of legacy action-based contribution items. + * + * @since 3.1 + */ + public static interface IActiveChecker { + /** + * Checks whether the command with the given identifier should be + * considered active. This can be used in systems using some kind of + * user interface filtering (e.g., activities in the Eclipse workbench). + * + * @param commandId + * The identifier for the command; must not be + * <code>null</code> + * @return <code>true</code> if the command is active; + * <code>false</code> otherwise. + */ + public bool isActive(String commandId); + } + + /** + * <p> + * A callback which communicates with the applications binding manager. This + * interface provides more information from the binding manager, which + * allows greater integration. Implementing this interface is preferred over + * {@link ExternalActionManager.ICallback}. + * </p> + * <p> + * Clients may implement this interface, but must not extend. + * </p> + * + * @since 3.2 + */ + public static interface IBindingManagerCallback : ICallback { + + /** + * <p> + * Returns the active bindings for a particular command identifier. + * </p> + * + * @param commandId + * The identifier of the command whose bindings are + * requested. This argument may be <code>null</code>. It + * is assumed that the command has no parameters. + * @return The array of active triggers (<code>TriggerSequence</code>) + * for a particular command identifier. This value is guaranteed + * not to be <code>null</code>, but it may be empty. + */ + public TriggerSequence[] getActiveBindingsFor(String commandId); + } + + /** + * A callback mechanism for some external tool to communicate extra + * information to actions and action contribution items. + * + * @since 3.0 + */ + public static interface ICallback { + + /** + * <p> + * Adds a listener to the object referenced by <code>identifier</code>. + * This listener will be notified if a property of the item is to be + * changed. This identifier is specific to mechanism being used. In the + * case of the Eclipse workbench, this is the command identifier. + * </p> + * <p> + * A single instance of the listener may only ever be associated with + * one identifier. Attempts to add the listener twice (without a removal + * in between) has undefined behaviour. + * </p> + * + * @param identifier + * The identifier of the item to which the listener should be + * attached; must not be <code>null</code>. + * @param listener + * The listener to be added; must not be <code>null</code>. + */ + public void addPropertyChangeListener(String identifier, + IPropertyChangeListener listener); + + /** + * An accessor for the accelerator associated with the item indicated by + * the identifier. This identifier is specific to mechanism being used. + * In the case of the Eclipse workbench, this is the command identifier. + * + * @param identifier + * The identifier of the item from which the accelerator + * should be obtained ; must not be <code>null</code>. + * @return An integer representation of the accelerator. This is the + * same accelerator format used by DWT. + */ + public Integer getAccelerator(String identifier); + + /** + * An accessor for the accelerator text associated with the item + * indicated by the identifier. This identifier is specific to mechanism + * being used. In the case of the Eclipse workbench, this is the command + * identifier. + * + * @param identifier + * The identifier of the item from which the accelerator text + * should be obtained ; must not be <code>null</code>. + * @return A string representation of the accelerator. This is the + * string representation that should be displayed to the user. + */ + public String getAcceleratorText(String identifier); + + /** + * Checks to see whether the given accelerator is being used by some + * other mechanism (outside of the menus controlled by JFace). This is + * used to keep JFace from trying to grab accelerators away from someone + * else. + * + * @param accelerator + * The accelerator to check -- in DWT's internal accelerator + * format. + * @return <code>true</code> if the accelerator is already being used + * and shouldn't be used again; <code>false</code> otherwise. + */ + public bool isAcceleratorInUse(int accelerator); + + /** + * Checks whether the item matching this identifier is active. This is + * used to decide whether a contribution item with this identifier + * should be made visible. An inactive item is not visible. + * + * @param identifier + * The identifier of the item from which the active state + * should be retrieved; must not be <code>null</code>. + * @return <code>true</code> if the item is active; <code>false</code> + * otherwise. + */ + public bool isActive(String identifier); + + /** + * Removes a listener from the object referenced by + * <code>identifier</code>. This identifier is specific to mechanism + * being used. In the case of the Eclipse workbench, this is the command + * identifier. + * + * @param identifier + * The identifier of the item to from the listener should be + * removed; must not be <code>null</code>. + * @param listener + * The listener to be removed; must not be <code>null</code>. + */ + public void removePropertyChangeListener(String identifier, + IPropertyChangeListener listener); + } + + /** + * The singleton instance of this class. This value may be <code>null</code>-- + * if it has not yet been initialized. + */ + private static ExternalActionManager instance; + + /** + * Retrieves the current singleton instance of this class. + * + * @return The singleton instance; this value is never <code>null</code>. + */ + public static ExternalActionManager getInstance() { + if (instance is null) { + instance = new ExternalActionManager(); + } + + return instance; + } + + /** + * The callback mechanism to use to retrieve extra information. + */ + private ICallback callback; + + /** + * Constructs a new instance of <code>ExternalActionManager</code>. + */ + private this() { + // This is a singleton class. Only this class should create an instance. + } + + /** + * An accessor for the current call back. + * + * @return The current callback mechanism being used. This is the callback + * that should be queried for extra information about actions and + * action contribution items. This value may be <code>null</code> + * if there is no extra information. + */ + public ICallback getCallback() { + return callback; + } + + /** + * A mutator for the current call back + * + * @param callbackToUse + * The new callback mechanism to use; this value may be + * <code>null</code> if the default is acceptable (i.e., no + * extra information will provided to actions). + */ + public void setCallback(ICallback callbackToUse) { + callback = callbackToUse; + } +}