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;
+    }
+}