# HG changeset patch # User Frank Benoit # Date 1207029631 -7200 # Node ID e0f0aaf75edd11942b0737e99d08b958d767a67a # Parent db8940420ed843964998a5c6f28536d862bf9a15 PopupDialog, bindings and actions diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/action/AbstractAction.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/action/AbstractAction.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2005, 2007 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 + *******************************************************************************/ + +module dwtx.jface.action.AbstractAction; + +import dwtx.jface.action.IAction; + +import dwtx.core.commands.common.EventManager; +import dwtx.jface.util.IPropertyChangeListener; +import dwtx.jface.util.PropertyChangeEvent; + +import dwt.dwthelper.utils; + +/** + *

+ * Some common functionality to share between implementations of + * IAction. This functionality deals with the property change + * event mechanism. + *

+ *

+ * Clients may neither instantiate nor extend this class. + *

+ * + * @since 3.2 + */ +public abstract class AbstractAction : EventManager, IAction { + + public void addPropertyChangeListener(IPropertyChangeListener listener) { + addListenerObject(cast(Object)listener); + } + + /** + * Notifies any property change listeners that a property has changed. Only + * listeners registered at the time this method is called are notified. + * + * @param event + * the property change event + * + * @see dwtx.jface.util.IPropertyChangeListener#propertyChange(PropertyChangeEvent) + */ + protected final void firePropertyChange(PropertyChangeEvent event) { + final Object[] list = getListeners(); + for (int i = 0; i < list.length; ++i) { + (cast(IPropertyChangeListener) list[i]).propertyChange(event); + } + } + + /** + * Notifies any property change listeners that a property has changed. Only + * listeners registered at the time this method is called are notified. This + * method avoids creating an event object if there are no listeners + * registered, but calls + * firePropertyChange(PropertyChangeEvent) if there are. + * + * @param propertyName + * the name of the property that has changed + * @param oldValue + * the old value of the property, or null if none + * @param newValue + * the new value of the property, or null if none + * + * @see dwtx.jface.util.IPropertyChangeListener#propertyChange(PropertyChangeEvent) + */ + protected final void firePropertyChange(String propertyName, + Object oldValue, Object newValue) { + if (isListenerAttached()) { + firePropertyChange(new PropertyChangeEvent(this, propertyName, + oldValue, newValue)); + } + } + + public void removePropertyChangeListener( + IPropertyChangeListener listener) { + removeListenerObject(cast(Object)listener); + } + +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/action/AbstractGroupMarker.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/action/AbstractGroupMarker.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,62 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +module dwtx.jface.action.AbstractGroupMarker; + +import dwtx.jface.action.ContributionItem; + +import dwtx.core.runtime.Assert; + +import dwt.dwthelper.utils; + +/** + * Abstract superclass for group marker classes. + *

+ * This class is not intended to be subclassed outside the framework. + *

+ */ +public abstract class AbstractGroupMarker : ContributionItem { + /** + * Constructor for use by subclasses. + */ + protected this() { + } + + /** + * Create a new group marker with the given name. + * The group name must not be null or the empty string. + * The group name is also used as the item id. + * + * @param groupName the name of the group + */ + protected this(String groupName) { + super(groupName); + Assert.isTrue(groupName !is null && groupName.length > 0); + } + + /** + * Returns the group name. + * + * @return the group name + */ + public String getGroupName() { + return getId(); + } + + /** + * The AbstractGroupMarker implementation of this IContributionItem + * method returns true iff the id is not null. Subclasses may override. + */ + public bool isGroupMarker() { + return getId() !is null; + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/action/Action.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/action/Action.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,721 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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 + *******************************************************************************/ +module dwtx.jface.action.Action; + +import dwtx.jface.action.AbstractAction; +import dwtx.jface.action.IAction; +import dwtx.jface.action.IMenuCreator; +import dwtx.jface.action.LegacyActionTools; + +import dwtx.jface.util.IPropertyChangeListener; + +import dwt.events.HelpListener; +import dwt.widgets.Control; +import dwt.widgets.Event; +import dwt.widgets.Menu; +import dwtx.jface.resource.ImageDescriptor; + +import dwt.dwthelper.utils; + +/** + * The standard abstract implementation of an action. + *

+ * Subclasses must implement the IAction.run method to carry out + * the action's semantics. + *

+ */ +public abstract class Action : AbstractAction, IAction { + + public void addPropertyChangeListener(IPropertyChangeListener listener) { + super.addPropertyChangeListener(listener); + } + public void removePropertyChangeListener( IPropertyChangeListener listener) { + super.removePropertyChangeListener(listener); + } + + + private static const IMenuCreator VAL_DROP_DOWN_MENU; + static this(){ + VAL_DROP_DOWN_MENU = new class IMenuCreator { + public void dispose() { + // do nothing + } + + public Menu getMenu(Control parent) { + // do nothing + return null; + } + + public Menu getMenu(Menu parent) { + // do nothing + return null; + } + }; + VAL_RADIO_BTN_OFF = new ValueWrapperInt(0); + VAL_RADIO_BTN_ON = new ValueWrapperInt(1); + VAL_TOGGLE_BTN_OFF = new ValueWrapperBool(false); + VAL_TOGGLE_BTN_ON = new ValueWrapperBool(true); + } + + /* + * The list of default values the action can have. These values will + * determine the style of the action. + */ + private static const String VAL_PUSH_BTN = "PUSH_BTN"; //$NON-NLS-1$ + + private static const ValueWrapperInt VAL_RADIO_BTN_OFF; + + private static const ValueWrapperInt VAL_RADIO_BTN_ON; + + private static const ValueWrapperBool VAL_TOGGLE_BTN_OFF; + + private static const ValueWrapperBool VAL_TOGGLE_BTN_ON; + + /** + * Converts an accelerator key code to a string representation. + * + * @param keyCode + * the key code to be translated + * @return a string representation of the key code + */ + public static String convertAccelerator(int keyCode) { + return LegacyActionTools.convertAccelerator(keyCode); + } + + /** + * Parses the given accelerator text, and converts it to an accelerator key + * code. + * + * @param acceleratorText + * the accelerator text + * @return the DWT key code, or 0 if there is no accelerator + */ + public static int convertAccelerator(String acceleratorText) { + return LegacyActionTools.convertAccelerator(acceleratorText); + } + + /** + * Maps a standard keyboard key name to an DWT key code. Key names are + * converted to upper case before comparison. If the key name is a single + * letter, for example "S", its character code is returned. + *

+ * The following key names are known (case is ignored): + *

    + *
  • "BACKSPACE"
  • + *
  • "TAB"
  • + *
  • "RETURN"
  • + *
  • "ENTER"
  • + *
  • "ESC"
  • + *
  • "ESCAPE"
  • + *
  • "DELETE"
  • + *
  • "SPACE"
  • + *
  • "ARROW_UP", "ARROW_DOWN", + * "ARROW_LEFT", and "ARROW_RIGHT"
  • + *
  • "PAGE_UP" and "PAGE_DOWN"
  • + *
  • "HOME"
  • + *
  • "END"
  • + *
  • "INSERT"
  • + *
  • "F1", "F2" through "F12"
  • + *
+ *

+ * + * @param token + * the key name + * @return the DWT key code, -1 if no match was found + * @see dwt.DWT + */ + public static int findKeyCode(String token) { + return LegacyActionTools.findKeyCode(token); + } + + /** + * Maps an DWT key code to a standard keyboard key name. The key code is + * stripped of modifiers (DWT.CTRL, DWT.ALT, DWT.SHIFT, and DWT.COMMAND). If + * the key code is not an DWT code (for example if it a key code for the key + * 'S'), a string containing a character representation of the key code is + * returned. + * + * @param keyCode + * the key code to be translated + * @return the string representation of the key code + * @see dwt.DWT + * @since 2.0 + */ + public static String findKeyString(int keyCode) { + return LegacyActionTools.findKeyString(keyCode); + } + + /** + * Maps standard keyboard modifier key names to the corresponding DWT + * modifier bit. The following modifier key names are recognized (case is + * ignored): "CTRL", "SHIFT", + * "ALT", and "COMMAND". The given modifier + * key name is converted to upper case before comparison. + * + * @param token + * the modifier key name + * @return the DWT modifier bit, or 0 if no match was found + * @see dwt.DWT + */ + public static int findModifier(String token) { + return LegacyActionTools.findModifier(token); + } + + /** + * Returns a string representation of an DWT modifier bit (DWT.CTRL, + * DWT.ALT, DWT.SHIFT, and DWT.COMMAND). Returns null if the + * key code is not an DWT modifier bit. + * + * @param keyCode + * the DWT modifier bit to be translated + * @return the string representation of the DWT modifier bit, or + * null if the key code was not an DWT modifier bit + * @see dwt.DWT + * @since 2.0 + */ + public static String findModifierString(int keyCode) { + return LegacyActionTools.findModifierString(keyCode); + } + + /** + * Convenience method for removing any optional accelerator text from the + * given string. The accelerator text appears at the end of the text, and is + * separated from the main part by a single tab character '\t'. + * + * @param text + * the text + * @return the text sans accelerator + */ + public static String removeAcceleratorText(String text) { + return LegacyActionTools.removeAcceleratorText(text); + } + + /** + * Convenience method for removing any mnemonics from the given string. For + * example, removeMnemonics("&Open") will return + * "Open". + * + * @param text + * the text + * @return the text sans mnemonics + * + * @since 3.0 + */ + public static String removeMnemonics(String text) { + return LegacyActionTools.removeMnemonics(text); + } + + /** + * This action's accelerator; 0 means none. + */ + private int accelerator = 0; + + /** + * This action's action definition id, or null if none. + */ + private String actionDefinitionId; + + /** + * This action's description, or null if none. + */ + private String description; + + /** + * This action's disabled image, or null if none. + */ + private ImageDescriptor disabledImage; + + /** + * Indicates this action is enabled. + */ + private bool enabled = true; + + /** + * An action's help listener, or null if none. + */ + private HelpListener helpListener; + + /** + * This action's hover image, or null if none. + */ + private ImageDescriptor hoverImage; + + /** + * This action's id, or null if none. + */ + private String id; + + /** + * This action's image, or null if none. + */ + private ImageDescriptor image; + + /** + * This action's text, or null if none. + */ + private String text; + + /** + * This action's tool tip text, or null if none. + */ + private String toolTipText; + + /** + * Holds the action's menu creator (an IMenuCreator) or checked state (a + * bool for toggle button, or an Integer for radio button), or + * null if neither have been set. + *

+ * The value of this field affects the value of getStyle(). + *

+ */ + private Object value = null; + + /** + * Creates a new action with no text and no image. + *

+ * Configure the action later using the set methods. + *

+ */ + protected this() { + // do nothing + } + + /** + * Creates a new action with the given text and no image. Calls the zero-arg + * constructor, then setText. + * + * @param text + * the string used as the text for the action, or + * null if there is no text + * @see #setText + */ + protected this(String text) { + this(); + setText(text); + } + + /** + * Creates a new action with the given text and image. Calls the zero-arg + * constructor, then setText and + * setImageDescriptor. + * + * @param text + * the action's text, or null if there is no text + * @param image + * the action's image, or null if there is no + * image + * @see #setText + * @see #setImageDescriptor + */ + protected this(String text, ImageDescriptor image) { + this(text); + setImageDescriptor(image); + } + + /** + * Creates a new action with the given text and style. + * + * @param text + * the action's text, or null if there is no text + * @param style + * one of AS_PUSH_BUTTON, + * AS_CHECK_BOX, AS_DROP_DOWN_MENU, + * AS_RADIO_BUTTON, and + * AS_UNSPECIFIED. + */ + protected this(String text, int style) { + this(text); + switch (style) { + case AS_PUSH_BUTTON: + value = stringcast(VAL_PUSH_BTN); + break; + case AS_CHECK_BOX: + value = VAL_TOGGLE_BTN_OFF; + break; + case AS_DROP_DOWN_MENU: + value = cast(Object)VAL_DROP_DOWN_MENU; + break; + case AS_RADIO_BUTTON: + value = VAL_RADIO_BTN_OFF; + break; + } + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public int getAccelerator() { + return accelerator; + } + + /* + * (non-Javadoc) Method declared on IAction. + * + */ + public String getActionDefinitionId() { + return actionDefinitionId; + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public String getDescription() { + if (description !is null) { + return description; + } + return getToolTipText(); + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public ImageDescriptor getDisabledImageDescriptor() { + return disabledImage; + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public HelpListener getHelpListener() { + return helpListener; + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public ImageDescriptor getHoverImageDescriptor() { + return hoverImage; + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public String getId() { + return id; + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public ImageDescriptor getImageDescriptor() { + return image; + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public IMenuCreator getMenuCreator() { + // The default drop down menu value is only used + // to mark this action requested style. So do not + // return it. For backward compatibility reasons. + if (value is cast(Object)VAL_DROP_DOWN_MENU) { + return null; + } + if ( auto v = cast(IMenuCreator) value ) { + return v; + } + return null; + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public int getStyle() { + // Infer the style from the value field. + if (value is stringcast(VAL_PUSH_BTN) || value is null) { + return AS_PUSH_BUTTON; + } + if (value is VAL_TOGGLE_BTN_ON || value is VAL_TOGGLE_BTN_OFF) { + return AS_CHECK_BOX; + } + if (value is VAL_RADIO_BTN_ON || value is VAL_RADIO_BTN_OFF) { + return AS_RADIO_BUTTON; + } + if (cast(IMenuCreator)value ) { + return AS_DROP_DOWN_MENU; + } + + // We should never get to this line... + return AS_PUSH_BUTTON; + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public String getText() { + return text; + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public String getToolTipText() { + return toolTipText; + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public bool isChecked() { + return value is VAL_TOGGLE_BTN_ON || value is VAL_RADIO_BTN_ON; + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public bool isEnabled() { + return enabled; + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public bool isHandled() { + return true; + } + + /** + * Reports the outcome of the running of this action via the + * {@link IAction#RESULT} property. + * + * @param success + * true if the action succeeded and + * false if the action failed or was not completed + * @see IAction#RESULT + * @since 3.0 + */ + public final void notifyResult(bool success) { + // avoid bool.valueOf(bool) to allow compilation against JCL + // Foundation (bug 80059) + firePropertyChange(RESULT, null, new ValueWrapperBool(success)); + } + + /** + * The default implementation of this IAction method does + * nothing. Subclasses should override this method if they do not need + * information from the triggering event, or override + * runWithEvent(Event) if they do. + */ + public void run() { + // do nothing + } + + /** + * The default implementation of this IAction method ignores + * the event argument, and simply calls run(). Subclasses + * should override this method if they need information from the triggering + * event, or override run() if not. + * + * @param event + * the DWT event which triggered this action being run + * @since 2.0 + */ + public void runWithEvent(Event event) { + run(); + } + + /* + * @see IAction#setAccelerator(int) + */ + public void setAccelerator(int keycode) { + this.accelerator = keycode; + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public void setActionDefinitionId(String id) { + actionDefinitionId = id; + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public void setChecked(bool checked) { + Object newValue = null; + + // For backward compatibility, if the style is not + // set yet, then convert it to a toggle button. + if (value is null || value is VAL_TOGGLE_BTN_ON + || value is VAL_TOGGLE_BTN_OFF) { + newValue = checked ? VAL_TOGGLE_BTN_ON : VAL_TOGGLE_BTN_OFF; + } else if (value is VAL_RADIO_BTN_ON || value is VAL_RADIO_BTN_OFF) { + newValue = checked ? VAL_RADIO_BTN_ON : VAL_RADIO_BTN_OFF; + } else { + // Some other style already, so do nothing. + return; + } + + if (newValue !is value) { + value = newValue; + if (checked) { + firePropertyChange(CHECKED, new ValueWrapperBool(false), new ValueWrapperBool(true)); + } else { + firePropertyChange(CHECKED, new ValueWrapperBool(true), new ValueWrapperBool(false)); + } + } + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public void setDescription(String text) { + + if ((description is null && text !is null) + || (description !is null && text is null) + || (description !is null && text !is null && !text + .equals(description))) { + String oldDescription = description; + description = text; + firePropertyChange(DESCRIPTION, stringcast(oldDescription), stringcast(description)); + } + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public void setDisabledImageDescriptor(ImageDescriptor newImage) { + if (disabledImage !is newImage) { + ImageDescriptor oldImage = disabledImage; + disabledImage = newImage; + firePropertyChange(IMAGE, oldImage, newImage); + } + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public void setEnabled(bool enabled) { + if (enabled !is this.enabled) { + this.enabled = enabled; + firePropertyChange(ENABLED, new ValueWrapperBool(this.enabled), new ValueWrapperBool(enabled)); + } + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public void setHelpListener(HelpListener listener) { + helpListener = listener; + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public void setHoverImageDescriptor(ImageDescriptor newImage) { + if (hoverImage !is newImage) { + ImageDescriptor oldImage = hoverImage; + hoverImage = newImage; + firePropertyChange(IMAGE, oldImage, newImage); + } + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public void setId(String id) { + this.id = id; + } + + /* + * (non-Javadoc) Method declared on IAction. + */ + public void setImageDescriptor(ImageDescriptor newImage) { + if (image !is newImage) { + ImageDescriptor oldImage = image; + image = newImage; + firePropertyChange(IMAGE, oldImage, newImage); + } + } + + /** + * Sets the menu creator for this action. + *

+ * Note that if this method is called, it overrides the check status. + *

+ * + * @param creator + * the menu creator, or null if none + */ + public void setMenuCreator(IMenuCreator creator) { + // For backward compatibility, if the style is not + // set yet, then convert it to a drop down menu. + if (value is null) { + value = cast(Object)creator; + return; + } + + if ( cast(IMenuCreator)value ) { + value = creator is null ? cast(Object)VAL_DROP_DOWN_MENU : cast(Object)creator; + } + } + + /** + * Sets the text for this action. + *

+ * Fires a property change event for the TEXT property if the + * text actually changes as a consequence. + *

+ *

+ * The accelerator is identified by the last index of a tab character. If + * there are no tab characters, then it is identified by the last index of a + * '@' character. If neither, then there is no accelerator text. Note that + * if you want to insert a '@' character into the text (but no accelerator, + * you can simply insert a '@' or a tab at the end of the text. + *

+ * + * @param text + * the text, or null if none + */ + public void setText(String text) { + String oldText = this.text; + int oldAccel = this.accelerator; + this.text = text; + if (text !is null) { + String acceleratorText = LegacyActionTools + .extractAcceleratorText(text); + if (acceleratorText !is null) { + int newAccelerator = LegacyActionTools + .convertLocalizedAccelerator(acceleratorText); + // Be sure to not wipe out the accelerator if nothing found + if (newAccelerator > 0) { + setAccelerator(newAccelerator); + } + } + } + if (!(this.accelerator is oldAccel && (oldText is null ? this.text is null + : oldText.equals(this.text)))) { + firePropertyChange(TEXT, stringcast(oldText), stringcast(this.text)); + } + } + + /** + * Sets the tool tip text for this action. + *

+ * Fires a property change event for the TOOL_TIP_TEXT + * property if the tool tip text actually changes as a consequence. + *

+ * + * @param toolTipText + * the tool tip text, or null if none + */ + public void setToolTipText(String toolTipText) { + String oldToolTipText = this.toolTipText; + if (!(oldToolTipText is null ? toolTipText is null : oldToolTipText + .equals(toolTipText))) { + this.toolTipText = toolTipText; + firePropertyChange(TOOL_TIP_TEXT, stringcast(oldToolTipText), stringcast(toolTipText)); + } + } + +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/action/ActionContributionItem.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/action/ActionContributionItem.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,1136 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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 + *******************************************************************************/ +module dwtx.jface.action.ActionContributionItem; + +import dwtx.jface.action.ContributionItem; +import dwtx.jface.action.IAction; +import dwtx.jface.action.LegacyActionTools; +import dwtx.jface.action.Action; +import dwtx.jface.action.IMenuCreator; +import dwtx.jface.action.IContributionManagerOverrides; + +import dwt.DWT; +import dwt.graphics.GC; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Button; +import dwt.widgets.Composite; +import dwt.widgets.Display; +import dwt.widgets.Event; +import dwt.widgets.Item; +import dwt.widgets.Listener; +import dwt.widgets.Menu; +import dwt.widgets.MenuItem; +import dwt.widgets.ToolBar; +import dwt.widgets.ToolItem; +import dwt.widgets.Widget; +import dwtx.jface.action.ExternalActionManager; +import dwtx.jface.bindings.Trigger; +import dwtx.jface.bindings.TriggerSequence; +import dwtx.jface.bindings.keys.IKeyLookup; +import dwtx.jface.bindings.keys.KeyLookupFactory; +import dwtx.jface.bindings.keys.KeyStroke; +import dwtx.jface.resource.ImageDescriptor; +import dwtx.jface.resource.JFaceResources; +import dwtx.jface.resource.LocalResourceManager; +import dwtx.jface.resource.ResourceManager; +import dwtx.jface.util.IPropertyChangeListener; +import dwtx.jface.util.Policy; +import dwtx.jface.util.PropertyChangeEvent; + +import dwt.dwthelper.utils; +import dwt.dwthelper.Runnable; +import tango.core.Thread; +import tango.io.Stdout; + +/** + * A contribution item which delegates to an action. + *

+ * This class may be instantiated; it is not intended to be subclassed. + *

+ */ +public class ActionContributionItem : ContributionItem { + + /** + * Mode bit: Show text on tool items, even if an image is present. If this + * mode bit is not set, text is only shown on tool items if there is no + * image present. + * + * @since 3.0 + */ + public static int MODE_FORCE_TEXT = 1; + + /** a string inserted in the middle of text that has been shortened */ + private static const String ellipsis = "..."; //$NON-NLS-1$ + + private static bool USE_COLOR_ICONS = true; + + /** + * Returns whether color icons should be used in toolbars. + * + * @return true if color icons should be used in toolbars, + * false otherwise + */ + public static bool getUseColorIconsInToolbars() { + return USE_COLOR_ICONS; + } + + /** + * Sets whether color icons should be used in toolbars. + * + * @param useColorIcons + * true if color icons should be used in toolbars, + * false otherwise + */ + public static void setUseColorIconsInToolbars(bool useColorIcons) { + USE_COLOR_ICONS = useColorIcons; + } + + /** + * The presentation mode. + */ + private int mode = 0; + + /** + * The action. + */ + private IAction action; + + /** + * The listener for changes to the text of the action contributed by an + * external source. + */ + private const IPropertyChangeListener actionTextListener; + + /** + * Remembers all images in use by this contribution item + */ + private LocalResourceManager imageManager; + + /** + * Listener for DWT button widget events. + */ + private Listener buttonListener; + + /** + * Listener for DWT menu item widget events. + */ + private Listener menuItemListener; + + /** + * Listener for action property change notifications. + */ + private const IPropertyChangeListener propertyListener; + + /** + * Listener for DWT tool item widget events. + */ + private Listener toolItemListener; + + /** + * The widget created for this item; null before creation and + * after disposal. + */ + private Widget widget = null; + + /** + * Creates a new contribution item from the given action. The id of the + * action is used as the id of the item. + * + * @param action + * the action + */ + public this(IAction action) { + super(action.getId()); + this.action = action; + actionTextListener = new class IPropertyChangeListener { + /** + * @see IPropertyChangeListener#propertyChange(PropertyChangeEvent) + */ + public void propertyChange(PropertyChangeEvent event) { + update(event.getProperty()); + } + }; + propertyListener = new class IPropertyChangeListener { + public void propertyChange(PropertyChangeEvent event) { + actionPropertyChange(event); + } + }; + } + + /** + * Handles a property change event on the action (forwarded by nested + * listener). + */ + private void actionPropertyChange(PropertyChangeEvent e) { + // This code should be removed. Avoid using free asyncExec + + if (isVisible() && widget !is null) { + Display display = widget.getDisplay(); + if (display.getThread() is Thread.getThis()) { + update(e.getProperty()); + } else { + display.asyncExec(new class Runnable { + PropertyChangeEvent e_; + this(){ + e_=e; + } + public void run() { + update(e_.getProperty()); + } + }); + } + + } + } + + /** + * Compares this action contribution item with another object. Two action + * contribution items are equal if they refer to the identical Action. + */ + public override int opEquals(Object o) { + if (!(cast(ActionContributionItem)o )) { + return false; + } + return (cast(Object)action).opEquals(cast(Object)(cast(ActionContributionItem) o).action); + } + + /** + * The ActionContributionItem implementation of this + * IContributionItem method creates an DWT + * Button for the action using the action's style. If the + * action's checked property has been set, the button is created and primed + * to the value of the checked property. + */ + public void fill(Composite parent) { + if (widget is null && parent !is null) { + int flags = DWT.PUSH; + if (action !is null) { + if (action.getStyle() is IAction.AS_CHECK_BOX) { + flags = DWT.TOGGLE; + } + if (action.getStyle() is IAction.AS_RADIO_BUTTON) { + flags = DWT.RADIO; + } + } + + Button b = new Button(parent, flags); + b.setData(this); + b.addListener(DWT.Dispose, getButtonListener()); + // Don't hook a dispose listener on the parent + b.addListener(DWT.Selection, getButtonListener()); + if (action.getHelpListener() !is null) { + b.addHelpListener(action.getHelpListener()); + } + widget = b; + + update(null); + + // Attach some extra listeners. + action.addPropertyChangeListener(propertyListener); + if (action !is null) { + String commandId = action.getActionDefinitionId(); + ExternalActionManager.ICallback callback = ExternalActionManager + .getInstance().getCallback(); + + if ((callback !is null) && (commandId !is null)) { + callback.addPropertyChangeListener(commandId, + actionTextListener); + } + } + } + } + + /** + * The ActionContributionItem implementation of this + * IContributionItem method creates an DWT + * MenuItem for the action using the action's style. If the + * action's checked property has been set, a button is created and primed to + * the value of the checked property. If the action's menu creator property + * has been set, a cascading submenu is created. + */ + public void fill(Menu parent, int index) { + if (widget is null && parent !is null) { + Menu subMenu = null; + int flags = DWT.PUSH; + if (action !is null) { + int style = action.getStyle(); + if (style is IAction.AS_CHECK_BOX) { + flags = DWT.CHECK; + } else if (style is IAction.AS_RADIO_BUTTON) { + flags = DWT.RADIO; + } else if (style is IAction.AS_DROP_DOWN_MENU) { + IMenuCreator mc = action.getMenuCreator(); + if (mc !is null) { + subMenu = mc.getMenu(parent); + flags = DWT.CASCADE; + } + } + } + + MenuItem mi = null; + if (index >= 0) { + mi = new MenuItem(parent, flags, index); + } else { + mi = new MenuItem(parent, flags); + } + widget = mi; + + mi.setData(this); + mi.addListener(DWT.Dispose, getMenuItemListener()); + mi.addListener(DWT.Selection, getMenuItemListener()); + if (action.getHelpListener() !is null) { + mi.addHelpListener(action.getHelpListener()); + } + + if (subMenu !is null) { + mi.setMenu(subMenu); + } + + update(null); + + // Attach some extra listeners. + action.addPropertyChangeListener(propertyListener); + if (action !is null) { + String commandId = action.getActionDefinitionId(); + ExternalActionManager.ICallback callback = ExternalActionManager + .getInstance().getCallback(); + + if ((callback !is null) && (commandId !is null)) { + callback.addPropertyChangeListener(commandId, + actionTextListener); + } + } + } + } + + /** + * The ActionContributionItem implementation of this , + * IContributionItem method creates an DWT + * ToolItem for the action using the action's style. If the + * action's checked property has been set, a button is created and primed to + * the value of the checked property. If the action's menu creator property + * has been set, a drop-down tool item is created. + */ + public void fill(ToolBar parent, int index) { + if (widget is null && parent !is null) { + int flags = DWT.PUSH; + if (action !is null) { + int style = action.getStyle(); + if (style is IAction.AS_CHECK_BOX) { + flags = DWT.CHECK; + } else if (style is IAction.AS_RADIO_BUTTON) { + flags = DWT.RADIO; + } else if (style is IAction.AS_DROP_DOWN_MENU) { + flags = DWT.DROP_DOWN; + } + } + + ToolItem ti = null; + if (index >= 0) { + ti = new ToolItem(parent, flags, index); + } else { + ti = new ToolItem(parent, flags); + } + ti.setData(this); + ti.addListener(DWT.Selection, getToolItemListener()); + ti.addListener(DWT.Dispose, getToolItemListener()); + + widget = ti; + + update(null); + + // Attach some extra listeners. + action.addPropertyChangeListener(propertyListener); + if (action !is null) { + String commandId = action.getActionDefinitionId(); + ExternalActionManager.ICallback callback = ExternalActionManager + .getInstance().getCallback(); + + if ((callback !is null) && (commandId !is null)) { + callback.addPropertyChangeListener(commandId, + actionTextListener); + } + } + } + } + + /** + * Returns the action associated with this contribution item. + * + * @return the action + */ + public IAction getAction() { + return action; + } + + /** + * Returns the listener for DWT button widget events. + * + * @return a listener for button events + */ + private Listener getButtonListener() { + if (buttonListener is null) { + buttonListener = new class Listener { + public void handleEvent(Event event) { + switch (event.type) { + case DWT.Dispose: + handleWidgetDispose(event); + break; + case DWT.Selection: + Widget ew = event.widget; + if (ew !is null) { + handleWidgetSelection(event, (cast(Button) ew) + .getSelection()); + } + break; + } + } + }; + } + return buttonListener; + } + + /** + * Returns the listener for DWT menu item widget events. + * + * @return a listener for menu item events + */ + private Listener getMenuItemListener() { + if (menuItemListener is null) { + menuItemListener = new class Listener { + public void handleEvent(Event event) { + switch (event.type) { + case DWT.Dispose: + handleWidgetDispose(event); + break; + case DWT.Selection: + Widget ew = event.widget; + if (ew !is null) { + handleWidgetSelection(event, (cast(MenuItem) ew) + .getSelection()); + } + break; + } + } + }; + } + return menuItemListener; + } + + /** + * Returns the presentation mode, which is the bitwise-or of the + * MODE_* constants. The default mode setting is 0, meaning + * that for menu items, both text and image are shown (if present), but for + * tool items, the text is shown only if there is no image. + * + * @return the presentation mode settings + * + * @since 3.0 + */ + public int getMode() { + return mode; + } + + /** + * Returns the listener for DWT tool item widget events. + * + * @return a listener for tool item events + */ + private Listener getToolItemListener() { + if (toolItemListener is null) { + toolItemListener = new class Listener { + public void handleEvent(Event event) { + switch (event.type) { + case DWT.Dispose: + handleWidgetDispose(event); + break; + case DWT.Selection: + Widget ew = event.widget; + if (ew !is null) { + handleWidgetSelection(event, (cast(ToolItem) ew) + .getSelection()); + } + break; + } + } + }; + } + return toolItemListener; + } + + /** + * Handles a widget dispose event for the widget corresponding to this item. + */ + private void handleWidgetDispose(Event e) { + // Check if our widget is the one being disposed. + if (e.widget is widget) { + // Dispose of the menu creator. + if (action.getStyle() is IAction.AS_DROP_DOWN_MENU) { + IMenuCreator mc = action.getMenuCreator(); + if (mc !is null) { + mc.dispose(); + } + } + + // Unhook all of the listeners. + action.removePropertyChangeListener(propertyListener); + if (action !is null) { + String commandId = action.getActionDefinitionId(); + ExternalActionManager.ICallback callback = ExternalActionManager + .getInstance().getCallback(); + + if ((callback !is null) && (commandId !is null)) { + callback.removePropertyChangeListener(commandId, + actionTextListener); + } + } + + // Clear the widget field. + widget = null; + + disposeOldImages(); + } + } + + /** + * Handles a widget selection event. + */ + private void handleWidgetSelection(Event e, bool selection) { + + Widget item = e.widget; + if (item !is null) { + int style = item.getStyle(); + + if ((style & (DWT.TOGGLE | DWT.CHECK)) !is 0) { + if (action.getStyle() is IAction.AS_CHECK_BOX) { + action.setChecked(selection); + } + } else if ((style & DWT.RADIO) !is 0) { + if (action.getStyle() is IAction.AS_RADIO_BUTTON) { + action.setChecked(selection); + } + } else if ((style & DWT.DROP_DOWN) !is 0) { + if (e.detail is 4) { // on drop-down button + if (action.getStyle() is IAction.AS_DROP_DOWN_MENU) { + IMenuCreator mc = action.getMenuCreator(); + ToolItem ti = cast(ToolItem) item; + // we create the menu as a sub-menu of "dummy" so that + // we can use + // it in a cascading menu too. + // If created on a DWT control we would get an DWT + // error... + // Menu dummy= new Menu(ti.getParent()); + // Menu m= mc.getMenu(dummy); + // dummy.dispose(); + if (mc !is null) { + Menu m = mc.getMenu(ti.getParent()); + if (m !is null) { + // position the menu below the drop down item + Rectangle b = ti.getBounds(); + Point p = ti.getParent().toDisplay( + new Point(b.x, b.y + b.height)); + m.setLocation(p.x, p.y); // waiting for DWT + // 0.42 + m.setVisible(true); + return; // we don't fire the action + } + } + } + } + } + + // Ensure action is enabled first. + // See 1GAN3M6: ITPUI:WINNT - Any IAction in the workbench can be + // executed while disabled. + if (action.isEnabled()) { + bool trace = Policy.TRACE_ACTIONS; + + long ms = 0L; + if (trace) { + ms = System.currentTimeMillis(); + Stdout.formatln("Running action: {}", action.getText()); //$NON-NLS-1$ + } + + action.runWithEvent(e); + + if (trace) { + Stdout.formatln("{} ms to run action: {}",(System.currentTimeMillis() - ms), action.getText()); //$NON-NLS-1$ + } + } + } + } + + /* + * (non-Javadoc) Method declared on Object. + */ + public override hash_t toHash() { + return (cast(Object)action).toHash(); + } + + /** + * Returns whether the given action has any images. + * + * @param actionToCheck + * the action + * @return true if the action has any images, + * false if not + */ + private bool hasImages(IAction actionToCheck) { + return actionToCheck.getImageDescriptor() !is null + || actionToCheck.getHoverImageDescriptor() !is null + || actionToCheck.getDisabledImageDescriptor() !is null; + } + + /** + * Returns whether the command corresponding to this action is active. + */ + private bool isCommandActive() { + IAction actionToCheck = getAction(); + + if (actionToCheck !is null) { + String commandId = actionToCheck.getActionDefinitionId(); + ExternalActionManager.ICallback callback = ExternalActionManager + .getInstance().getCallback(); + + if (callback !is null) { + return callback.isActive(commandId); + } + } + return true; + } + + /** + * The action item implementation of this IContributionItem + * method returns true for menu items and false + * for everything else. + */ + public bool isDynamic() { + if (cast(MenuItem)widget ) { + // Optimization. Only recreate the item is the check or radio style + // has changed. + bool itemIsCheck = (widget.getStyle() & DWT.CHECK) !is 0; + bool actionIsCheck = getAction() !is null + && getAction().getStyle() is IAction.AS_CHECK_BOX; + bool itemIsRadio = (widget.getStyle() & DWT.RADIO) !is 0; + bool actionIsRadio = getAction() !is null + && getAction().getStyle() is IAction.AS_RADIO_BUTTON; + return (itemIsCheck !is actionIsCheck) + || (itemIsRadio !is actionIsRadio); + } + return false; + } + + /* + * (non-Javadoc) Method declared on IContributionItem. + */ + public bool isEnabled() { + return action !is null && action.isEnabled(); + } + + /** + * Returns true if this item is allowed to enable, + * false otherwise. + * + * @return if this item is allowed to be enabled + * @since 2.0 + */ + protected bool isEnabledAllowed() { + if (getParent() is null) { + return true; + } + auto value = getParent().getOverrides().getEnabled(this); + return (value is null) ? true : value.value; + } + + /** + * The ActionContributionItem implementation of this + * ContributionItem method extends the super implementation + * by also checking whether the command corresponding to this action is + * active. + */ + public bool isVisible() { + return super.isVisible() && isCommandActive(); + } + + /** + * Sets the presentation mode, which is the bitwise-or of the + * MODE_* constants. + * + * @param mode + * the presentation mode settings + * + * @since 3.0 + */ + public void setMode(int mode) { + this.mode = mode; + update(); + } + + /** + * The action item implementation of this IContributionItem + * method calls update(null). + */ + public final void update() { + update(null); + } + + /** + * Synchronizes the UI with the given property. + * + * @param propertyName + * the name of the property, or null meaning all + * applicable properties + */ + public void update(String propertyName) { + if (widget !is null) { + // determine what to do + bool textChanged = propertyName is null + || propertyName.equals(IAction.TEXT); + bool imageChanged = propertyName is null + || propertyName.equals(IAction.IMAGE); + bool tooltipTextChanged = propertyName is null + || propertyName.equals(IAction.TOOL_TIP_TEXT); + bool enableStateChanged = propertyName is null + || propertyName.equals(IAction.ENABLED) + || propertyName + .equals(IContributionManagerOverrides.P_ENABLED); + bool checkChanged = (action.getStyle() is IAction.AS_CHECK_BOX || action + .getStyle() is IAction.AS_RADIO_BUTTON) + && (propertyName is null || propertyName + .equals(IAction.CHECKED)); + + if (cast(ToolItem)widget ) { + ToolItem ti = cast(ToolItem) widget; + String text = action.getText(); + // the set text is shown only if there is no image or if forced + // by MODE_FORCE_TEXT + bool showText = text !is null + && ((getMode() & MODE_FORCE_TEXT) !is 0 || !hasImages(action)); + + // only do the trimming if the text will be used + if (showText && text !is null) { + text = Action.removeAcceleratorText(text); + text = Action.removeMnemonics(text); + } + + if (textChanged) { + String textToSet = showText ? text : ""; //$NON-NLS-1$ + bool rightStyle = (ti.getParent().getStyle() & DWT.RIGHT) !is 0; + if (rightStyle || !ti.getText().equals(textToSet)) { + // In addition to being required to update the text if + // it + // gets nulled out in the action, this is also a + // workaround + // for bug 50151: Using DWT.RIGHT on a ToolBar leaves + // blank space + ti.setText(textToSet); + } + } + + if (imageChanged) { + // only substitute a missing image if it has no text + updateImages(!showText); + } + + if (tooltipTextChanged || textChanged) { + String toolTip = action.getToolTipText(); + if ((toolTip is null) || (toolTip.length is 0)) { + toolTip = text; + } + + ExternalActionManager.ICallback callback = ExternalActionManager + .getInstance().getCallback(); + String commandId = action.getActionDefinitionId(); + if ((callback !is null) && (commandId !is null) && (toolTip !is null)) { + String acceleratorText = callback.getAcceleratorText(commandId); + if (acceleratorText !is null && acceleratorText.length !is 0) { + toolTip = JFaceResources.format( + "Toolbar_Tooltip_Accelerator", //$NON-NLS-1$ + [ toolTip, acceleratorText ]); + } + } + + // if the text is showing, then only set the tooltip if + // different + if (!showText || toolTip !is null && !toolTip.equals(text)) { + ti.setToolTipText(toolTip); + } else { + ti.setToolTipText(null); + } + } + + if (enableStateChanged) { + bool shouldBeEnabled = action.isEnabled() + && isEnabledAllowed(); + + if (ti.getEnabled() !is shouldBeEnabled) { + ti.setEnabled(shouldBeEnabled); + } + } + + if (checkChanged) { + bool bv = action.isChecked(); + + if (ti.getSelection() !is bv) { + ti.setSelection(bv); + } + } + return; + } + + if (cast(MenuItem)widget ) { + MenuItem mi = cast(MenuItem) widget; + + if (textChanged) { + int accelerator = 0; + String acceleratorText = null; + IAction updatedAction = getAction(); + String text = null; + accelerator = updatedAction.getAccelerator(); + ExternalActionManager.ICallback callback = ExternalActionManager + .getInstance().getCallback(); + + // Block accelerators that are already in use. + if ((accelerator !is 0) && (callback !is null) + && (callback.isAcceleratorInUse(accelerator))) { + accelerator = 0; + } + + /* + * Process accelerators on GTK in a special way to avoid Bug + * 42009. We will override the native input method by + * allowing these reserved accelerators to be placed on the + * menu. We will only do this for "Ctrl+Shift+[0-9A-FU]". + */ + String commandId = updatedAction + .getActionDefinitionId(); + if (("gtk".equals(DWT.getPlatform())) && (cast(ExternalActionManager.IBindingManagerCallback)callback ) //$NON-NLS-1$ + && (commandId !is null)) { + ExternalActionManager.IBindingManagerCallback bindingManagerCallback = cast(ExternalActionManager.IBindingManagerCallback) callback; + IKeyLookup lookup = KeyLookupFactory.getDefault(); + TriggerSequence[] triggerSequences = bindingManagerCallback + .getActiveBindingsFor(commandId); + for (int i = 0; i < triggerSequences.length; i++) { + TriggerSequence triggerSequence = triggerSequences[i]; + Trigger[] triggers = triggerSequence + .getTriggers(); + if (triggers.length is 1) { + Trigger trigger = triggers[0]; + if (cast(KeyStroke)trigger ) { + KeyStroke currentKeyStroke = cast(KeyStroke) trigger; + int currentNaturalKey = currentKeyStroke + .getNaturalKey(); + if ((currentKeyStroke.getModifierKeys() is (lookup + .getCtrl() | lookup.getShift())) + && ((currentNaturalKey >= '0' && currentNaturalKey <= '9') + || (currentNaturalKey >= 'A' && currentNaturalKey <= 'F') || (currentNaturalKey is 'U'))) { + accelerator = currentKeyStroke + .getModifierKeys() + | currentNaturalKey; + acceleratorText = triggerSequence + .format(); + break; + } + } + } + } + } + + if (accelerator is 0) { + if ((callback !is null) && (commandId !is null)) { + acceleratorText = callback + .getAcceleratorText(commandId); + } + } + + IContributionManagerOverrides overrides = null; + + if (getParent() !is null) { + overrides = getParent().getOverrides(); + } + + if (overrides !is null) { + text = getParent().getOverrides().getText(this); + } + + mi.setAccelerator(accelerator); + + if (text is null) { + text = updatedAction.getText(); + } + + if (text !is null && acceleratorText is null) { + // use extracted accelerator text in case accelerator cannot be fully represented in one int (e.g. multi-stroke keys) + acceleratorText = LegacyActionTools.extractAcceleratorText(text); + if (acceleratorText is null && accelerator !is 0) { + acceleratorText= Action.convertAccelerator(accelerator); + } + } + + if (text is null) { + text = ""; //$NON-NLS-1$ + } else { + text = Action.removeAcceleratorText(text); + } + + if (acceleratorText is null) { + mi.setText(text); + } else { + mi.setText(text ~ '\t' ~ acceleratorText); + } + } + + if (imageChanged) { + updateImages(false); + } + + if (enableStateChanged) { + bool shouldBeEnabled = action.isEnabled() + && isEnabledAllowed(); + + if (mi.getEnabled() !is shouldBeEnabled) { + mi.setEnabled(shouldBeEnabled); + } + } + + if (checkChanged) { + bool bv = action.isChecked(); + + if (mi.getSelection() !is bv) { + mi.setSelection(bv); + } + } + + return; + } + + if (cast(Button)widget ) { + Button button = cast(Button) widget; + + if (imageChanged && updateImages(false)) { + textChanged = false; // don't update text if it has an + // image + } + + if (textChanged) { + String text = action.getText(); + if (text is null) { + text = ""; //$NON-NLS-1$ + } else { + text = Action.removeAcceleratorText(text); + } + button.setText(text); + } + + if (tooltipTextChanged) { + button.setToolTipText(action.getToolTipText()); + } + + if (enableStateChanged) { + bool shouldBeEnabled = action.isEnabled() + && isEnabledAllowed(); + + if (button.getEnabled() !is shouldBeEnabled) { + button.setEnabled(shouldBeEnabled); + } + } + + if (checkChanged) { + bool bv = action.isChecked(); + + if (button.getSelection() !is bv) { + button.setSelection(bv); + } + } + return; + } + } + } + + /** + * Updates the images for this action. + * + * @param forceImage + * true if some form of image is compulsory, and + * false if it is acceptable for this item to have + * no image + * @return true if there are images for this action, + * false if not + */ + private bool updateImages(bool forceImage) { + + ResourceManager parentResourceManager = JFaceResources.getResources(); + + if (cast(ToolItem)widget ) { + if (USE_COLOR_ICONS) { + ImageDescriptor image = action.getHoverImageDescriptor(); + if (image is null) { + image = action.getImageDescriptor(); + } + ImageDescriptor disabledImage = action + .getDisabledImageDescriptor(); + + // Make sure there is a valid image. + if (image is null && forceImage) { + image = ImageDescriptor.getMissingImageDescriptor(); + } + + LocalResourceManager localManager = new LocalResourceManager( + parentResourceManager); + + // performance: more efficient in DWT to set disabled and hot + // image before regular image + (cast(ToolItem) widget) + .setDisabledImage(disabledImage is null ? null + : localManager + .createImageWithDefault(disabledImage)); + (cast(ToolItem) widget).setImage(image is null ? null + : localManager.createImageWithDefault(image)); + + disposeOldImages(); + imageManager = localManager; + + return image !is null; + } + ImageDescriptor image = action.getImageDescriptor(); + ImageDescriptor hoverImage = action.getHoverImageDescriptor(); + ImageDescriptor disabledImage = action.getDisabledImageDescriptor(); + + // If there is no regular image, but there is a hover image, + // convert the hover image to gray and use it as the regular image. + if (image is null && hoverImage !is null) { + image = ImageDescriptor.createWithFlags(action + .getHoverImageDescriptor(), DWT.IMAGE_GRAY); + } else { + // If there is no hover image, use the regular image as the + // hover image, + // and convert the regular image to gray + if (hoverImage is null && image !is null) { + hoverImage = image; + image = ImageDescriptor.createWithFlags(action + .getImageDescriptor(), DWT.IMAGE_GRAY); + } + } + + // Make sure there is a valid image. + if (hoverImage is null && image is null && forceImage) { + image = ImageDescriptor.getMissingImageDescriptor(); + } + + // Create a local resource manager to remember the images we've + // allocated for this tool item + LocalResourceManager localManager = new LocalResourceManager( + parentResourceManager); + + // performance: more efficient in DWT to set disabled and hot image + // before regular image + (cast(ToolItem) widget).setDisabledImage(disabledImage is null ? null + : localManager.createImageWithDefault(disabledImage)); + (cast(ToolItem) widget).setHotImage(hoverImage is null ? null + : localManager.createImageWithDefault(hoverImage)); + (cast(ToolItem) widget).setImage(image is null ? null : localManager + .createImageWithDefault(image)); + + // Now that we're no longer referencing the old images, clear them + // out. + disposeOldImages(); + imageManager = localManager; + + return image !is null; + } else if (cast(Item)widget || cast(Button)widget ) { + + // Use hover image if there is one, otherwise use regular image. + ImageDescriptor image = action.getHoverImageDescriptor(); + if (image is null) { + image = action.getImageDescriptor(); + } + // Make sure there is a valid image. + if (image is null && forceImage) { + image = ImageDescriptor.getMissingImageDescriptor(); + } + + // Create a local resource manager to remember the images we've + // allocated for this widget + LocalResourceManager localManager = new LocalResourceManager( + parentResourceManager); + + if (cast(Item)widget) { + (cast(Item) widget).setImage(image is null ? null : localManager + .createImageWithDefault(image)); + } else if (cast(Button)widget) { + (cast(Button) widget).setImage(image is null ? null : localManager + .createImageWithDefault(image)); + } + + // Now that we're no longer referencing the old images, clear them + // out. + disposeOldImages(); + imageManager = localManager; + + return image !is null; + } + return false; + } + + /** + * Dispose any images allocated for this contribution item + */ + private void disposeOldImages() { + if (imageManager !is null) { + imageManager.dispose(); + imageManager = null; + } + } + + /** + * Shorten the given text t so that its length doesn't exceed + * the width of the given ToolItem.The default implementation replaces + * characters in the center of the original string with an ellipsis ("..."). + * Override if you need a different strategy. + * + * @param textValue + * the text to shorten + * @param item + * the tool item the text belongs to + * @return the shortened string + * + */ + protected String shortenText(String textValue, ToolItem item) { + if (textValue is null) { + return null; + } + + GC gc = new GC(item.getParent()); + + int maxWidth = item.getImage().getBounds().width * 4; + + if (gc.textExtent(textValue).x < maxWidth) { + gc.dispose(); + return textValue; + } + + for (int i = textValue.length; i > 0; i--) { + String test = textValue.substring(0, i); + test = test ~ ellipsis; + if (gc.textExtent(test).x < maxWidth) { + gc.dispose(); + return test; + } + + } + gc.dispose(); + // If for some reason we fall through abort + return textValue; + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/action/ContributionItem.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/action/ContributionItem.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,218 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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 + *******************************************************************************/ +module dwtx.jface.action.ContributionItem; + +import dwtx.jface.action.IContributionItem; +import dwtx.jface.action.IContributionManager; + +import dwt.widgets.Composite; +import dwt.widgets.CoolBar; +import dwt.widgets.Menu; +import dwt.widgets.ToolBar; + +import dwt.dwthelper.utils; + +/** + * An abstract base implementation for contribution items. + */ +public abstract class ContributionItem : IContributionItem { + + /** + * The identifier for this contribution item, of null if none. + */ + private String id = null; + + /** + * Indicates this item is visible in its manager; true + * by default. + */ + private bool visible = true; + + /** + * The parent contribution manager for this item + */ + private IContributionManager parent; + + /** + * Creates a contribution item with a null id. + * Calls this(String) with null. + */ + protected this() { + this(null); + } + + /** + * Creates a contribution item with the given (optional) id. + * The given id is used to find items in a contribution manager, + * and for positioning items relative to other items. + * + * @param id the contribution item identifier, or null + */ + protected this(String id) { + this.id = id; + } + + /** + * The default implementation of this IContributionItem + * method does nothing. Subclasses may override. + */ + public void dispose() { + } + + /** + * The default implementation of this IContributionItem + * method does nothing. Subclasses may override. + */ + public void fill(Composite parent) { + } + + /** + * The default implementation of this IContributionItem + * method does nothing. Subclasses may override. + */ + public void fill(Menu menu, int index) { + } + + /** + * The default implementation of this IContributionItem + * method does nothing. Subclasses may override. + */ + public void fill(ToolBar parent, int index) { + } + + /** + * The default implementation of this IContributionItem + * method does nothing. Subclasses may override. + * + * @since 3.0 + */ + public void fill(CoolBar parent, int index) { + } + + /** + * The default implementation of this IContributionItem + * method does nothing. Subclasses may override. + * + * @since 3.0 + */ + public void saveWidgetState() { + } + + /* (non-Javadoc) + * Method declared on IContributionItem. + */ + public String getId() { + return id; + } + + /** + * Returns the parent contribution manager, or null if this + * contribution item is not currently added to a contribution manager. + * + * @return the parent contribution manager, or null + * @since 2.0 + */ + public IContributionManager getParent() { + return parent; + } + + /** + * The default implementation of this IContributionItem + * method returns false. Subclasses may override. + */ + public bool isDirty() { + // @issue should this be false instead of calling isDynamic()? + return isDynamic(); + } + + /** + * The default implementation of this IContributionItem + * method returns true. Subclasses may override. + */ + public bool isEnabled() { + return true; + } + + /** + * The default implementation of this IContributionItem + * method returns false. Subclasses may override. + */ + public bool isDynamic() { + return false; + } + + /** + * The default implementation of this IContributionItem + * method returns false. Subclasses may override. + */ + public bool isGroupMarker() { + return false; + } + + /** + * The default implementation of this IContributionItem + * method returns false. Subclasses may override. + */ + public bool isSeparator() { + return false; + } + + /** + * The default implementation of this IContributionItem + * method returns the value recorded in an internal state variable, + * which is true by default. setVisible + * should be used to change this setting. + */ + public bool isVisible() { + return visible; + } + + /** + * The default implementation of this IContributionItem + * method stores the value in an internal state variable, + * which is true by default. + */ + public void setVisible(bool visible) { + this.visible = visible; + } + + /** + * Returns a string representation of this contribution item + * suitable only for debugging. + */ + public String toString() { + return this.classinfo.name ~ "(id=" ~ getId() ~ ")";//$NON-NLS-2$//$NON-NLS-1$ + } + + /** + * The default implementation of this IContributionItem + * method does nothing. Subclasses may override. + */ + public void update() { + } + + /* (non-Javadoc) + * Method declared on IContributionItem. + */ + public void setParent(IContributionManager parent) { + this.parent = parent; + } + + /** + * The ContributionItem implementation of this + * method declared on IContributionItem does nothing. + * Subclasses should override to update their state. + */ + public void update(String id) { + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/action/ContributionManager.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/action/ContributionManager.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,594 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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 + *******************************************************************************/ +module dwtx.jface.action.ContributionManager; + +import dwtx.jface.action.ActionContributionItem; + +import dwtx.jface.action.IContributionManager; +import dwtx.jface.action.IContributionItem; +import dwtx.jface.action.IContributionManagerOverrides; +import dwtx.jface.action.IAction; + +import tango.util.collection.ArraySeq; +import tango.util.collection.model.Seq; + +import dwtx.jface.util.Policy; + +import dwt.dwthelper.utils; +import dwt.dwthelper.Integer; +import tango.io.Stdout; +import tango.core.Exception; +import tango.text.convert.Format; + +/** + * Abstract base class for all contribution managers, and standard + * implementation of IContributionManager. This class provides + * functionality common across the specific managers defined by this framework. + *

+ * This class maintains a list of contribution items and a dirty flag, both as + * internal state. In addition to providing implementations of most + * IContributionManager methods, this class automatically + * coalesces adjacent separators, hides beginning and ending separators, and + * deals with dynamically changing sets of contributions. When the set of + * contributions does change dynamically, the changes are propagated to the + * control via the update method, which subclasses must + * implement. + *

+ *

+ * Note: A ContributionItem cannot be shared between different + * ContributionManagers. + *

+ */ +public abstract class ContributionManager : IContributionManager { + + // Internal debug flag. + // protected static final bool DEBUG = false; + + /** + * The list of contribution items. + */ + private Seq!(IContributionItem) contributions; + + /** + * Indicates whether the widgets are in sync with the contributions. + */ + private bool isDirty_ = true; + + /** + * Number of dynamic contribution items. + */ + private int dynamicItems = 0; + + /** + * The overrides for items of this manager + */ + private IContributionManagerOverrides overrides; + + /** + * Creates a new contribution manager. + */ + protected this() { + contributions = new ArraySeq!(IContributionItem); + // Do nothing. + } + + /* + * (non-Javadoc) Method declared on IContributionManager. + */ + public void add(IAction action) { + add(new ActionContributionItem(action)); + } + + /* + * (non-Javadoc) Method declared on IContributionManager. + */ + public void add(IContributionItem item) { + if (allowItem(item)) { + contributions.append(item); + itemAdded(item); + } + } + + /** + * Adds a contribution item to the start or end of the group with the given + * name. + * + * @param groupName + * the name of the group + * @param item + * the contribution item + * @param append + * true to add to the end of the group, and + * false to add the beginning of the group + * @exception IllegalArgumentException + * if there is no group with the given name + */ + private void addToGroup(String groupName, IContributionItem item, + bool append) { + int i; + auto items = contributions.elements(); + for (i = 0; items.more(); i++) { + IContributionItem o = cast(IContributionItem) items.get(); + if (o.isGroupMarker()) { + String id = o.getId(); + if (id !is null && id.equalsIgnoreCase(groupName)) { + i++; + if (append) { + for (; items.more(); i++) { + IContributionItem ci = cast(IContributionItem) items + .get(); + if (ci.isGroupMarker()) { + break; + } + } + } + if (allowItem(item)) { + //TODO: does this corrupt the iterator? + contributions.addAt(i, item); + itemAdded(item); + } + return; + } + } + } + throw new IllegalArgumentException("Group not found: " ~ groupName);//$NON-NLS-1$ + } + + /* + * (non-Javadoc) Method declared on IContributionManager. + */ + public void appendToGroup(String groupName, IAction action) { + addToGroup(groupName, new ActionContributionItem(action), true); + } + + /* + * (non-Javadoc) Method declared on IContributionManager. + */ + public void appendToGroup(String groupName, IContributionItem item) { + addToGroup(groupName, item, true); + } + + /** + * This method allows subclasses of ContributionManager to + * prevent certain items in the contributions list. + * ContributionManager will either block or allow an addition + * based on the result of this method call. This can be used to prevent + * duplication, for example. + * + * @param itemToAdd + * The contribution item to be added; may be null. + * @return true if the addition should be allowed; + * false otherwise. The default implementation allows + * all items. + * @since 3.0 + */ + protected bool allowItem(IContributionItem itemToAdd) { + return true; + } + + /** + * Internal debug method for printing statistics about this manager to + * System.out. + */ + protected void dumpStatistics() { + int size = 0; + if (contributions !is null) { + size = contributions.size(); + } + + Stdout.formatln(this.toString()); + Stdout.formatln(" Number of elements: {}", size);//$NON-NLS-1$ + int sum = 0; + for (int i = 0; i < size; i++) { + if ((cast(IContributionItem) contributions.get(i)).isVisible()) { + sum++; + } + } + Stdout.formatln(" Number of visible elements: {}", sum);//$NON-NLS-1$ + Stdout.formatln(" Is dirty: {}", isDirty()); //$NON-NLS-1$ + } + + /* + * (non-Javadoc) Method declared on IContributionManager. + */ + public IContributionItem find(String id) { + auto e = contributions.elements(); + while (e.more()) { + IContributionItem item = cast(IContributionItem) e.get(); + String itemId = item.getId(); + if (itemId !is null && itemId.equalsIgnoreCase(id)) { + return item; + } + } + return null; + } + + /* + * (non-Javadoc) Method declared on IContributionManager. + */ + public IContributionItem[] getItems() { + return contributions.toArray(); + } + + /** + * Return the number of contributions in this manager. + * + * @return the number of contributions in this manager + * @since 3.3 + */ + public int getSize() { + return contributions.size(); + } + + /** + * The ContributionManager implementation of this method + * declared on IContributionManager returns the current + * overrides. If there is no overrides it lazily creates one which overrides + * no item state. + * + * @since 2.0 + */ + public IContributionManagerOverrides getOverrides() { + if (overrides is null) { + overrides = new class IContributionManagerOverrides { + public ValueWrapperBool getEnabled(IContributionItem item) { + return null; + } + + public ValueWrapperInt getAccelerator(IContributionItem item) { + return null; + } + + public String getAcceleratorText(IContributionItem item) { + return null; + } + + public String getText(IContributionItem item) { + return null; + } + }; + } + return overrides; + } + + /** + * Returns whether this contribution manager contains dynamic items. A + * dynamic contribution item contributes items conditionally, dependent on + * some internal state. + * + * @return true if this manager contains dynamic items, and + * false otherwise + */ + protected bool hasDynamicItems() { + return (dynamicItems > 0); + } + + /** + * Returns the index of the item with the given id. + * + * @param id + * The id of the item whose index is requested. + * + * @return int the index or -1 if the item is not found + */ + public int indexOf(String id) { + for (int i = 0; i < contributions.size(); i++) { + IContributionItem item = cast(IContributionItem) contributions.get(i); + String itemId = item.getId(); + if (itemId !is null && itemId.equalsIgnoreCase(id)) { + return i; + } + } + return -1; + } + + /** + * Returns the index of the object in the internal structure. This is + * different from indexOf(String id) since some contribution + * items may not have an id. + * + * @param item + * The contribution item + * @return the index, or -1 if the item is not found + * @since 3.0 + */ + protected int indexOf(IContributionItem item) { + int res = -1; + int idx = 0; + foreach( e; contributions ){ + if( e == item ) { + res = idx; + break; + } + idx++; + } + return res; + } + + /** + * Insert the item at the given index. + * + * @param index + * The index to be used for insertion + * @param item + * The item to be inserted + */ + public void insert(int index, IContributionItem item) { + if (index > contributions.size()) { + throw new IndexOutOfBoundsException( Format( + "inserting {} at {}", item.getId(), index)); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (allowItem(item)) { + contributions.addAt(index, item); + itemAdded(item); + } + } + + /* + * (non-Javadoc) Method declared on IContributionManager. + */ + public void insertAfter(String ID, IAction action) { + insertAfter(ID, new ActionContributionItem(action)); + } + + /* + * (non-Javadoc) Method declared on IContributionManager. + */ + public void insertAfter(String ID, IContributionItem item) { + IContributionItem ci = find(ID); + if (ci is null) { + throw new IllegalArgumentException(Format("can't find ID{}", ID));//$NON-NLS-1$ + } + int ix = SeqIndexOf!(IContributionItem)( contributions, ci ); + if (ix >= 0) { + // System.out.println("insert after: " + ix); + if (allowItem(item)) { + contributions.addAt(ix + 1, item); + itemAdded(item); + } + } + } + + /* + * (non-Javadoc) Method declared on IContributionManager. + */ + public void insertBefore(String ID, IAction action) { + insertBefore(ID, new ActionContributionItem(action)); + } + + /* + * (non-Javadoc) Method declared on IContributionManager. + */ + public void insertBefore(String ID, IContributionItem item) { + IContributionItem ci = find(ID); + if (ci is null) { + throw new IllegalArgumentException(Format("can't find ID {}", ID));//$NON-NLS-1$ + } + int ix = SeqIndexOf!(IContributionItem)(contributions,ci); + if (ix >= 0) { + // System.out.println("insert before: " + ix); + if (allowItem(item)) { + contributions.addAt(ix, item); + itemAdded(item); + } + } + } + + /* + * (non-Javadoc) Method declared on IContributionManager. + */ + public bool isDirty() { + if (isDirty_) { + return true; + } + if (hasDynamicItems()) { + foreach( e; contributions ){ + IContributionItem item = cast(IContributionItem) e; + if (item.isDirty()) { + return true; + } + } + } + return false; + } + + /* + * (non-Javadoc) Method declared on IContributionManager. + */ + public bool isEmpty() { + return contributions.drained(); + } + + /** + * The given item was added to the list of contributions. Marks the manager + * as dirty and updates the number of dynamic items, and the memento. + * + * @param item + * the item to be added + * + */ + protected void itemAdded(IContributionItem item) { + item.setParent(this); + markDirty(); + if (item.isDynamic()) { + dynamicItems++; + } + } + + /** + * The given item was removed from the list of contributions. Marks the + * manager as dirty and updates the number of dynamic items. + * + * @param item + * remove given parent from list of contributions + */ + protected void itemRemoved(IContributionItem item) { + item.setParent(null); + markDirty(); + if (item.isDynamic()) { + dynamicItems--; + } + } + + /* + * (non-Javadoc) Method declared on IContributionManager. + */ + public void markDirty() { + setDirty(true); + } + + /* + * (non-Javadoc) Method declared on IContributionManager. + */ + public void prependToGroup(String groupName, IAction action) { + addToGroup(groupName, new ActionContributionItem(action), false); + } + + /* + * (non-Javadoc) Method declared on IContributionManager. + */ + public void prependToGroup(String groupName, IContributionItem item) { + addToGroup(groupName, item, false); + } + + /* + * (non-Javadoc) Method declared on IContributionManager. + */ + public IContributionItem remove(String ID) { + IContributionItem ci = find(ID); + if (ci is null) { + return null; + } + return remove(ci); + } + + /* + * (non-Javadoc) Method declared on IContributionManager. + */ + public IContributionItem remove(IContributionItem item) { + bool contained = contributions.contains(item); + contributions.remove(item); + if (contained) { + itemRemoved(item); + return item; + } + return null; + } + + /* + * (non-Javadoc) Method declared on IContributionManager. + */ + public void removeAll() { + IContributionItem[] items = getItems(); + contributions.clear(); + for (int i = 0; i < items.length; i++) { + IContributionItem item = items[i]; + itemRemoved(item); + } + dynamicItems = 0; + markDirty(); + } + + /** + * Replaces the item of the given identifier with another contribution item. + * This can be used, for example, to replace large contribution items with + * placeholders to avoid memory leaks. If the identifier cannot be found in + * the current list of items, then this does nothing. If multiple + * occurrences are found, then the replacement items is put in the first + * position and the other positions are removed. + * + * @param identifier + * The identifier to look for in the list of contributions; + * should not be null. + * @param replacementItem + * The contribution item to replace the old item; must not be + * null. Use + * {@link dwtx.jface.action.ContributionManager#remove(java.lang.String) remove} + * if that is what you want to do. + * @return true if the given identifier can be; + * @since 3.0 + */ + public bool replaceItem(String identifier, + IContributionItem replacementItem) { + if (identifier is null) { + return false; + } + + int index = indexOf(identifier); + if (index < 0) { + return false; // couldn't find the item. + } + + // Remove the old item. + IContributionItem oldItem = cast(IContributionItem) contributions + .get(index); + itemRemoved(oldItem); + + // Add the new item. + contributions.replaceAt(index, replacementItem); + itemAdded(replacementItem); // throws NPE if (replacementItem is null) + + // Go through and remove duplicates. + for (int i = contributions.size() - 1; i > index; i--) { + IContributionItem item = cast(IContributionItem) contributions.get(i); + if ((item !is null) && (identifier.equals(item.getId()))) { + if (Policy.TRACE_TOOLBAR) { + Stdout.formatln("Removing duplicate on replace: {}", identifier); //$NON-NLS-1$ + } + contributions.removeAt(i); + itemRemoved(item); + } + } + + return true; // success + } + + /** + * Sets whether this manager is dirty. When dirty, the list of contributions + * is not accurately reflected in the corresponding widgets. + * + * @param dirty + * true if this manager is dirty, and + * false if it is up-to-date + */ + protected void setDirty(bool dirty) { + isDirty_ = dirty; + } + + /** + * Sets the overrides for this contribution manager + * + * @param newOverrides + * the overrides for the items of this manager + * @since 2.0 + */ + public void setOverrides(IContributionManagerOverrides newOverrides) { + overrides = newOverrides; + } + + /** + * An internal method for setting the order of the contribution items. + * + * @param items + * the contribution items in the specified order + * @since 3.0 + */ + protected void internalSetItems(IContributionItem[] items) { + contributions.clear(); + for (int i = 0; i < items.length; i++) { + if (allowItem(items[i])) { + contributions.append(items[i]); + } + } + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/action/ExternalActionManager.d --- /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 + *******************************************************************************/ + +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; + +/** + *

+ * 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. + *

+ *

+ * 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. + *

+ *

+ * For example, in the Eclipse workbench, this mechanism is used to allow the + * command architecture to override certain values in action contribution items. + *

+ *

+ * This class is not intended to be called or extended by any external clients. + *

+ * + * @since 3.0 + */ +public final class ExternalActionManager { + + /** + * A simple implementation of the ICallback mechanism that + * simply takes a BindingManager and a + * CommandManager. + * + * @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 + * null. + */ + 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 + * null. + */ + 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 null. + */ + private const Set!(String) loggedCommandIds; + + /** + * The list of listeners that have registered for property change + * notification. This is a map of command identifiers (String) + * to listeners (IPropertyChangeListener). + */ + private const Map!(String,IPropertyChangeListener) registeredListeners; + + static this(){ + RESOURCE_BUNDLE = ResourceBundle.getBundle(ExternalActionManager.classinfo.name); + } + /** + * Constructs a new instance of CommandCallback 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 null. + * @param commandManager + * The command manager which will provide the callback; must + * not be null. + * + * @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 CommandCallback with the + * workbench it should be using. + * + * @param bindingManager + * The binding manager which will provide the callback; must + * not be null. + * @param commandManager + * The command manager which will provide the callback; must + * not be null. + * @param activeChecker + * The callback mechanism for checking whether a command is + * active; must not be null. + * + * @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 null. It + * is assumed that the command has no parameters. + * @return The array of active triggers (TriggerSequence) + * for a particular command identifier. This value is guaranteed + * not to be null, 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 + * null + * @return true if the command is active; + * false otherwise. + */ + public bool isActive(String commandId); + } + + /** + *

+ * 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}. + *

+ *

+ * Clients may implement this interface, but must not extend. + *

+ * + * @since 3.2 + */ + public static interface IBindingManagerCallback : ICallback { + + /** + *

+ * Returns the active bindings for a particular command identifier. + *

+ * + * @param commandId + * The identifier of the command whose bindings are + * requested. This argument may be null. It + * is assumed that the command has no parameters. + * @return The array of active triggers (TriggerSequence) + * for a particular command identifier. This value is guaranteed + * not to be null, 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 { + + /** + *

+ * Adds a listener to the object referenced by identifier. + * 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. + *

+ *

+ * 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. + *

+ * + * @param identifier + * The identifier of the item to which the listener should be + * attached; must not be null. + * @param listener + * The listener to be added; must not be null. + */ + 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 null. + * @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 null. + * @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 true if the accelerator is already being used + * and shouldn't be used again; false 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 null. + * @return true if the item is active; false + * otherwise. + */ + public bool isActive(String identifier); + + /** + * Removes a listener from the object referenced by + * 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 to from the listener should be + * removed; must not be null. + * @param listener + * The listener to be removed; must not be null. + */ + public void removePropertyChangeListener(String identifier, + IPropertyChangeListener listener); + } + + /** + * The singleton instance of this class. This value may be null-- + * 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 null. + */ + 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 ExternalActionManager. + */ + 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 null + * 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 + * null if the default is acceptable (i.e., no + * extra information will provided to actions). + */ + public void setCallback(ICallback callbackToUse) { + callback = callbackToUse; + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/action/GroupMarker.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/action/GroupMarker.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,49 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +module dwtx.jface.action.GroupMarker; + +import dwtx.jface.action.AbstractGroupMarker; + +import dwt.dwthelper.utils; + +/** + * A group marker is a special kind of contribution item denoting + * the beginning of a group. These groups are used to structure + * the list of items. Unlike regular contribution items and + * separators, group markers have no visual representation. + * The name of the group is synonymous with the contribution item id. + *

+ * This class may be instantiated; it is not intended to be + * subclassed outside the framework. + *

+ */ +public class GroupMarker : AbstractGroupMarker { + /** + * Create a new group marker with the given name. + * The group name must not be null or the empty string. + * The group name is also used as the item id. + * + * @param groupName the name of the group + */ + public this(String groupName) { + super(groupName); + } + + /** + * The GroupMarker implementation of this method + * returns false since group markers are always invisible. + */ + public bool isVisible() { + return false; + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/action/IAction.d --- a/dwtx/jface/action/IAction.d Mon Mar 31 02:00:41 2008 +0200 +++ b/dwtx/jface/action/IAction.d Tue Apr 01 08:00:31 2008 +0200 @@ -57,25 +57,25 @@ * * @since 2.1 */ - public static int AS_UNSPECIFIED = 0x00; + public static const int AS_UNSPECIFIED = 0x00; /** * Action style constant (value 1) indicating action is * a simple push button. */ - public static int AS_PUSH_BUTTON = 0x01; + public static const int AS_PUSH_BUTTON = 0x01; /** * Action style constant (value 2) indicating action is * a check box (or a toggle button). */ - public static int AS_CHECK_BOX = 0x02; + public static const int AS_CHECK_BOX = 0x02; /** * Action style constant (value 4) indicating action is * a drop down menu. */ - public static int AS_DROP_DOWN_MENU = 0x04; + public static const int AS_DROP_DOWN_MENU = 0x04; /** * Action style constant (value 8) indicating action is @@ -83,7 +83,7 @@ * * @since 2.1 */ - public static int AS_RADIO_BUTTON = 0x08; + public static const int AS_RADIO_BUTTON = 0x08; /** * Property name of an action's text (value "text"). diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/action/IContributionItem.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/action/IContributionItem.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,201 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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 + *******************************************************************************/ + +module dwtx.jface.action.IContributionItem; + +import dwtx.jface.action.IContributionManager; + +import dwt.widgets.Composite; +import dwt.widgets.CoolBar; +import dwt.widgets.Menu; +import dwt.widgets.ToolBar; + +import dwt.dwthelper.utils; + +/** + * A contribution item represents a contribution to a shared UI resource such as a + * menu or tool bar. More generally, contribution items are managed by a contribution + * manager. + * For instance, in a tool bar a contribution item is a tool bar button or a separator. + * In a menu bar a contribution item is a menu, and in a menu a contribution item + * is a menu item or separator. + *

+ * A contribution item can realize itself in different DWT widgets, using the different + * fill methods. The same type of contribution item can be used with a + * MenuBarManager, ToolBarManager, CoolBarManager, + * or a StatusLineManager. + *

+ *

+ * This interface is internal to the framework; it should not be implemented outside + * the framework. + *

+ * + * @see IContributionManager + */ +public interface IContributionItem { + + /** + * Disposes of this contribution item. Called by the parent + * contribution manager when the manager is being disposed. + * Clients should not call this method directly, unless they + * have removed this contribution item from the containing + * IContributionManager before the contribution lifecycle + * has ended. + * + * @since 2.1 + */ + public void dispose(); + + /** + * Fills the given composite control with controls representing this + * contribution item. Used by StatusLineManager. + * + * @param parent the parent control + */ + public void fill(Composite parent); + + /** + * Fills the given menu with controls representing this contribution item. + * Used by MenuManager. + * + * @param parent the parent menu + * @param index the index where the controls are inserted, + * or -1 to insert at the end + */ + public void fill(Menu parent, int index); + + /** + * Fills the given tool bar with controls representing this contribution item. + * Used by ToolBarManager. + * + * @param parent the parent tool bar + * @param index the index where the controls are inserted, + * or -1 to insert at the end + */ + public void fill(ToolBar parent, int index); + + /** + * Fills the given cool bar with controls representing this contribution item. + * Used by CoolBarManager. + * + * @param parent the parent cool bar + * @param index the index where the controls are inserted, + * or -1 to insert at the end + * @since 3.0 + */ + public void fill(CoolBar parent, int index); + + /** + * Returns the identifier of this contribution item. + * The id is used for retrieving an item from its manager. + * + * @return the contribution item identifier, or null + * if none + */ + public String getId(); + + /** + * Returns whether this contribution item is enabled. + * + * @return true if this item is enabled + */ + public bool isEnabled(); + + /** + * Returns whether this contribution item is dirty. A dirty item will be + * recreated when the action bar is updated. + * + * @return true if this item is dirty + */ + public bool isDirty(); + + /** + * Returns whether this contribution item is dynamic. A dynamic contribution + * item contributes items conditionally, dependent on some internal state. + * + * @return true if this item is dynamic, and + * false for normal items + */ + public bool isDynamic(); + + /** + * Returns whether this contribution item is a group marker. + * This information is used when adding items to a group. + * + * @return true if this item is a group marker, and + * false for normal items + * + * @see GroupMarker + * @see IContributionManager#appendToGroup(String, IContributionItem) + * @see IContributionManager#prependToGroup(String, IContributionItem) + */ + public bool isGroupMarker(); + + /** + * Returns whether this contribution item is a separator. + * This information is used to enable hiding of unnecessary separators. + * + * @return true if this item is a separator, and + * false for normal items + * @see Separator + */ + public bool isSeparator(); + + /** + * Returns whether this contribution item is visibile within its manager. + * + * @return true if this item is visible, and + * false otherwise + */ + public bool isVisible(); + + /** + * Saves any state information of the control(s) owned by this contribution item. + * The contribution manager calls this method before disposing of the controls. + * + * @since 3.0 + */ + public void saveWidgetState(); + + /** + * Sets the parent manager of this item + * + * @param parent the parent contribution manager + * @since 2.0 + */ + public void setParent(IContributionManager parent); + + /** + * Sets whether this contribution item is visibile within its manager. + * + * @param visible true if this item should be visible, and + * false otherwise + */ + public void setVisible(bool visible); + + /** + * Updates any DWT controls cached by this contribution item with any + * changes which have been made to this contribution item since the last update. + * Called by contribution manager update methods. + */ + public void update(); + + /** + * Updates any DWT controls cached by this contribution item with changes + * for the the given property. + * + * @param id the id of the changed property + * @since 2.0 + */ + public void update(String id); +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/action/IContributionManager.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/action/IContributionManager.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,236 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +module dwtx.jface.action.IContributionManager; + +import dwtx.jface.action.IAction; +import dwtx.jface.action.IContributionItem; +import dwtx.jface.action.IContributionManagerOverrides; + +import dwt.dwthelper.utils; + +/** + * A contribution manager organizes contributions to such UI components + * as menus, toolbars and status lines. + *

+ * A contribution manager keeps track of a list of contribution + * items. Each contribution item may has an optional identifier, which can be used + * to retrieve items from a manager, and for positioning items relative to + * each other. The list of contribution items can be subdivided into named groups + * using special contribution items that serve as group markers. + *

+ *

+ * The IContributionManager interface provides general + * protocol for adding, removing, and retrieving contribution items. + * It also provides convenience methods that make it convenient + * to contribute actions. This interface should be implemented + * by all objects that wish to manage contributions. + *

+ *

+ * There are several implementions of this interface in this package, + * including ones for menus ({@link MenuManager MenuManager}), + * tool bars ({@link ToolBarManager ToolBarManager}), + * and status lines ({@link StatusLineManager StatusLineManager}). + *

+ */ +public interface IContributionManager { + /** + * Adds an action as a contribution item to this manager. + * Equivalent to add(new ActionContributionItem(action)). + * + * @param action the action + */ + public void add(IAction action); + + /** + * Adds a contribution item to this manager. + * + * @param item the contribution item + */ + public void add(IContributionItem item); + + /** + * Adds a contribution item for the given action at the end of the group + * with the given name. + * Equivalent to + * appendToGroup(groupName,new ActionContributionItem(action)). + * + * @param groupName the name of the group + * @param action the action + * @exception IllegalArgumentException if there is no group with + * the given name + */ + public void appendToGroup(String groupName, IAction action); + + /** + * Adds a contribution item to this manager at the end of the group + * with the given name. + * + * @param groupName the name of the group + * @param item the contribution item + * @exception IllegalArgumentException if there is no group with + * the given name + */ + public void appendToGroup(String groupName, IContributionItem item); + + /** + * Finds the contribution item with the given id. + * + * @param id the contribution item id + * @return the contribution item, or null if + * no item with the given id can be found + */ + public IContributionItem find(String id); + + /** + * Returns all contribution items known to this manager. + * + * @return a list of contribution items + */ + public IContributionItem[] getItems(); + + /** + * Returns the overrides for the items of this manager. + * + * @return the overrides for the items of this manager + * @since 2.0 + */ + public IContributionManagerOverrides getOverrides(); + + /** + * Inserts a contribution item for the given action after the item + * with the given id. + * Equivalent to + * insertAfter(id,new ActionContributionItem(action)). + * + * @param id the contribution item id + * @param action the action to insert + * @exception IllegalArgumentException if there is no item with + * the given id + */ + public void insertAfter(String id, IAction action); + + /** + * Inserts a contribution item after the item with the given id. + * + * @param id the contribution item id + * @param item the contribution item to insert + * @exception IllegalArgumentException if there is no item with + * the given id + */ + public void insertAfter(String id, IContributionItem item); + + /** + * Inserts a contribution item for the given action before the item + * with the given id. + * Equivalent to + * insertBefore(id,new ActionContributionItem(action)). + * + * @param id the contribution item id + * @param action the action to insert + * @exception IllegalArgumentException if there is no item with + * the given id + */ + public void insertBefore(String id, IAction action); + + /** + * Inserts a contribution item before the item with the given id. + * + * @param id the contribution item id + * @param item the contribution item to insert + * @exception IllegalArgumentException if there is no item with + * the given id + */ + public void insertBefore(String id, IContributionItem item); + + /** + * Returns whether the list of contributions has recently changed and + * has yet to be reflected in the corresponding widgets. + * + * @return true if this manager is dirty, and false + * if it is up-to-date + */ + public bool isDirty(); + + /** + * Returns whether this manager has any contribution items. + * + * @return true if there are no items, and + * false otherwise + */ + public bool isEmpty(); + + /** + * Marks this contribution manager as dirty. + */ + public void markDirty(); + + /** + * Adds a contribution item for the given action at the beginning of the + * group with the given name. + * Equivalent to + * prependToGroup(groupName,new ActionContributionItem(action)). + * + * @param groupName the name of the group + * @param action the action + * @exception IllegalArgumentException if there is no group with + * the given name + */ + public void prependToGroup(String groupName, IAction action); + + /** + * Adds a contribution item to this manager at the beginning of the + * group with the given name. + * + * @param groupName the name of the group + * @param item the contribution item + * @exception IllegalArgumentException if there is no group with + * the given name + */ + public void prependToGroup(String groupName, IContributionItem item); + + /** + * Removes and returns the contribution item with the given id from this manager. + * Returns null if this manager has no contribution items + * with the given id. + * + * @param id the contribution item id + * @return the item that was found and removed, or null if none + */ + public IContributionItem remove(String id); + + /** + * Removes the given contribution item from the contribution items + * known to this manager. + * + * @param item the contribution item + * @return the item parameter if the item was removed, + * and null if it was not found + */ + public IContributionItem remove(IContributionItem item); + + /** + * Removes all contribution items from this manager. + */ + public void removeAll(); + + /** + * Updates this manager's underlying widget(s) with any changes which + * have been made to it or its items. Normally changes to a contribution + * manager merely mark it as dirty, without updating the underlying widgets. + * This brings the underlying widgets up to date with any changes. + * + * @param force true means update even if not dirty, + * and false for normal incremental updating + */ + public void update(bool force); +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/action/IContributionManagerOverrides.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/action/IContributionManagerOverrides.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,80 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +module dwtx.jface.action.IContributionManagerOverrides; + +import dwtx.jface.action.IContributionItem; + +import dwt.dwthelper.utils; + +/** + * This interface is used by instances of IContributionItem + * to determine if the values for certain properties have been overriden + * by their manager. + *

+ * This interface is internal to the framework; it should not be implemented outside + * the framework. + *

+ * + * @since 2.0 + */ +public interface IContributionManagerOverrides { + /** + * Id for the enabled property. Value is "enabled". + * + * @since 2.0 + */ + public const static String P_ENABLED = "enabled"; //$NON-NLS-1$ + + /** + * Find out the enablement of the item + * @param item the contribution item for which the enable override value is + * determined + * @return
    + *
  • bool.TRUE if the given contribution item should be enabled
  • + *
  • bool.FALSE if the item should be disabled
  • + *
  • null if the item may determine its own enablement
  • + *
+ * @since 2.0 + */ + public ValueWrapperBool getEnabled(IContributionItem item); + + /** + * This is not intended to be called outside of the workbench. This method + * is intended to be deprecated in 3.1. + * + * TODO deprecate for 3.1 + * @param item the contribution item for which the accelerator value is determined + * @return the accelerator + */ + public ValueWrapperInt getAccelerator(IContributionItem item); + + /** + * This is not intended to be called outside of the workbench. This method + * is intended to be deprecated in 3.1. + * + * TODO deprecate for 3.1 + * @param item the contribution item for which the accelerator text is determined + * @return the text for the accelerator + */ + public String getAcceleratorText(IContributionItem item); + + /** + * This is not intended to be called outside of the workbench. This method + * is intended to be deprecated in 3.1. + * + * TODO deprecate for 3.1 + * @param item the contribution item for which the text is determined + * @return the text + */ + public String getText(IContributionItem item); +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/action/IMenuListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/action/IMenuListener.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,30 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +module dwtx.jface.action.IMenuListener; + +import dwtx.jface.action.IMenuManager; + +/** + * A menu listener that gets informed when a menu is about to show. + * + * @see MenuManager#addMenuListener + */ +public interface IMenuListener { + /** + * Notifies this listener that the menu is about to be shown by + * the given menu manager. + * + * @param manager the menu manager + */ + public void menuAboutToShow(IMenuManager manager); +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/action/IMenuListener2.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/action/IMenuListener2.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 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 + *******************************************************************************/ +module dwtx.jface.action.IMenuListener2; + +import dwtx.jface.action.IMenuListener; +import dwtx.jface.action.IMenuManager; + +/** + * A menu listener that gets informed when a menu is about to hide. + * + * @see MenuManager#addMenuListener + * @since 3.2 + */ +public interface IMenuListener2 : IMenuListener { + /** + * Notifies this listener that the menu is about to be hidden by + * the given menu manager. + * + * @param manager the menu manager + */ + public void menuAboutToHide(IMenuManager manager); +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/action/IMenuManager.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/action/IMenuManager.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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 + *******************************************************************************/ +module dwtx.jface.action.IMenuManager; + +import dwtx.jface.action.IContributionItem; +import dwtx.jface.action.IContributionManager; +import dwtx.jface.action.IMenuListener; + +import dwt.dwthelper.utils; + +/** + * The IMenuManager interface provides protocol for managing + * contributions to a menu bar and its sub menus. + * An IMenuManager is also an IContributionItem, + * allowing sub-menus to be nested in parent menus. + *

+ * This interface is internal to the framework; it should not be implemented outside + * the framework. + *

+ *

+ * This package provides a concrete menu manager implementation, + * {@link MenuManager MenuManager}. + *

+ */ +public interface IMenuManager : IContributionManager, IContributionItem { + /** + * Adds a menu listener to this menu. + * Has no effect if an identical listener is already registered. + * + * @param listener a menu listener + */ + public void addMenuListener(IMenuListener listener); + + /** + * Finds the manager for the menu at the given path. A path + * consists of contribution item ids separated by the separator + * character. The path separator character is '/'. + *

+ * Convenience for findUsingPath(path) which + * extracts an IMenuManager if possible. + *

+ * + * @param path the path string + * @return the menu contribution item, or null + * if there is no such contribution item or if the item does + * not have an associated menu manager + */ + public IMenuManager findMenuUsingPath(String path); + + /** + * Finds the contribution item at the given path. A path + * consists of contribution item ids separated by the separator + * character. The path separator character is '/'. + * + * @param path the path string + * @return the contribution item, or null if there is no + * such contribution item + */ + public IContributionItem findUsingPath(String path); + + /** + * Returns whether all items should be removed when the menu is about to + * show, but before notifying menu listeners. The default is + * false. + * + * @return true if all items should be removed when shown, + * false if not + */ + public bool getRemoveAllWhenShown(); + + /** + * Returns whether this menu should be enabled or not. + * + * @return true if enabled, and + * false if disabled + */ + public bool isEnabled(); + + /** + * Removes the given menu listener from this menu. + * Has no effect if an identical listener is not registered. + * + * @param listener the menu listener + */ + public void removeMenuListener(IMenuListener listener); + + /** + * Sets whether all items should be removed when the menu is about to show, + * but before notifying menu listeners. + * + * @param removeAll + * true if all items should be removed when shown, + * false if not + */ + public void setRemoveAllWhenShown(bool removeAll); + + /** + * Incrementally builds the menu from the contribution items, and + * does so recursively for all submenus. + * + * @param force true means update even if not dirty, + * and false for normal incremental updating + */ + public void updateAll(bool force); +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/action/LegacyActionTools.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/action/LegacyActionTools.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,746 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + *******************************************************************************/ + +module dwtx.jface.action.LegacyActionTools; + +import tango.util.collection.HashMap; +import tango.util.collection.model.Map; + +import dwt.DWT; +import dwtx.jface.resource.JFaceResources; + +import dwt.dwthelper.utils; +import dwt.dwthelper.Integer; +static import tango.text.Text; +alias tango.text.Text.Text!(char) StringBuffer; + +/** + *

+ * Some static utility methods for handling labels on actions. This includes + * mnemonics and accelerators. + *

+ *

+ * Clients may neither instantiate this class nor extend. + *

+ * + * @since 3.2 + */ +public final class LegacyActionTools { + + /** + * Table of key codes (key type: String, value type: + * Integer); null if not yet initialized. + * + * @see #findKeyCode + */ + private static Map!(String,Object) keyCodes = null; + + /** + * Table of string representations of keys (key type: Integer, + * value type: String); null> if not yet + * initialized. + * + * @see #findKeyString + */ + private static Map!(Object,String) keyStrings = null; + + /** + * The localized uppercase version of ALT + */ + private static String localizedAlt; + + /** + * The localized uppercase version of COMMAND + */ + private static String localizedCommand; + + /** + * The localized uppercase version of CTRL + */ + private static String localizedCtrl; + + /** + * Table of key codes (key type: String, value type: + * Integer); null if not yet initialized. The + * key is the localalized name of the key as it appears in menus. + * + * @see #findLocalizedKeyCode + */ + private static Map!(String,Object) localizedKeyCodes = null; + + /** + * The localized uppercase version of SHIFT + */ + private static String localizedShift; + + /** + * The constant to use if there is no mnemonic for this location. + */ + public static const char MNEMONIC_NONE = 0; + + /** + * Converts an accelerator key code to a string representation. + * + * @param keyCode + * the key code to be translated + * @return a string representation of the key code + */ + public static final String convertAccelerator(int keyCode) { + String modifier = getModifierString(keyCode); + String fullKey; + if (modifier.equals("")) { //$NON-NLS-1$ + fullKey = findKeyString(keyCode); + } else { + fullKey = modifier ~ "+" ~ findKeyString(keyCode); //$NON-NLS-1$ + } + return fullKey; + } + + /** + * Parses the given accelerator text, and converts it to an accelerator key + * code. + * + * @param acceleratorText + * the accelerator text + * @return the DWT key code, or 0 if there is no accelerator + */ + public static final int convertAccelerator(String acceleratorText) { + int accelerator = 0; + auto tokens = tango.text.Util.delimit( acceleratorText, "+" ); + + int keyCode = -1; + foreach( idx, token; tokens ){ +// bool hasMoreTokens = stok.hasMoreTokens(); +// while (hasMoreTokens) { +// String token = stok.nextToken(); +// bool hasMoreTokens = idx+1 < tokens.length;//stok.hasMoreTokens(); + // Every token except the last must be one of the modifiers + // Ctrl, Shift, Alt, or Command + if (idx+1 < tokens.length) { + int modifier = findModifier(token); + if (modifier !is 0) { + accelerator |= modifier; + } else { // Leave if there are none + return 0; + } + } else { + keyCode = findKeyCode(token); + } + } + if (keyCode !is -1) { + accelerator |= keyCode; + } + return accelerator; + } + + /** + * Parses the given accelerator text, and converts it to an accelerator key + * code. + * + * Support for localized modifiers is for backwards compatibility with 1.0. + * Use setAccelerator(int) to set accelerators programatically or the + * accelerator tag in action definitions in plugin.xml. + * + * @param acceleratorText + * the accelerator text localized to the current locale + * @return the DWT key code, or 0 if there is no accelerator + */ + static final int convertLocalizedAccelerator(String acceleratorText) { + int accelerator = 0; + auto tokens = tango.text.Util.delimit( acceleratorText, "+" ); +// StringTokenizer stok = new StringTokenizer(acceleratorText, "+"); //$NON-NLS-1$ + + int keyCode = -1; + + foreach( idx, token; tokens ){ +// bool hasMoreTokens = stok.hasMoreTokens(); +// while (hasMoreTokens) { +// String token = stok.nextToken(); +// hasMoreTokens = stok.hasMoreTokens(); + // Every token except the last must be one of the modifiers + // Ctrl, Shift, Alt, or Command + if (idx+1 < tokens.length) { + int modifier = findLocalizedModifier(token); + if (modifier !is 0) { + accelerator |= modifier; + } else { // Leave if there are none + return 0; + } + } else { + keyCode = findLocalizedKeyCode(token); + } + } + if (keyCode !is -1) { + accelerator |= keyCode; + } + return accelerator; + } + + /** + * Extracts the accelerator text from the given text. Returns + * null if there is no accelerator text, and the empty string + * if there is no text after the accelerator delimeter (tab or '@'). + * + * @param text + * the text for the action; may be null. + * @return the accelerator text, or null + */ + public static final String extractAcceleratorText(String text) { + if (text is null) { + return null; + } + + int index = text.lastIndexOf('\t'); + if (index is -1) { + index = text.lastIndexOf('@'); + } + if (index >= 0) { + return text.substring(index + 1); + } + return null; + } + + /** + * Extracts the mnemonic text from the given string. + * + * @param text + * The text from which the mnemonic should be extracted; may be + * null + * @return The text of the mnemonic; will be {@link #MNEMONIC_NONE} if there + * is no mnemonic; + */ + public static final char extractMnemonic(String text) { + if (text is null) { + return MNEMONIC_NONE; + } + + int index = text.indexOf('&'); + if (index is -1) { + return MNEMONIC_NONE; + } + + int textLength = text.length; + + // Ignore '&' at the end of the string. + if (index is textLength - 1) { + return MNEMONIC_NONE; + } + + // Ignore two consecutive ampersands. + while (text.charAt(index + 1) is '&') { + index = text.indexOf('&', ++index); + if (index is textLength - 1) { + return MNEMONIC_NONE; + } + } + + return text.charAt(index + 1); + } + + /** + * Maps a standard keyboard key name to an DWT key code. Key names are + * converted to upper case before comparison. If the key name is a single + * letter, for example "S", its character code is returned. + *

+ * The following key names are known (case is ignored): + *

    + *
  • "BACKSPACE"
  • + *
  • "TAB"
  • + *
  • "RETURN"
  • + *
  • "ENTER"
  • + *
  • "ESC"
  • + *
  • "ESCAPE"
  • + *
  • "DELETE"
  • + *
  • "SPACE"
  • + *
  • "ARROW_UP", "ARROW_DOWN", + * "ARROW_LEFT", and "ARROW_RIGHT"
  • + *
  • "PAGE_UP" and "PAGE_DOWN"
  • + *
  • "HOME"
  • + *
  • "END"
  • + *
  • "INSERT"
  • + *
  • "F1", "F2" through "F12"
  • + *
+ *

+ * + * @param token + * the key name + * @return the DWT key code, -1 if no match was found + * @see DWT + */ + public static final int findKeyCode(String token) { + if (keyCodes is null) { + initKeyCodes(); + } + token = token.toUpperCase(); + Integer i = cast(Integer) keyCodes.get(token); + if (i !is null) { + return i.intValue(); + } + if (token.length is 1) { + return token.charAt(0); + } + return -1; + } + + /** + * Maps an DWT key code to a standard keyboard key name. The key code is + * stripped of modifiers (DWT.CTRL, DWT.ALT, DWT.SHIFT, and DWT.COMMAND). If + * the key code is not an DWT code (for example if it a key code for the key + * 'S'), a string containing a character representation of the key code is + * returned. + * + * @param keyCode + * the key code to be translated + * @return the string representation of the key code + * @see DWT + * @since 2.0 + */ + public static final String findKeyString( int keyCode) { + if (keyStrings is null) { + initKeyStrings(); + } + int i = keyCode & ~(DWT.CTRL | DWT.ALT | DWT.SHIFT | DWT.COMMAND); + Integer integer = new Integer(i); + String result = keyStrings.get(integer); + if (result !is null) { + return result; + } + result = dcharToString( cast(dchar) i ); + return result; + } + + /** + * Find the supplied code for a localized key. As #findKeyCode but localized + * to the current locale. + * + * Support for localized modifiers is for backwards compatibility with 1.0. + * Use setAccelerator(int) to set accelerators programatically or the + * accelerator tag in action definitions in plugin.xml. + * + * @param token + * the localized key name + * @return the DWT key code, -1 if no match was found + * @see #findKeyCode + */ + private static final int findLocalizedKeyCode(String token) { + if (localizedKeyCodes is null) { + initLocalizedKeyCodes(); + } + token = token.toUpperCase(); + Integer i = cast(Integer) localizedKeyCodes.get(token); + if (i !is null) { + return i.intValue(); + } + if (token.length is 1) { + return token.charAt(0); + } + return -1; + } + + /** + * Maps the localized modifier names to a code in the same manner as + * #findModifier. + * + * Support for localized modifiers is for backwards compatibility with 1.0. + * Use setAccelerator(int) to set accelerators programatically or the + * accelerator tag in action definitions in plugin.xml. + * + * @see #findModifier + */ + private static final int findLocalizedModifier(String token) { + if (localizedCtrl is null) { + initLocalizedModifiers(); + } + + token = token.toUpperCase(); + if (token.equals(localizedCtrl)) { + return DWT.CTRL; + } + if (token.equals(localizedShift)) { + return DWT.SHIFT; + } + if (token.equals(localizedAlt)) { + return DWT.ALT; + } + if (token.equals(localizedCommand)) { + return DWT.COMMAND; + } + return 0; + } + + /** + * Maps standard keyboard modifier key names to the corresponding DWT + * modifier bit. The following modifier key names are recognized (case is + * ignored): "CTRL", "SHIFT", + * "ALT", and "COMMAND". The given modifier + * key name is converted to upper case before comparison. + * + * @param token + * the modifier key name + * @return the DWT modifier bit, or 0 if no match was found + * @see DWT + */ + public static final int findModifier(String token) { + token = token.toUpperCase(); + if (token.equals("CTRL")) { //$NON-NLS-1$ + return DWT.CTRL; + } + if (token.equals("SHIFT")) { //$NON-NLS-1$ + return DWT.SHIFT; + } + if (token.equals("ALT")) { //$NON-NLS-1$ + return DWT.ALT; + } + if (token.equals("COMMAND")) { //$NON-NLS-1$ + return DWT.COMMAND; + } + return 0; + } + + /** + * Returns a string representation of an DWT modifier bit (DWT.CTRL, + * DWT.ALT, DWT.SHIFT, and DWT.COMMAND). Returns null if the + * key code is not an DWT modifier bit. + * + * @param keyCode + * the DWT modifier bit to be translated + * @return the string representation of the DWT modifier bit, or + * null if the key code was not an DWT modifier bit + * @see DWT + */ + public static final String findModifierString(int keyCode) { + if (keyCode is DWT.CTRL) { + return JFaceResources.getString("Ctrl"); //$NON-NLS-1$ + } + if (keyCode is DWT.ALT) { + return JFaceResources.getString("Alt"); //$NON-NLS-1$ + } + if (keyCode is DWT.SHIFT) { + return JFaceResources.getString("Shift"); //$NON-NLS-1$ + } + if (keyCode is DWT.COMMAND) { + return JFaceResources.getString("Command"); //$NON-NLS-1$ + } + return null; + } + + /** + * Returns the string representation of the modifiers (Ctrl, Alt, Shift, + * Command) of the key event. + * + * @param keyCode + * The key code for which the modifier string is desired. + * @return The string representation of the key code; never + * null. + */ + private static String getModifierString(int keyCode) { + String modString = ""; //$NON-NLS-1$ + + if ((keyCode & DWT.CTRL) !is 0) { + modString = findModifierString(keyCode & DWT.CTRL); + } + + if ((keyCode & DWT.ALT) !is 0) { + if (modString.equals("")) { //$NON-NLS-1$ + modString = findModifierString(keyCode & DWT.ALT); + } else { + modString = modString + ~ "+" ~ findModifierString(keyCode & DWT.ALT); //$NON-NLS-1$ + } + } + + if ((keyCode & DWT.SHIFT) !is 0) { + if (modString.equals("")) { //$NON-NLS-1$ + modString = findModifierString(keyCode & DWT.SHIFT); + } else { + modString = modString + ~ "+" ~ findModifierString(keyCode & DWT.SHIFT); //$NON-NLS-1$ + } + } + + if ((keyCode & DWT.COMMAND) !is 0) { + if (modString.equals("")) { //$NON-NLS-1$ + modString = findModifierString(keyCode & DWT.COMMAND); + } else { + modString = modString + ~ "+" ~ findModifierString(keyCode & DWT.COMMAND); //$NON-NLS-1$ + } + } + + return modString; + } + + /** + * Initializes the internal key code table. + */ + private static final void initKeyCodes() { + keyCodes = new HashMap!(String,Object); + + keyCodes.add("BACKSPACE", new Integer(8)); //$NON-NLS-1$ + keyCodes.add("TAB", new Integer(9)); //$NON-NLS-1$ + keyCodes.add("RETURN", new Integer(13)); //$NON-NLS-1$ + keyCodes.add("ENTER", new Integer(13)); //$NON-NLS-1$ + keyCodes.add("ESCAPE", new Integer(27)); //$NON-NLS-1$ + keyCodes.add("ESC", new Integer(27)); //$NON-NLS-1$ + keyCodes.add("DELETE", new Integer(127)); //$NON-NLS-1$ + + keyCodes.add("SPACE", new Integer(' ')); //$NON-NLS-1$ + keyCodes.add("ARROW_UP", new Integer(DWT.ARROW_UP)); //$NON-NLS-1$ + keyCodes.add("ARROW_DOWN", new Integer(DWT.ARROW_DOWN)); //$NON-NLS-1$ + keyCodes.add("ARROW_LEFT", new Integer(DWT.ARROW_LEFT)); //$NON-NLS-1$ + keyCodes.add("ARROW_RIGHT", new Integer(DWT.ARROW_RIGHT)); //$NON-NLS-1$ + keyCodes.add("PAGE_UP", new Integer(DWT.PAGE_UP)); //$NON-NLS-1$ + keyCodes.add("PAGE_DOWN", new Integer(DWT.PAGE_DOWN)); //$NON-NLS-1$ + keyCodes.add("HOME", new Integer(DWT.HOME)); //$NON-NLS-1$ + keyCodes.add("END", new Integer(DWT.END)); //$NON-NLS-1$ + keyCodes.add("INSERT", new Integer(DWT.INSERT)); //$NON-NLS-1$ + keyCodes.add("F1", new Integer(DWT.F1)); //$NON-NLS-1$ + keyCodes.add("F2", new Integer(DWT.F2)); //$NON-NLS-1$ + keyCodes.add("F3", new Integer(DWT.F3)); //$NON-NLS-1$ + keyCodes.add("F4", new Integer(DWT.F4)); //$NON-NLS-1$ + keyCodes.add("F5", new Integer(DWT.F5)); //$NON-NLS-1$ + keyCodes.add("F6", new Integer(DWT.F6)); //$NON-NLS-1$ + keyCodes.add("F7", new Integer(DWT.F7)); //$NON-NLS-1$ + keyCodes.add("F8", new Integer(DWT.F8)); //$NON-NLS-1$ + keyCodes.add("F9", new Integer(DWT.F9)); //$NON-NLS-1$ + keyCodes.add("F10", new Integer(DWT.F10)); //$NON-NLS-1$ + keyCodes.add("F11", new Integer(DWT.F11)); //$NON-NLS-1$ + keyCodes.add("F12", new Integer(DWT.F12)); //$NON-NLS-1$ + } + + /** + * Initializes the internal key string table. + */ + private static void initKeyStrings() { + keyStrings = new HashMap!(Object,String); + + keyStrings.add(new Integer(8), JFaceResources.getString("Backspace")); //$NON-NLS-1$ + keyStrings.add(new Integer(9), JFaceResources.getString("Tab")); //$NON-NLS-1$ + keyStrings.add(new Integer(13), JFaceResources.getString("Return")); //$NON-NLS-1$ + keyStrings.add(new Integer(13), JFaceResources.getString("Enter")); //$NON-NLS-1$ + keyStrings.add(new Integer(27), JFaceResources.getString("Escape")); //$NON-NLS-1$ + keyStrings.add(new Integer(27), JFaceResources.getString("Esc")); //$NON-NLS-1$ + keyStrings.add(new Integer(127), JFaceResources.getString("Delete")); //$NON-NLS-1$ + + keyStrings.add(new Integer(' '), JFaceResources.getString("Space")); //$NON-NLS-1$ + + keyStrings.add(new Integer(DWT.ARROW_UP), JFaceResources + .getString("Arrow_Up")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.ARROW_DOWN), JFaceResources + .getString("Arrow_Down")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.ARROW_LEFT), JFaceResources + .getString("Arrow_Left")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.ARROW_RIGHT), JFaceResources + .getString("Arrow_Right")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.PAGE_UP), JFaceResources + .getString("Page_Up")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.PAGE_DOWN), JFaceResources + .getString("Page_Down")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.HOME), JFaceResources.getString("Home")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.END), JFaceResources.getString("End")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.INSERT), JFaceResources + .getString("Insert")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.F1), JFaceResources.getString("F1")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.F2), JFaceResources.getString("F2")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.F3), JFaceResources.getString("F3")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.F4), JFaceResources.getString("F4")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.F5), JFaceResources.getString("F5")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.F6), JFaceResources.getString("F6")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.F7), JFaceResources.getString("F7")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.F8), JFaceResources.getString("F8")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.F9), JFaceResources.getString("F9")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.F10), JFaceResources.getString("F10")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.F11), JFaceResources.getString("F11")); //$NON-NLS-1$ + keyStrings.add(new Integer(DWT.F12), JFaceResources.getString("F12")); //$NON-NLS-1$ + } + + /** + * Initializes the localized internal key code table. + */ + private static void initLocalizedKeyCodes() { + localizedKeyCodes = new HashMap!(String,Object); + + localizedKeyCodes.add(JFaceResources + .getString("Backspace").toUpperCase(), new Integer(8)); //$NON-NLS-1$ + localizedKeyCodes.add( + JFaceResources.getString("Tab").toUpperCase(), new Integer(9)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("Return").toUpperCase(), new Integer(13)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("Enter").toUpperCase(), new Integer(13)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("Escape").toUpperCase(), new Integer(27)); //$NON-NLS-1$ + localizedKeyCodes.add( + JFaceResources.getString("Esc").toUpperCase(), new Integer(27)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("Delete").toUpperCase(), new Integer(127)); //$NON-NLS-1$ + + localizedKeyCodes + .add( + JFaceResources.getString("Space").toUpperCase(), new Integer(' ')); //$NON-NLS-1$ + + localizedKeyCodes + .add( + JFaceResources.getString("Arrow_Up").toUpperCase(), new Integer(DWT.ARROW_UP)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("Arrow_Down").toUpperCase(), new Integer(DWT.ARROW_DOWN)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("Arrow_Left").toUpperCase(), new Integer(DWT.ARROW_LEFT)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("Arrow_Right").toUpperCase(), new Integer(DWT.ARROW_RIGHT)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("Page_Up").toUpperCase(), new Integer(DWT.PAGE_UP)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("Page_Down").toUpperCase(), new Integer(DWT.PAGE_DOWN)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("Home").toUpperCase(), new Integer(DWT.HOME)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("End").toUpperCase(), new Integer(DWT.END)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("Insert").toUpperCase(), new Integer(DWT.INSERT)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("F1").toUpperCase(), new Integer(DWT.F1)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("F2").toUpperCase(), new Integer(DWT.F2)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("F3").toUpperCase(), new Integer(DWT.F3)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("F4").toUpperCase(), new Integer(DWT.F4)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("F5").toUpperCase(), new Integer(DWT.F5)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("F6").toUpperCase(), new Integer(DWT.F6)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("F7").toUpperCase(), new Integer(DWT.F7)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("F8").toUpperCase(), new Integer(DWT.F8)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("F9").toUpperCase(), new Integer(DWT.F9)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("F10").toUpperCase(), new Integer(DWT.F10)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("F11").toUpperCase(), new Integer(DWT.F11)); //$NON-NLS-1$ + localizedKeyCodes + .add( + JFaceResources.getString("F12").toUpperCase(), new Integer(DWT.F12)); //$NON-NLS-1$ + } + + /** + * Initialize the list of localized modifiers + */ + private static void initLocalizedModifiers() { + localizedCtrl = JFaceResources.getString("Ctrl").toUpperCase(); //$NON-NLS-1$ + localizedShift = JFaceResources.getString("Shift").toUpperCase(); //$NON-NLS-1$ + localizedAlt = JFaceResources.getString("Alt").toUpperCase(); //$NON-NLS-1$ + localizedCommand = JFaceResources.getString("Command").toUpperCase(); //$NON-NLS-1$ + } + + /** + * Convenience method for removing any optional accelerator text from the + * given string. The accelerator text appears at the end of the text, and is + * separated from the main part by a single tab character '\t'. + * + * @param text + * the text + * @return the text sans accelerator + */ + public static final String removeAcceleratorText(String text) { + int index = text.lastIndexOf('\t'); + if (index is -1) { + index = text.lastIndexOf('@'); + } + if (index >= 0) { + return text.substring(0, index); + } + return text; + } + + /** + * Convenience method for removing any mnemonics from the given string. For + * example, removeMnemonics("&Open") will return + * "Open". + * + * @param text + * the text + * @return the text sans mnemonics + */ + public static final String removeMnemonics(String text) { + int index = text.indexOf('&'); + if (index is -1) { + return text; + } + int len = text.length; + StringBuffer sb = new StringBuffer(len); + int lastIndex = 0; + while (index !is -1) { + // ignore & at the end + if (index is len - 1) { + break; + } + // handle the && case + if (text.charAt(index + 1) is '&') { + ++index; + } + + // DBCS languages use "(&X)" format + if (index > 0 && text.charAt(index - 1) is '(' + && text.length >= index + 3 + && text.charAt(index + 2) is ')') { + sb.append(text.substring(lastIndex, index - 1)); + index += 3; + } else { + sb.append(text.substring(lastIndex, index)); + // skip the & + ++index; + } + + lastIndex = index; + index = text.indexOf('&', index); + } + if (lastIndex < len) { + sb.append(text.substring(lastIndex, len)); + } + return sb.toString(); + } + + /** + * This class cannot be instantiated. + */ + private this() { + // Does nothing + } + +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/action/MenuManager.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/action/MenuManager.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,830 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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 + *******************************************************************************/ +module dwtx.jface.action.MenuManager; + +import dwtx.jface.action.ContributionManager; +import dwtx.jface.action.IMenuManager; +import dwtx.jface.action.IContributionManagerOverrides; +import dwtx.jface.action.IContributionManager; +import dwtx.jface.action.IMenuListener; +import dwtx.jface.action.IMenuListener2; +import dwtx.jface.action.IContributionItem; +import dwtx.jface.action.SubContributionItem; +import dwtx.jface.action.IAction; +import dwtx.jface.action.ExternalActionManager; + +import tango.util.collection.ArraySeq; +import tango.util.collection.model.Seq; + +import dwt.DWT; +import dwt.events.MenuAdapter; +import dwt.events.MenuEvent; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.CoolBar; +import dwt.widgets.Decorations; +import dwt.widgets.Menu; +import dwt.widgets.MenuItem; +import dwt.widgets.Shell; +import dwt.widgets.ToolBar; +import dwtx.core.runtime.ListenerList; + +import dwt.dwthelper.utils; + +/** + * A menu manager is a contribution manager which realizes itself and its items + * in a menu control; either as a menu bar, a sub-menu, or a context menu. + *

+ * This class may be instantiated; it may also be subclassed. + *

+ */ +public class MenuManager : ContributionManager, IMenuManager { + + public bool isDirty(){ + return super.isDirty(); + } + + /** + * The menu id. + */ + private String id; + + /** + * List of registered menu listeners (element type: IMenuListener). + */ + private ListenerList listeners; + + /** + * The menu control; null before + * creation and after disposal. + */ + private Menu menu = null; + + /** + * The menu item widget; null before + * creation and after disposal. This field is used + * when this menu manager is a sub-menu. + */ + private MenuItem menuItem; + + /** + * The text for a sub-menu. + */ + private String menuText; + + /** + * The overrides for items of this manager + */ + private IContributionManagerOverrides overrides; + + /** + * The parent contribution manager. + */ + private IContributionManager parent; + + /** + * Indicates whether removeAll should be + * called just before the menu is displayed. + */ + private bool removeAllWhenShown = false; + + /** + * Indicates this item is visible in its manager; true + * by default. + * @since 3.3 + */ + protected bool visible = true; + + /** + * Creates a menu manager. The text and id are null. + * Typically used for creating a context menu, where it doesn't need to be referred to by id. + */ + public this() { + this(null, null); + } + + /** + * Creates a menu manager with the given text. The id of the menu + * is null. + * Typically used for creating a sub-menu, where it doesn't need to be referred to by id. + * + * @param text the text for the menu, or null if none + */ + public this(String text) { + this(text, null); + } + + /** + * Creates a menu manager with the given text and id. + * Typically used for creating a sub-menu, where it needs to be referred to by id. + * + * @param text the text for the menu, or null if none + * @param id the menu id, or null if it is to have no id + */ + public this(String text, String id) { + listeners = new ListenerList(); + this.menuText = text; + this.id = id; + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IMenuManager#addMenuListener(dwtx.jface.action.IMenuListener) + */ + public void addMenuListener(IMenuListener listener) { + listeners.add(cast(Object)listener); + } + + /** + * Creates and returns an DWT context menu control for this menu, + * and installs all registered contributions. + * Does not create a new control if one already exists. + *

+ * Note that the menu is not expected to be dynamic. + *

+ * + * @param parent the parent control + * @return the menu control + */ + public Menu createContextMenu(Control parent) { + if (!menuExist()) { + menu = new Menu(parent); + initializeMenu(); + } + return menu; + } + + /** + * Creates and returns an DWT menu bar control for this menu, + * for use in the given Decorations, and installs all registered + * contributions. Does not create a new control if one already exists. + * + * @param parent the parent decorations + * @return the menu control + * @since 2.1 + */ + public Menu createMenuBar(Decorations parent) { + if (!menuExist()) { + menu = new Menu(parent, DWT.BAR); + update(false); + } + return menu; + } + + /** + * Creates and returns an DWT menu bar control for this menu, for use in the + * given Shell, and installs all registered contributions. Does not + * create a new control if one already exists. This implementation simply calls + * the createMenuBar(Decorations) method + * + * @param parent the parent decorations + * @return the menu control + * @deprecated use createMenuBar(Decorations) instead. + */ + public Menu createMenuBar(Shell parent) { + return createMenuBar(cast(Decorations) parent); + } + + /** + * Disposes of this menu manager and frees all allocated DWT resources. + * Notifies all contribution items of the dispose. Note that this method does + * not clean up references between this menu manager and its associated + * contribution items. Use removeAll for that purpose. + */ + public void dispose() { + if (menuExist()) { + menu.dispose(); + } + menu = null; + + if (menuItem !is null) { + menuItem.dispose(); + menuItem = null; + } + + IContributionItem[] items = getItems(); + for (int i = 0; i < items.length; i++) { + items[i].dispose(); + } + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IContributionItem#fill(dwt.widgets.Composite) + */ + public void fill(Composite parent) { + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IContributionItem#fill(dwt.widgets.CoolBar, int) + */ + public void fill(CoolBar parent, int index) { + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IContributionItem#fill(dwt.widgets.Menu, int) + */ + public void fill(Menu parent, int index) { + if (menuItem is null || menuItem.isDisposed()) { + if (index >= 0) { + menuItem = new MenuItem(parent, DWT.CASCADE, index); + } else { + menuItem = new MenuItem(parent, DWT.CASCADE); + } + + menuItem.setText(getMenuText()); + + if (!menuExist()) { + menu = new Menu(parent); + } + + menuItem.setMenu(menu); + + initializeMenu(); + + // populate the submenu, in order to enable accelerators + // and to set enabled state on the menuItem properly + update(true); + } + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IContributionItem#fill(dwt.widgets.ToolBar, int) + */ + public void fill(ToolBar parent, int index) { + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IMenuManager#findMenuUsingPath(java.lang.String) + */ + public IMenuManager findMenuUsingPath(String path) { + IContributionItem item = findUsingPath(path); + if (auto mm = cast(IMenuManager)item ) { + return mm; + } + return null; + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IMenuManager#findUsingPath(java.lang.String) + */ + public IContributionItem findUsingPath(String path) { + String id = path; + String rest = null; + int separator = dwt.dwthelper.utils.indexOf( path, '/'); + if (separator !is -1) { + id = path.substring(0, separator); + rest = path.substring(separator + 1); + } else { + return super.find(path); + } + + IContributionItem item = super.find(id); + if (auto mm = cast(IMenuManager)item ) { + return mm.findUsingPath(rest); + } + return null; + } + + /** + * Notifies any menu listeners that a menu is about to show. + * Only listeners registered at the time this method is called are notified. + * + * @param manager the menu manager + * + * @see IMenuListener#menuAboutToShow + */ + private void fireAboutToShow(IMenuManager manager) { + Object[] listeners = this.listeners.getListeners(); + for (int i = 0; i < listeners.length; ++i) { + (cast(IMenuListener) listeners[i]).menuAboutToShow(manager); + } + } + + /** + * Notifies any menu listeners that a menu is about to hide. + * Only listeners registered at the time this method is called are notified. + * + * @param manager the menu manager + * + */ + private void fireAboutToHide(IMenuManager manager) { + final Object[] listeners = this.listeners.getListeners(); + for (int i = 0; i < listeners.length; ++i) { + final Object listener = listeners[i]; + if (auto listener2 = cast(IMenuListener2)listener) { + listener2.menuAboutToHide(manager); + } + } + } + + /** + * Returns the menu id. The menu id is used when creating a contribution + * item for adding this menu as a sub menu of another. + * + * @return the menu id + */ + public String getId() { + return id; + } + + /** + * Returns the DWT menu control for this menu manager. + * + * @return the menu control + */ + public Menu getMenu() { + return menu; + } + + /** + * Returns the text shown in the menu. + * + * @return the menu text + */ + public String getMenuText() { + return menuText; + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IContributionManager#getOverrides() + */ + public IContributionManagerOverrides getOverrides() { + if (overrides is null) { + if (parent is null) { + overrides = new class IContributionManagerOverrides { + public ValueWrapperInt getAccelerator(IContributionItem item) { + return null; + } + + public String getAcceleratorText(IContributionItem item) { + return null; + } + + public ValueWrapperBool getEnabled(IContributionItem item) { + return null; + } + + public String getText(IContributionItem item) { + return null; + } + }; + } else { + overrides = parent.getOverrides(); + } + super.setOverrides(overrides); + } + return overrides; + } + + /** + * Returns the parent contribution manager of this manger. + * + * @return the parent contribution manager + * @since 2.0 + */ + public IContributionManager getParent() { + return parent; + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IMenuManager#getRemoveAllWhenShown() + */ + public bool getRemoveAllWhenShown() { + return removeAllWhenShown; + } + + /** + * Notifies all listeners that this menu is about to appear. + */ + private void handleAboutToShow() { + if (removeAllWhenShown) { + removeAll(); + } + fireAboutToShow(this); + update(false, true); + } + + /** + * Notifies all listeners that this menu is about to disappear. + */ + private void handleAboutToHide() { + fireAboutToHide(this); + } + + /** + * Initializes the menu control. + */ + private void initializeMenu() { + menu.addMenuListener(new class MenuAdapter { + public void menuHidden(MenuEvent e) { + // ApplicationWindow.resetDescription(e.widget); + handleAboutToHide(); + } + + public void menuShown(MenuEvent e) { + handleAboutToShow(); + } + }); + // Don't do an update(true) here, in case menu is never opened. + // Always do it lazily in handleAboutToShow(). + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IContributionItem#isDynamic() + */ + public bool isDynamic() { + return false; + } + + /** + * Returns whether this menu should be enabled or not. + * Used to enable the menu item containing this menu when it is realized as a sub-menu. + *

+ * The default implementation of this framework method + * returns true. Subclasses may reimplement. + *

+ * + * @return true if enabled, and + * false if disabled + */ + public bool isEnabled() { + return true; + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IContributionItem#isGroupMarker() + */ + public bool isGroupMarker() { + return false; + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IContributionItem#isSeparator() + */ + public bool isSeparator() { + return false; + } + + /** + * Check if the contribution is item is a subsitute for ourselves + * + * @param item the contribution item + * @return true if give item is a substitution for ourselves + * @deprecated this method is no longer a part of the + * {@link dwtx.jface.action.IContributionItem} API. + */ + public bool isSubstituteFor(IContributionItem item) { + return this.opEquals(cast(Object)item) !is 0; + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IContributionItem#isVisible() + */ + public bool isVisible() { + if (!visible) { + return false; // short circut calculations in this case + } + + // menus arent visible if all of its children are invisible (or only contains visible separators). + IContributionItem[] childItems = getItems(); + bool visibleChildren = false; + for (int j = 0; j < childItems.length; j++) { + if (childItems[j].isVisible() && !childItems[j].isSeparator()) { + visibleChildren = true; + break; + } + } + + return visibleChildren; + } + + + /** + * The MenuManager implementation of this ContributionManager method + * also propagates the dirty flag up the parent chain. + * + * @since 3.1 + */ + public void markDirty() { + super.markDirty(); + // Can't optimize by short-circuiting when the first dirty manager is encountered, + // since non-visible children are not even processed. + // That is, it's possible to have a dirty sub-menu under a non-dirty parent menu + // even after the parent menu has been updated. + // If items are added/removed in the sub-menu, we still need to propagate the dirty flag up, + // even if the sub-menu is already dirty, since the result of isVisible() may change + // due to the added/removed items. + IContributionManager parent = getParent(); + if (parent !is null) { + parent.markDirty(); + } + } + + /** + * Returns whether the menu control is created + * and not disposed. + * + * @return true if the control is created + * and not disposed, false otherwise + */ + private bool menuExist() { + return menu !is null && !menu.isDisposed(); + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IMenuManager#removeMenuListener(dwtx.jface.action.IMenuListener) + */ + public void removeMenuListener(IMenuListener listener) { + listeners.remove(cast(Object)listener); + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IContributionItem#saveWidgetState() + */ + public void saveWidgetState() { + } + + /** + * Sets the overrides for this contribution manager + * + * @param newOverrides the overrides for the items of this manager + * @since 2.0 + */ + public void setOverrides(IContributionManagerOverrides newOverrides) { + overrides = newOverrides; + super.setOverrides(overrides); + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IContributionItem#setParent(dwtx.jface.action.IContributionManager) + */ + public void setParent(IContributionManager manager) { + parent = manager; + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IMenuManager#setRemoveAllWhenShown(bool) + */ + public void setRemoveAllWhenShown(bool removeAll) { + this.removeAllWhenShown = removeAll; + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IContributionItem#setVisible(bool) + */ + public void setVisible(bool visible) { + this.visible = visible; + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IContributionItem#update() + */ + public void update() { + updateMenuItem(); + } + + /** + * The MenuManager implementation of this IContributionManager + * updates this menu, but not any of its submenus. + * + * @see #updateAll + */ + public void update(bool force) { + update(force, false); + } + + /** + * Incrementally builds the menu from the contribution items. + * This method leaves out double separators and separators in the first + * or last position. + * + * @param force true means update even if not dirty, + * and false for normal incremental updating + * @param recursive true means recursively update + * all submenus, and false means just this menu + */ + protected void update(bool force, bool recursive) { + if (isDirty() || force) { + if (menuExist()) { + // clean contains all active items without double separators + IContributionItem[] items = getItems(); + auto clean = new ArraySeq!(IContributionItem); + clean.capacity(items.length); + IContributionItem separator = null; + for (int i = 0; i < items.length; ++i) { + IContributionItem ci = items[i]; + if (!ci.isVisible()) { + continue; + } + if (ci.isSeparator()) { + // delay creation until necessary + // (handles both adjacent separators, and separator at end) + separator = ci; + } else { + if (separator !is null) { + if (clean.size() > 0) { + clean.append(separator); + } + separator = null; + } + clean.append(ci); + } + } + + // remove obsolete (removed or non active) + MenuItem[] mi = menu.getItems(); + + for (int i = 0; i < mi.length; i++) { + Object data = mi[i].getData(); + + bool clean_contains = false; + foreach( ci; clean ){ + if( cast(Object)ci == data ) clean_contains = true; + } + if (data is null || !clean_contains) { + mi[i].dispose(); + } else if (cast(IContributionItem)data + && (cast(IContributionItem) data).isDynamic() + && (cast(IContributionItem) data).isDirty()) { + mi[i].dispose(); + } + } + + // add new + mi = menu.getItems(); + int srcIx = 0; + int destIx = 0; + + foreach( src; clean ){ + IContributionItem dest; + + // get corresponding item in DWT widget + if (srcIx < mi.length) { + dest = cast(IContributionItem) mi[srcIx].getData(); + } else { + dest = null; + } + + if (dest !is null && (cast(Object)src).opEquals(cast(Object)dest)) { + srcIx++; + destIx++; + } else if (dest !is null && dest.isSeparator() + && src.isSeparator()) { + mi[srcIx].setData(cast(Object)src); + srcIx++; + destIx++; + } else { + int start = menu.getItemCount(); + src.fill(menu, destIx); + int newItems = menu.getItemCount() - start; + for (int i = 0; i < newItems; i++) { + MenuItem item = menu.getItem(destIx++); + item.setData(cast(Object)src); + } + } + + // May be we can optimize this call. If the menu has just + // been created via the call src.fill(fMenuBar, destIx) then + // the menu has already been updated with update(true) + // (see MenuManager). So if force is true we do it again. But + // we can't set force to false since then information for the + // sub sub menus is lost. + if (recursive) { + IContributionItem item = src; + if ( auto sub = cast(SubContributionItem)item ) { + item = sub.getInnerItem(); + } + if (auto mm = cast(IMenuManager)item ) { + mm.updateAll(force); + } + } + + } + + // remove any old menu items not accounted for + for (; srcIx < mi.length; srcIx++) { + mi[srcIx].dispose(); + } + + setDirty(false); + } + } else { + // I am not dirty. Check if I must recursivly walk down the hierarchy. + if (recursive) { + IContributionItem[] items = getItems(); + for (int i = 0; i < items.length; ++i) { + IContributionItem ci = items[i]; + if ( auto mm = cast(IMenuManager) ci ) { + if (mm.isVisible()) { + mm.updateAll(force); + } + } + } + } + } + updateMenuItem(); + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IContributionItem#update(java.lang.String) + */ + public void update(String property) { + IContributionItem items[] = getItems(); + + for (int i = 0; i < items.length; i++) { + items[i].update(property); + } + + if (menu !is null && !menu.isDisposed() && menu.getParentItem() !is null + && IAction.TEXT.equals(property)) { + String text = getOverrides().getText(this); + + if (text is null) { + text = getMenuText(); + } + + if (text !is null) { + ExternalActionManager.ICallback callback = ExternalActionManager + .getInstance().getCallback(); + + if (callback !is null) { + int index = dwt.dwthelper.utils.indexOf( text, '&' ); + + if (index >= 0 && index < text.length - 1) { + char character = CharacterToUpper(text + .charAt(index + 1)); + + if (callback.isAcceleratorInUse(DWT.ALT | character)) { + if (index is 0) { + text = text.substring(1); + } else { + text = text.substring(0, index) + ~ text.substring(index + 1); + } + } + } + } + + menu.getParentItem().setText(text); + } + } + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IMenuManager#updateAll(bool) + */ + public void updateAll(bool force) { + update(force, true); + } + + /** + * Updates the menu item for this sub menu. + * The menu item is disabled if this sub menu is empty. + * Does nothing if this menu is not a submenu. + */ + private void updateMenuItem() { + /* + * Commented out until proper solution to enablement of + * menu item for a sub-menu is found. See bug 30833 for + * more details. + * + if (menuItem !is null && !menuItem.isDisposed() && menuExist()) { + IContributionItem items[] = getItems(); + bool enabled = false; + for (int i = 0; i < items.length; i++) { + IContributionItem item = items[i]; + enabled = item.isEnabled(); + if(enabled) break; + } + // Workaround for 1GDDCN2: DWT:Linux - MenuItem.setEnabled() always causes a redraw + if (menuItem.getEnabled() !is enabled) + menuItem.setEnabled(enabled); + } + */ + // Partial fix for bug #34969 - diable the menu item if no + // items in sub-menu (for context menus). + if (menuItem !is null && !menuItem.isDisposed() && menuExist()) { + bool enabled = menu.getItemCount() > 0; + // Workaround for 1GDDCN2: DWT:Linux - MenuItem.setEnabled() always causes a redraw + if (menuItem.getEnabled() !is enabled) { + // We only do this for context menus (for bug #34969) + Menu topMenu = menu; + while (topMenu.getParentMenu() !is null) { + topMenu = topMenu.getParentMenu(); + } + if ((topMenu.getStyle() & DWT.BAR) is 0) { + menuItem.setEnabled(enabled); + } + } + } + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/action/Separator.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/action/Separator.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,85 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +module dwtx.jface.action.Separator; + +import dwtx.jface.action.AbstractGroupMarker; + +import dwt.DWT; +import dwt.widgets.Menu; +import dwt.widgets.MenuItem; +import dwt.widgets.ToolBar; +import dwt.widgets.ToolItem; + +import dwt.dwthelper.utils; + +/** + * A separator is a special kind of contribution item which acts + * as a visual separator and, optionally, acts as a group marker. + * Unlike group markers, separators do have a visual representation for + * menus and toolbars. + *

+ * This class may be instantiated; it is not intended to be + * subclassed outside the framework. + *

+ */ +public class Separator : AbstractGroupMarker { + /** + * Creates a separator which does not start a new group. + */ + public this() { + super(); + } + + /** + * Creates a new separator which also defines a new group having the given group name. + * The group name must not be null or the empty string. + * The group name is also used as the item id. + * + * @param groupName the group name of the separator + */ + public this(String groupName) { + super(groupName); + } + + /* (non-Javadoc) + * Method declared on IContributionItem. + * Fills the given menu with a DWT separator MenuItem. + */ + public void fill(Menu menu, int index) { + if (index >= 0) { + new MenuItem(menu, DWT.SEPARATOR, index); + } else { + new MenuItem(menu, DWT.SEPARATOR); + } + } + + /* (non-Javadoc) + * Method declared on IContributionItem. + * Fills the given tool bar with a DWT separator ToolItem. + */ + public void fill(ToolBar toolbar, int index) { + if (index >= 0) { + new ToolItem(toolbar, DWT.SEPARATOR, index); + } else { + new ToolItem(toolbar, DWT.SEPARATOR); + } + } + + /** + * The Separator implementation of this IContributionItem + * method returns true + */ + public bool isSeparator() { + return true; + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/action/SubContributionItem.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/action/SubContributionItem.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,189 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +module dwtx.jface.action.SubContributionItem; + +import dwtx.jface.action.IContributionItem; +import dwtx.jface.action.IContributionManager; + +import dwt.widgets.Composite; +import dwt.widgets.CoolBar; +import dwt.widgets.Menu; +import dwt.widgets.ToolBar; + +import dwt.dwthelper.utils; + +/** + * A SubContributionItem is a wrapper for an IContributionItem. + * It is used within a SubContributionManager to control the visibility + * of items. + *

+ * This class is not intended to be subclassed. + *

+ */ +public class SubContributionItem : IContributionItem { + /** + * The visibility of the item. + */ + private bool visible; + + /** + * The inner item for this contribution. + */ + private IContributionItem innerItem; + + /** + * Creates a new SubContributionItem. + * @param item the contribution item to be wrapped + */ + public this(IContributionItem item) { + innerItem = item; + } + + /** + * The default implementation of this IContributionItem + * delegates to the inner item. Subclasses may override. + */ + public void dispose() { + innerItem.dispose(); + } + + /* (non-Javadoc) + * Method declared on IContributionItem. + */ + public void fill(Composite parent) { + if (visible) { + innerItem.fill(parent); + } + } + + /* (non-Javadoc) + * Method declared on IContributionItem. + */ + public void fill(Menu parent, int index) { + if (visible) { + innerItem.fill(parent, index); + } + } + + /* (non-Javadoc) + * Method declared on IContributionItem. + */ + public void fill(ToolBar parent, int index) { + if (visible) { + innerItem.fill(parent, index); + } + } + + /* (non-Javadoc) + * Method declared on IContributionItem. + */ + public String getId() { + return innerItem.getId(); + } + + /** + * Returns the inner contribution item. + * + * @return the inner contribution item + */ + public IContributionItem getInnerItem() { + return innerItem; + } + + /* (non-Javadoc) + * Method declared on IContributionItem. + */ + public bool isEnabled() { + return innerItem.isEnabled(); + } + + /* (non-Javadoc) + * Method declared on IContributionItem. + */ + public bool isDirty() { + return innerItem.isDirty(); + } + + /* (non-Javadoc) + * Method declared on IContributionItem. + */ + public bool isDynamic() { + return innerItem.isDynamic(); + } + + /* (non-Javadoc) + * Method declared on IContributionItem. + */ + public bool isGroupMarker() { + return innerItem.isGroupMarker(); + } + + /* (non-Javadoc) + * Method declared on IContributionItem. + */ + public bool isSeparator() { + return innerItem.isSeparator(); + } + + /* (non-Javadoc) + * Method declared on IContributionItem. + */ + public bool isVisible() { + return visible && innerItem.isVisible(); + } + + /* (non-Javadoc) + * Method declared on IContributionItem. + */ + public void setParent(IContributionManager parent) { + // do nothing, the parent of our inner item + // is its SubContributionManager + } + + /* (non-Javadoc) + * Method declared on IContributionItem. + */ + public void setVisible(bool visible) { + this.visible = visible; + } + + /* (non-Javadoc) + * Method declared on IContributionItem. + */ + public void update() { + innerItem.update(); + } + + /* (non-Javadoc) + * Method declared on IContributionItem. + */ + public void update(String id) { + innerItem.update(id); + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IContributionItem#fill(dwt.widgets.CoolBar, int) + */ + public void fill(CoolBar parent, int index) { + if (visible) { + innerItem.fill(parent, index); + } + } + + /* (non-Javadoc) + * @see dwtx.jface.action.IContributionItem#saveWidgetState() + */ + public void saveWidgetState() { + } + +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/Binding.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/Binding.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,397 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 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 + *******************************************************************************/ +module dwtx.jface.bindings.Binding; + +import dwtx.jface.bindings.TriggerSequence; +// import java.io.BufferedWriter; +// import java.io.IOException; +// import java.io.StringWriter; + +import dwtx.core.commands.ParameterizedCommand; +import dwtx.jface.util.Util; + +import dwt.dwthelper.utils; +import tango.text.convert.Format; + +/** + *

+ * A binding is a link between user input and the triggering of a particular + * command. The most common example of a binding is a keyboard shortcut, but + * there are also mouse and gesture bindings. + *

+ *

+ * Bindings are linked to particular conditions within the application. Some of + * these conditions change infrequently (e.g., locale, scheme), while some will + * tend to change quite frequently (e.g., context). This allows the bindings to + * be tailored to particular situations. For example, a set of bindings may be + * appropriate only inside a text editor. Or, perhaps, a set of bindings might + * be appropriate only for a given locale, such as bindings that coexist with + * the Input Method Editor (IME) on Chinese locales. + *

+ *

+ * It is also possible to remove a particular binding. This is typically done as + * part of user configuration (e.g., user changing keyboard shortcuts). However, + * it can also be helpful when trying to change a binding on a particular locale + * or platform. An "unbinding" is really just a binding with no command + * identifier. For it to unbind a particular binding, it must match that binding + * in its context identifier and scheme identifier. Subclasses (e.g., + * KeyBinding) may require other properties to match (e.g., + * keySequence). If these properties match, then this is an + * unbinding. Note: the locale and platform can be different. + *

+ *

+ * For example, imagine you have a key binding that looks like this: + *

+ *
+ * KeyBinding(command, scheme, context, "Ctrl+Shift+F")
+ * 
+ *

+ * On GTK+, the "Ctrl+Shift+F" interferes with some native behaviour. To change + * the binding, we first unbind the "Ctrl+Shift+F" key sequence by + * assigning it a null command on the gtk platform. We then create a new binding + * that maps the command to the "Esc Ctrl+F" key sequence. + *

+ *
+ *     KeyBinding("Ctrl+Shift+F",null,scheme,context,null,gtk,null,SYSTEM)
+ *     KeyBinding("Esc Ctrl+F",parameterizedCommand,scheme,context,null,gtk,SYSTEM)
+ * 
+ *

+ * Bindings are intended to be immutable objects. + *

+ * + * @since 3.1 + */ +public abstract class Binding { + + /** + * The constant integer hash code value meaning the hash code has not yet + * been computed. + */ + private static const int HASH_CODE_NOT_COMPUTED = -1; + + /** + * A factor for computing the hash code for all key bindings. + */ + private final const int HASH_FACTOR = 89; + + /** + * The type of binding that is defined by the system (i.e., by the + * application developer). In the case of an application based on the + * Eclipse workbench, this is the registry. + */ + public static const int SYSTEM = 0; + + /** + * The type of binding that is defined by the user (i.e., by the end user of + * the application). In the case of an application based on the Eclipse + * workbench, this is the preference store. + */ + public static const int USER = 1; + + /** + * The parameterized command to which this binding applies. This value may + * be null if this binding is meant to "unbind" an existing + * binding. + */ + private const ParameterizedCommand command; + + /** + * The context identifier to which this binding applies. This context must + * be active before this key binding becomes active. This value will never + * be null. + */ + private const String contextId; + + /** + * The hash code for this key binding. This value is computed lazily, and + * marked as invalid when one of the values on which it is based changes. + */ + private /+transient+/ int hashCode = HASH_CODE_NOT_COMPUTED; + + /** + * The locale in which this binding applies. This value may be + * null if this binding is meant to apply to all locales. + * This string should be in the same format returned by + * Locale.getDefault().toString(). + */ + private const String locale; + + /** + * The platform on which this binding applies. This value may be + * null if this binding is meant to apply to all platforms. + * This string should be in the same format returned by + * DWT.getPlatform. + */ + private const String platform; + + /** + * The identifier of the scheme in which this binding applies. This value + * will never be null. + */ + private const String schemeId; + + /** + * The string representation of this binding. This string is for debugging + * purposes only, and is not meant to be displayed to the user. This value + * is computed lazily. + */ + protected /+transient+/ String string = null; + + /** + * The type of binding this represents. This is used to distinguish between + * different priority levels for bindings. For example, in our case, + * USER bindings override SYSTEM bindings. + */ + private const int type; + + /** + * Constructs a new instance of Binding. + * + * @param command + * The parameterized command to which this binding applies; this + * value may be null if the binding is meant to + * "unbind" a previously defined binding. + * @param schemeId + * The scheme to which this binding belongs; this value must not + * be null. + * @param contextId + * The context to which this binding applies; this value must not + * be null. + * @param locale + * The locale to which this binding applies; this value may be + * null if it applies to all locales. + * @param platform + * The platform to which this binding applies; this value may be + * null if it applies to all platforms. + * @param windowManager + * The window manager to which this binding applies; this value + * may be null if it applies to all window + * managers. This value is currently ignored. + * @param type + * The type of binding. This should be either SYSTEM + * or USER. + */ + protected this(ParameterizedCommand command, + String schemeId, String contextId, String locale, + String platform, String windowManager, int type) { + if (schemeId is null) { + throw new NullPointerException("The scheme cannot be null"); //$NON-NLS-1$ + } + + if (contextId is null) { + throw new NullPointerException("The context cannot be null"); //$NON-NLS-1$ + } + + if ((type !is SYSTEM) && (type !is USER)) { + throw new IllegalArgumentException( + "The type must be SYSTEM or USER"); //$NON-NLS-1$ + } + + this.command = command; + this.schemeId = schemeId.intern(); + this.contextId = contextId.intern(); + this.locale = (locale is null) ? null : locale.intern(); + this.platform = (platform is null) ? null : platform.intern(); + this.type = type; + } + + /** + * Tests whether this binding is intended to delete another binding. The + * receiver must have a null command identifier. + * + * @param binding + * The binding to test; must not be null. + * This binding must be a SYSTEM binding. + * @return true if the receiver deletes the binding defined by + * the argument. + */ + final bool deletes(Binding binding) { + bool deletes = true; + deletes &= Util.opEquals(getContextId(), binding.getContextId()); + deletes &= Util.opEquals(getTriggerSequence(), binding + .getTriggerSequence()); + if (getLocale() !is null) { + deletes &= !Util.opEquals(getLocale(), binding.getLocale()); + } + if (getPlatform() !is null) { + deletes &= !Util.opEquals(getPlatform(), binding.getPlatform()); + } + deletes &= (binding.getType() is SYSTEM); + deletes &= Util.opEquals(getParameterizedCommand(), null); + + return deletes; + } + + /** + * Tests whether this binding is equal to another object. Bindings are only + * equal to other bindings with equivalent values. + * + * @param object + * The object with which to compare; may be null. + * @return true if the object is a binding with equivalent + * values for all of its properties; false otherwise. + */ + public final override int opEquals( Object object) { + if (this is object) { + return true; + + } + if (!(cast(Binding)object )) { + return false; + } + + Binding binding = cast(Binding) object; + if (!Util.opEquals(getParameterizedCommand(), binding + .getParameterizedCommand())) { + return false; + } + if (!Util.opEquals(getContextId(), binding.getContextId())) { + return false; + } + if (!Util.opEquals(getTriggerSequence(), binding.getTriggerSequence())) { + return false; + } + if (!Util.opEquals(getLocale(), binding.getLocale())) { + return false; + } + if (!Util.opEquals(getPlatform(), binding.getPlatform())) { + return false; + } + if (!Util.opEquals(getSchemeId(), binding.getSchemeId())) { + return false; + } + return (getType() !is binding.getType()); + } + + /** + * Returns the parameterized command to which this binding applies. If the + * identifier is null, then this binding is "unbinding" an + * existing binding. + * + * @return The fully-parameterized command; may be null. + */ + public final ParameterizedCommand getParameterizedCommand() { + return command; + } + + /** + * Returns the identifier of the context in which this binding applies. + * + * @return The context identifier; never null. + */ + public final String getContextId() { + return contextId; + } + + /** + * Returns the locale in which this binding applies. If the locale is + * null, then this binding applies to all locales. This + * string is the same format as returned by + * Locale.getDefault().toString(). + * + * @return The locale; may be null. + */ + public final String getLocale() { + return locale; + } + + /** + * Returns the platform on which this binding applies. If the platform is + * null, then this binding applies to all platforms. This + * string is the same format as returned by DWT.getPlatform(). + * + * @return The platform; may be null. + */ + public final String getPlatform() { + return platform; + } + + /** + * Returns the identifier of the scheme in which this binding applies. + * + * @return The scheme identifier; never null. + */ + public final String getSchemeId() { + return schemeId; + } + + /** + * Returns the sequence of trigger for a given binding. The triggers can be + * anything, but above all it must be hashable. This trigger sequence is + * used by the binding manager to distinguish between different bindings. + * + * @return The object representing an input event that will trigger this + * binding; must not be null. + */ + public abstract TriggerSequence getTriggerSequence(); + + /** + * Returns the type for this binding. As it stands now, this value will + * either be SYSTEM or USER. In the future, + * more types might be added. + * + * @return The type for this binding. + */ + public final int getType() { + return type; + } + + /** + * Computes the hash code for this key binding based on all of its + * attributes. + * + * @return The hash code for this key binding. + */ + public final override hash_t toHash() { + if (hashCode is HASH_CODE_NOT_COMPUTED) { + auto HASH_INITIAL = dwt.dwthelper.utils.toHash(Binding.classinfo.name); + hashCode = HASH_INITIAL; + hashCode = hashCode * HASH_FACTOR + + Util.toHash(getParameterizedCommand()); + hashCode = hashCode * HASH_FACTOR + Util.toHash(getContextId()); + hashCode = hashCode * HASH_FACTOR + + Util.toHash(getTriggerSequence()); + hashCode = hashCode * HASH_FACTOR + Util.toHash(getLocale()); + hashCode = hashCode * HASH_FACTOR + Util.toHash(getPlatform()); + hashCode = hashCode * HASH_FACTOR + Util.toHash(getSchemeId()); + hashCode = hashCode * HASH_FACTOR + Util.toHash(getType()); + if (hashCode is HASH_CODE_NOT_COMPUTED) { + hashCode++; + } + } + + return hashCode; + } + + /** + * The string representation of this binding -- for debugging purposes only. + * This string should not be shown to an end user. This should be overridden + * by subclasses that add properties. + * + * @return The string representation; never null. + */ + public override String toString() { + if (string is null) { + string = Format("Binding({},\n\t{},\n\t{},\n\t{},{},{},{})", + getTriggerSequence().toString(), + command is null?"":command.toString(), + schemeId, + contextId, + locale is null?"":locale, + platform is null?"":platform, + (type is SYSTEM) ? "system" : "user"); + } + + return string; + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/BindingManager.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/BindingManager.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,2330 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 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 + *******************************************************************************/ +module dwtx.jface.bindings.BindingManager; + +import dwtx.jface.bindings.Binding; +import dwtx.jface.bindings.BindingManagerEvent; +import dwtx.jface.bindings.CachedBindingSet; +import dwtx.jface.bindings.IBindingManagerListener; +import dwtx.jface.bindings.ISchemeListener; +import dwtx.jface.bindings.Scheme; +import dwtx.jface.bindings.SchemeEvent; +import dwtx.jface.bindings.Trigger; +import dwtx.jface.bindings.TriggerSequence; + +// import java.io.BufferedWriter; +// import java.io.IOException; +// import java.io.StringWriter; +import tango.util.collection.HashMap; +import tango.util.collection.HashSet; +import tango.util.collection.ArraySeq; +import tango.util.collection.LinkSeq; +import tango.util.collection.model.Seq; +import tango.util.collection.model.Map; +import tango.util.collection.model.Set; +import tango.util.collection.model.View; + +import dwt.DWT; +import dwtx.core.commands.CommandManager; +import dwtx.core.commands.ParameterizedCommand; +import dwtx.core.commands.common.HandleObjectManager; +import dwtx.core.commands.common.NotDefinedException; +import dwtx.core.commands.contexts.Context; +import dwtx.core.commands.contexts.ContextManager; +import dwtx.core.commands.contexts.ContextManagerEvent; +import dwtx.core.commands.contexts.IContextManagerListener; +import dwtx.core.commands.util.Tracing; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.MultiStatus; +import dwtx.core.runtime.Status; +import dwtx.jface.bindings.keys.IKeyLookup; +import dwtx.jface.bindings.keys.KeyLookupFactory; +import dwtx.jface.bindings.keys.KeyStroke; +import dwtx.jface.contexts.IContextIds; +import dwtx.jface.internal.InternalPolicy; +import dwtx.jface.util.Policy; +import dwtx.jface.util.Util; + +import dwt.dwthelper.utils; +static import tango.text.Text; +alias tango.text.Text.Text!(char) StringBuffer; +import tango.text.convert.Format; +import tango.text.locale.Core; + +/** + *

+ * A central repository for bindings -- both in the defined and undefined + * states. Schemes and bindings can be created and retrieved using this manager. + * It is possible to listen to changes in the collection of schemes and bindings + * by adding a listener to the manager. + *

+ *

+ * The binding manager is very sensitive to performance. Misusing the manager + * can render an application unenjoyable to use. As such, each of the public + * methods states the current run-time performance. In future releases, it is + * guaranteed that the method will run in at least the stated time constraint -- + * though it might get faster. Where possible, we have also tried to be memory + * efficient. + *

+ * + * @since 3.1 + */ +public final class BindingManager : HandleObjectManager, + IContextManagerListener, ISchemeListener { + + private static Map!(Object,Object) EMPTY_MAP; + /** + * This flag can be set to true if the binding manager should + * print information to System.out when certain boundary + * conditions occur. + */ + public static bool DEBUG = false; + + /** + * Returned for optimized lookup. + */ + private static const TriggerSequence[] EMPTY_TRIGGER_SEQUENCE = null; + + /** + * The separator character used in locales. + */ + private static const String LOCALE_SEPARATOR = "_"; //$NON-NLS-1$ + + /** + *

+ * A utility method for adding entries to a map. The map is checked for + * entries at the key. If such an entry exists, it is expected to be a + * Seq!(Object). The value is then appended to the collection. + * If no such entry exists, then a collection is created, and the value + * added to the collection. + *

+ * + * @param map + * The map to modify; if this value is null, then + * this method simply returns. + * @param key + * The key to look up in the map; may be null. + * @param value + * The value to look up in the map; may be null. + */ + private static final void addReverseLookup(Map!(Object,Object) map, Object key, + Object value) { + if (map is null) { + return; + } + + Object currentValue = map.get(key); + if (currentValue !is null) { + auto values = cast(Seq!(Object)) currentValue; + values.append(value); + } else { // currentValue is null + auto values = new ArraySeq!(Object); + values.append(value); + map.add(key, values); + } + } + + /** + *

+ * Takes a fully-specified string, and converts it into an array of + * increasingly less-specific strings. So, for example, "en_GB" would become + * ["en_GB", "en", "", null]. + *

+ *

+ * This method runs in linear time (O(n)) over the length of the string. + *

+ * + * @param string + * The string to break apart into its less specific components; + * should not be null. + * @param separator + * The separator that indicates a separation between a degrees of + * specificity; should not be null. + * @return An array of strings from the most specific (i.e., + * string) to the least specific (i.e., + * null). + */ + private static final String[] expand(String string, String separator) { + // Test for boundary conditions. + if (string is null || separator is null) { + return new String[0]; + } + + auto strings = new LinkSeq!(String); + auto stringBuffer = new StringBuffer(); + string = string.trim(); // remove whitespace + if (string.length > 0) { + + auto tokens = tango.text.Util.delimit(string, separator); + foreach( tok; tokens ){ + if (stringBuffer.length() > 0) { + stringBuffer.append(separator); + } + stringBuffer.append(tok.trim()); + strings.prepend(stringBuffer.toString()); + } + } + strings.append(Util.ZERO_LENGTH_STRING); + strings.append(cast(char[])null); + return strings.toArray(); + } + + /** + * The active bindings. This is a map of triggers ( + * TriggerSequence) to bindings (Binding). + * This value will only be null if the active bindings have + * not yet been computed. Otherwise, this value may be empty. + */ + private Map!(Object,Object) activeBindings = null; + + /** + * The active bindings indexed by fully-parameterized commands. This is a + * map of fully-parameterized commands (ParameterizedCommand) + * to triggers ( TriggerSequence). This value will only be + * null if the active bindings have not yet been computed. + * Otherwise, this value may be empty. + */ + private Map!(Object,Object) activeBindingsByParameterizedCommand = null; + + private Set!(Object) triggerConflicts; + + /** + * The scheme that is currently active. An active scheme is the one that is + * currently dictating which bindings will actually work. This value may be + * null if there is no active scheme. If the active scheme + * becomes undefined, then this should automatically revert to + * null. + */ + private Scheme activeScheme = null; + + /** + * The array of scheme identifiers, starting with the active scheme and + * moving up through its parents. This value may be null if + * there is no active scheme. + */ + private String[] activeSchemeIds = null; + + /** + * The number of bindings in the bindings array. + */ + private int bindingCount = 0; + + /** + * A cache of context IDs that weren't defined. + */ + private Set!(Object) bindingErrors; + + /** + * The array of all bindings currently handled by this manager. This array + * is the raw list of bindings, as provided to this manager. This value may + * be null if there are no bindings. The size of this array + * is not necessarily the number of bindings. + */ + private Binding[] bindings = null; + + /** + * A cache of the bindings previously computed by this manager. This value + * may be empty, but it is never null. This is a map of + * CachedBindingSet to CachedBindingSet. + */ + private Map!(Object,Object) cachedBindings; + + /** + * The command manager for this binding manager. This manager is only needed + * for the getActiveBindingsFor(String) method. This value is + * guaranteed to never be null. + */ + private const CommandManager commandManager; + + /** + * The context manager for this binding manager. For a binding manager to + * function, it needs to listen for changes to the contexts. This value is + * guaranteed to never be null. + */ + private const ContextManager contextManager; + + /** + * The locale for this manager. This defaults to the current locale. The + * value will never be null. + */ + private String locale; + + /** + * The array of locales, starting with the active locale and moving up + * through less specific representations of the locale. For example, + * ["en_US", "en", "", null]. This value will never be null. + */ + private String[] locales; + + /** + * The platform for this manager. This defaults to the current platform. The + * value will never be null. + */ + private String platform; + + /** + * The array of platforms, starting with the active platform and moving up + * through less specific representations of the platform. For example, + * ["gtk", "", null]. This value will never be null,/code>. + */ + private String[] platforms; + + /** + * A map of prefixes (TriggerSequence) to a map of + * available completions (possibly null, which means there + * is an exact match). The available completions is a map of trigger (TriggerSequence) + * to bindings (Binding). This value may be + * null if there is no existing solution. + */ + private Map!(Object,Object) prefixTable = null; + + /** + *

+ * Constructs a new instance of BindingManager. + *

+ *

+ * This method completes in amortized constant time (O(1)). + *

+ * + * @param contextManager + * The context manager that will support this binding manager. + * This value must not be null. + * @param commandManager + * The command manager that will support this binding manager. + * This value must not be null. + */ + public this(ContextManager contextManager, + CommandManager commandManager) { + triggerConflicts = new HashSet!(Object); + bindingErrors = new HashSet!(Object); + cachedBindings = new HashMap!(Object,Object); + locale = /+Locale+/Culture.current().toString(); + locales = expand(locale, LOCALE_SEPARATOR); + platform = DWT.getPlatform(); + platforms = expand(platform, Util.ZERO_LENGTH_STRING); + if (contextManager is null) { + throw new NullPointerException( + "A binding manager requires a context manager"); //$NON-NLS-1$ + } + + if (commandManager is null) { + throw new NullPointerException( + "A binding manager requires a command manager"); //$NON-NLS-1$ + } + + this.contextManager = contextManager; + contextManager.addContextManagerListener(this); + this.commandManager = commandManager; + } + + /** + *

+ * Adds a single new binding to the existing array of bindings. If the array + * is currently null, then a new array is created and this + * binding is added to it. This method does not detect duplicates. + *

+ *

+ * This method completes in amortized O(1). + *

+ * + * @param binding + * The binding to be added; must not be null. + */ + public final void addBinding(Binding binding) { + if (binding is null) { + throw new NullPointerException("Cannot add a null binding"); //$NON-NLS-1$ + } + + if (bindings is null) { + bindings = new Binding[1]; + } else if (bindingCount >= bindings.length) { + Binding[] oldBindings = bindings; + bindings = new Binding[oldBindings.length * 2]; + System.arraycopy(oldBindings, 0, bindings, 0, oldBindings.length); + } + bindings[bindingCount++] = binding; + clearCache(); + } + + /** + *

+ * Adds a listener to this binding manager. The listener will be notified + * when the set of defined schemes or bindings changes. This can be used to + * track the global appearance and disappearance of bindings. + *

+ *

+ * This method completes in amortized constant time (O(1)). + *

+ * + * @param listener + * The listener to attach; must not be null. + */ + public final void addBindingManagerListener( + IBindingManagerListener listener) { + addListenerObject(cast(Object)listener); + } + + /** + *

+ * Builds a prefix table look-up for a map of active bindings. + *

+ *

+ * This method takes O(mn), where m is the + * length of the trigger sequences and n is the number of + * bindings. + *

+ * + * @param activeBindings + * The map of triggers (TriggerSequence) to + * command ids (String) which are currently + * active. This value may be null if there are no + * active bindings, and it may be empty. It must not be + * null. + * @return A map of prefixes (TriggerSequence) to a map of + * available completions (possibly null, which means + * there is an exact match). The available completions is a map of + * trigger (TriggerSequence) to command identifier (String). + * This value will never be null, but may be empty. + */ + private final Map!(Object,Object) buildPrefixTable(Map!(Object,Object) activeBindings) { + auto prefixTable = new HashMap!(Object,Object); + foreach( k, v; activeBindings ){ + TriggerSequence triggerSequence = cast(TriggerSequence)k; + + // Add the perfect match. + if (!prefixTable.containsKey(triggerSequence)) { + prefixTable.add(triggerSequence, null); + } + + TriggerSequence[] prefixes = triggerSequence.getPrefixes(); + int prefixesLength = prefixes.length; + if (prefixesLength is 0) { + continue; + } + + // Break apart the trigger sequence. + Binding binding = cast(Binding) v; + for (int i = 0; i < prefixesLength; i++) { + TriggerSequence prefix = prefixes[i]; + Object value = prefixTable.get(prefix); + if ((prefixTable.containsKey(prefix)) && (cast(Map!(Object,Object))value )) { + (cast(Map!(Object,Object)) value).add(triggerSequence, binding); + } else { + auto map = new HashMap!(Object,Object); + prefixTable.add(prefix, map); + map.add(triggerSequence, binding); + } + } + } + + return prefixTable; + } + + /** + *

+ * Clears the cache, and the existing solution. If debugging is turned on, + * then this will also print a message to standard out. + *

+ *

+ * This method completes in O(1). + *

+ */ + private final void clearCache() { + if (DEBUG) { + Tracing.printTrace("BINDINGS", "Clearing cache"); //$NON-NLS-1$ //$NON-NLS-2$ + } + cachedBindings.clear(); + clearSolution(); + } + + /** + *

+ * Clears the existing solution. + *

+ *

+ * This method completes in O(1). + */ + private final void clearSolution() { + setActiveBindings(null, null, null, null); + } + + /** + * Compares the identifier of two schemes, and decides which scheme is the + * youngest (i.e., the child) of the two. Both schemes should be active + * schemes. + * + * @param schemeId1 + * The identifier of the first scheme; must not be + * null. + * @param schemeId2 + * The identifier of the second scheme; must not be + * null. + * @return 0 if the two schemes are equal of if neither + * scheme is active; 1 if the second scheme is the + * youngest; and -1 if the first scheme is the + * youngest. + * @since 3.2 + */ + private final int compareSchemes(String schemeId1, + String schemeId2) { + if (!schemeId2.equals(schemeId1)) { + for (int i = 0; i < activeSchemeIds.length; i++) { + String schemePointer = activeSchemeIds[i]; + if (schemeId2.equals(schemePointer)) { + return 1; + + } else if (schemeId1.equals(schemePointer)) { + return -1; + + } + + } + } + + return 0; + } + + /** + *

+ * Computes the bindings given the context tree, and inserts them into the + * commandIdsByTrigger. It is assumed that + * locales,platforsm and + * schemeIds correctly reflect the state of the application. + * This method does not deal with caching. + *

+ *

+ * This method completes in O(n), where n is + * the number of bindings. + *

+ * + * @param activeContextTree + * The map representing the tree of active contexts. The map is + * one of child to parent, each being a context id ( + * String). The keys are never null, + * but the values may be (i.e., no parent). This map may be + * empty. It may be null if we shouldn't consider + * contexts. + * @param bindingsByTrigger + * The empty of map that is intended to be filled with triggers ( + * TriggerSequence) to bindings ( + * Binding). This value must not be + * null and must be empty. + * @param triggersByCommandId + * The empty of map that is intended to be filled with command + * identifiers (String) to triggers ( + * TriggerSequence). This value must either be + * null (indicating that these values are not + * needed), or empty (indicating that this map should be + * computed). + */ + private final void computeBindings(Map!(Object,Object) activeContextTree, + Map!(Object,Object) bindingsByTrigger, Map!(Object,Object) triggersByCommandId, + Map!(Object,Object) conflictsByTrigger) { + /* + * FIRST PASS: Remove all of the bindings that are marking deletions. + */ + Binding[] trimmedBindings = removeDeletions(bindings); + + /* + * SECOND PASS: Just throw in bindings that match the current state. If + * there is more than one match for a binding, then create a list. + */ + auto possibleBindings = new HashMap!(Object,Object); + int length = trimmedBindings.length; + for (int i = 0; i < length; i++) { + Binding binding = trimmedBindings[i]; + bool found; + + // Check the context. + String contextId = binding.getContextId(); + if ((activeContextTree !is null) + && (!activeContextTree.containsKey( new ArrayWrapperString(contextId)))) { + continue; + } + + // Check the locale. + if (!localeMatches(binding)) { + continue; + } + + // Check the platform. + if (!platformMatches(binding)) { + continue; + } + + // Check the scheme ids. + String schemeId = binding.getSchemeId(); + found = false; + if (activeSchemeIds !is null) { + for (int j = 0; j < activeSchemeIds.length; j++) { + if (Util.opEquals(schemeId, activeSchemeIds[j])) { + found = true; + break; + } + } + } + if (!found) { + continue; + } + + // Insert the match into the list of possible matches. + TriggerSequence trigger = binding.getTriggerSequence(); + Object existingMatch = possibleBindings.get(trigger); + if (cast(Binding)existingMatch ) { + possibleBindings.remove(trigger); + auto matches = new ArraySeq!(Object); + matches.append(existingMatch); + matches.append(binding); + possibleBindings.add(trigger, matches); + + } else if (cast(Seq!(Object))existingMatch ) { + auto matches = cast(Seq!(Object)) existingMatch; + matches.append(binding); + + } else { + possibleBindings.add(trigger, binding); + } + } + + MultiStatus conflicts = new MultiStatus("dwtx.jface", 0, //$NON-NLS-1$ + "Keybinding conflicts occurred. They may interfere with normal accelerator operation.", //$NON-NLS-1$ + null); + /* + * THIRD PASS: In this pass, we move any non-conflicting bindings + * directly into the map. In the case of conflicts, we apply some + * further logic to try to resolve them. If the conflict can't be + * resolved, then we log the problem. + */ + foreach( k,v; possibleBindings ){ +// Iterator possibleBindingItr = possibleBindings.entrySet() +// .iterator(); +// while (possibleBindingItr.hasNext()) { +// Map.Entry entry = cast(Map.Entry) possibleBindingItr.next(); + TriggerSequence trigger = cast(TriggerSequence) k;//entry.getKey(); + Object match = v;//entry.getValue(); + /* + * What we do depends slightly on whether we are trying to build a + * list of all possible bindings (disregarding context), or a flat + * map given the currently active contexts. + */ + if (activeContextTree is null) { + // We are building the list of all possible bindings. + auto bindings = new ArraySeq!(Object); + if (cast(Binding)match ) { + bindings.append(match); + bindingsByTrigger.add(trigger, bindings); + addReverseLookup(triggersByCommandId, (cast(Binding) match) + .getParameterizedCommand(), trigger); + + } else if (cast(View!(Object))match ) { + bindings.append( (cast(View!(Object)) match).elements ); + bindingsByTrigger.add(trigger, bindings); + + foreach( e; bindings ){ +// Iterator matchItr = bindings.iterator(); +// while (matchItr.hasNext()) { + addReverseLookup(triggersByCommandId, + (cast(Binding) e) + .getParameterizedCommand(), trigger); + } + } + + } else { + // We are building the flat map of trigger to commands. + if (cast(Binding)match ) { + Binding binding = cast(Binding) match; + bindingsByTrigger.add(trigger, binding); + addReverseLookup(triggersByCommandId, binding + .getParameterizedCommand(), trigger); + + } else if (cast(Seq!(Object))match ) { + Binding winner = resolveConflicts(cast(Seq!(Object)) match, + activeContextTree); + if (winner is null) { + // warn once ... so as not to flood the logs + conflictsByTrigger.add(trigger, match); + if (!triggerConflicts.contains(trigger)) { + triggerConflicts.add(trigger); +// StringWriter sw = new StringWriter(); +// BufferedWriter buffer = new BufferedWriter(sw); + StringBuffer sb = new StringBuffer(); + try { + sb.append("A conflict occurred for "); //$NON-NLS-1$ + sb.append(trigger.toString()); + sb.append(':'); + foreach( e; cast(Seq!(Object)) match){ +// Iterator i = (cast(Seq!(Object)) match).iterator(); +// while (i.hasNext()) { + sb.append('\n'); + sb.append( e.toString() ); + } + } catch (IOException e) { + // we should not get this + } + conflicts.add(new Status(IStatus.WARNING, + "dwtx.jface", //$NON-NLS-1$ + sb.toString())); + } + if (DEBUG) { + Tracing.printTrace("BINDINGS", //$NON-NLS-1$ + "A conflict occurred for " ~ trigger.toString); //$NON-NLS-1$ + Tracing.printTrace("BINDINGS", " " ~ match.toString); //$NON-NLS-1$ //$NON-NLS-2$ + } + } else { + bindingsByTrigger.add(trigger, winner); + addReverseLookup(triggersByCommandId, winner + .getParameterizedCommand(), trigger); + } + } + } + } + if (conflicts.getSeverity() !is IStatus.OK) { + Policy.getLog().log(conflicts); + } + } + + /** + *

+ * Notifies this manager that the context manager has changed. This method + * is intended for internal use only. + *

+ *

+ * This method completes in O(1). + *

+ */ + public final void contextManagerChanged( + ContextManagerEvent contextManagerEvent) { + if (contextManagerEvent.isActiveContextsChanged()) { +// clearSolution(); + recomputeBindings(); + } + } + + /** + * Returns the number of strokes in an array of triggers. It is assumed that + * there is one natural key per trigger. The strokes are counted based on + * the type of key. Natural keys are worth one; ctrl is worth two; shift is + * worth four; and alt is worth eight. + * + * @param triggers + * The triggers on which to count strokes; must not be + * null. + * @return The value of the strokes in the triggers. + * @since 3.2 + */ + private final int countStrokes(Trigger[] triggers) { + int strokeCount = triggers.length; + for (int i = 0; i < triggers.length; i++) { + Trigger trigger = triggers[i]; + if (cast(KeyStroke)trigger ) { + KeyStroke keyStroke = cast(KeyStroke) trigger; + int modifierKeys = keyStroke.getModifierKeys(); + IKeyLookup lookup = KeyLookupFactory.getDefault(); + if ((modifierKeys & lookup.getAlt()) !is 0) { + strokeCount += 8; + } + if ((modifierKeys & lookup.getCtrl()) !is 0) { + strokeCount += 2; + } + if ((modifierKeys & lookup.getShift()) !is 0) { + strokeCount += 4; + } + if ((modifierKeys & lookup.getCommand()) !is 0) { + strokeCount += 2; + } + } else { + strokeCount += 99; + } + } + + return strokeCount; + } + + /** + *

+ * Creates a tree of context identifiers, representing the hierarchical + * structure of the given contexts. The tree is structured as a mapping from + * child to parent. + *

+ *

+ * This method completes in O(n), where n is + * the height of the context tree. + *

+ * + * @param contextIds + * The set of context identifiers to be converted into a tree; + * must not be null. + * @return The tree of contexts to use; may be empty, but never + * null. The keys and values are both strings. + */ + private final Map!(Object,Object) createContextTreeFor(Set!(Object) contextIds) { + auto contextTree = new HashMap!(Object,Object); + + foreach( e; contextIds ){ + auto childContextId = (cast(ArrayWrapperString)e).array; + while (childContextId !is null) { + // Check if we've already got the part of the tree from here up. + if (contextTree.containsKey(/+childContextId+/e)) { + break; + } + + // Retrieve the context. + Context childContext = contextManager + .getContext(childContextId); + + // Add the child-parent pair to the tree. + try { + String parentContextId = childContext.getParentId(); + contextTree.add(new ArrayWrapperString(childContextId), new ArrayWrapperString(parentContextId)); + childContextId = parentContextId; + } catch (NotDefinedException e) { + break; // stop ascending + } + } + } + + return contextTree; + } + + /** + *

+ * Creates a tree of context identifiers, representing the hierarchical + * structure of the given contexts. The tree is structured as a mapping from + * child to parent. In this tree, the key binding specific filtering of + * contexts will have taken place. + *

+ *

+ * This method completes in O(n^2), where n + * is the height of the context tree. + *

+ * + * @param contextIds + * The set of context identifiers to be converted into a tree; + * must not be null. + * @return The tree of contexts to use; may be empty, but never + * null. The keys and values are both strings. + */ + private final Map!(Object,Object) createFilteredContextTreeFor(Set!(Object) contextIds) { + // Check to see whether a dialog or window is active. + bool dialog = false; + bool window = false; + foreach( e; contextIds ){ + String contextId = (cast(ArrayWrapperString) e).array; + if (IContextIds.CONTEXT_ID_DIALOG.equals(contextId)) { + dialog = true; + continue; + } + if (IContextIds.CONTEXT_ID_WINDOW.equals(contextId)) { + window = true; + continue; + } + } + + /* + * Remove all context identifiers for contexts whose parents are dialog + * or window, and the corresponding dialog or window context is not + * active. + */ + foreach( e; contextIds.dup ){ + String contextId = (cast(ArrayWrapperString) e).array; + Context context = contextManager.getContext(contextId); + try { + String parentId = context.getParentId(); + while (parentId !is null) { + if (IContextIds.CONTEXT_ID_DIALOG.equals(parentId)) { + if (!dialog) { + contextIds.remove(e); +// contextIdItr.remove(); + } + break; + } + if (IContextIds.CONTEXT_ID_WINDOW.equals(parentId)) { + if (!window) { + contextIds.remove(e); +// contextIdItr.remove(); + } + break; + } + if (IContextIds.CONTEXT_ID_DIALOG_AND_WINDOW + .equals(parentId)) { + if ((!window) && (!dialog)) { + contextIds.remove(e); +// contextIdItr.remove(); + } + break; + } + + context = contextManager.getContext(parentId); + parentId = context.getParentId(); + } + } catch (NotDefinedException e) { + // since this context was part of an undefined hierarchy, + // I'm going to yank it out as a bad bet + contextIds.remove(e); +// contextIdItr.remove(); + + // This is a logging optimization, only log the error once. + if (context is null || !bindingErrors.contains(new ArrayWrapperString(context.getId()))) { + if (context !is null) { + bindingErrors.add(new ArrayWrapperString(context.getId())); + } + + // now log like you've never logged before! + Policy.getLog().log(new Status( IStatus.ERROR, Policy.JFACE, IStatus.OK, + "Undefined context while filtering dialog/window contexts", //$NON-NLS-1$ + e)); + } + } + } + + return createContextTreeFor(contextIds); + } + + /** + *

+ * Notifies all of the listeners to this manager that the defined or active + * schemes of bindings have changed. + *

+ *

+ * The time this method takes to complete is dependent on external + * listeners. + *

+ * + * @param event + * The event to send to all of the listeners; must not be + * null. + */ + private final void fireBindingManagerChanged(BindingManagerEvent event) { + if (event is null) { + throw new NullPointerException(); + } + + Object[] listeners = getListeners(); + for (int i = 0; i < listeners.length; i++) { + IBindingManagerListener listener = cast(IBindingManagerListener) listeners[i]; + listener.bindingManagerChanged(event); + } + } + + /** + *

+ * Returns the active bindings. The caller must not modify the returned map. + *

+ *

+ * This method completes in O(1). If the active bindings are + * not yet computed, then this completes in O(nn), where + * n is the number of bindings. + *

+ * + * @return The map of triggers (TriggerSequence) to + * bindings (Binding) which are currently active. + * This value may be null if there are no active + * bindings, and it may be empty. + */ + private final Map!(Object,Object) getActiveBindings() { + if (activeBindings is null) { + recomputeBindings(); + } + + return activeBindings; + } + + /** + *

+ * Returns the active bindings indexed by command identifier. The caller + * must not modify the returned map. + *

+ *

+ * This method completes in O(1). If the active bindings are + * not yet computed, then this completes in O(nn), where + * n is the number of bindings. + *

+ * + * @return The map of fully-parameterized commands (ParameterizedCommand) + * to triggers (TriggerSequence) which are + * currently active. This value may be null if there + * are no active bindings, and it may be empty. + */ + private final Map!(Object,Object) getActiveBindingsByParameterizedCommand() { + if (activeBindingsByParameterizedCommand is null) { + recomputeBindings(); + } + + return activeBindingsByParameterizedCommand; + } + + /** + *

+ * Computes the bindings for the current state of the application, but + * disregarding the current contexts. This can be useful when trying to + * display all the possible bindings. + *

+ *

+ * This method completes in O(n), where n is + * the number of bindings. + *

+ * + * @return A map of trigger (TriggerSequence) to bindings ( + * Seq!(Object) containing Binding). + * This map may be empty, but it is never null. + */ + public final Map!(Object,Object) getActiveBindingsDisregardingContext() { + if (bindings is null) { + // Not yet initialized. This is happening too early. Do nothing. + if( EMPTY_MAP is null ) EMPTY_MAP = new HashMap!(Object,Object); + return EMPTY_MAP; + } + + // Build a cached binding set for that state. + CachedBindingSet bindingCache = new CachedBindingSet(null, + locales, platforms, activeSchemeIds); + + /* + * Check if the cached binding set already exists. If so, simply set the + * active bindings and return. + */ + CachedBindingSet existingCache = cast(CachedBindingSet) cachedBindings + .get(bindingCache); + if (existingCache is null) { + existingCache = bindingCache; + cachedBindings.add(existingCache, existingCache); + } + auto commandIdsByTrigger = existingCache.getBindingsByTrigger(); + if (commandIdsByTrigger !is null) { + if (DEBUG) { + Tracing.printTrace("BINDINGS", "Cache hit"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + return commandIdsByTrigger; + } + + // There is no cached entry for this. + if (DEBUG) { + Tracing.printTrace("BINDINGS", "Cache miss"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + // Compute the active bindings. + commandIdsByTrigger = new HashMap!(Object,Object); + auto triggersByParameterizedCommand = new HashMap!(Object,Object); + auto conflictsByTrigger = new HashMap!(Object,Object); + computeBindings(null, commandIdsByTrigger, + triggersByParameterizedCommand, conflictsByTrigger); + existingCache.setBindingsByTrigger(commandIdsByTrigger); + existingCache.setTriggersByCommandId(triggersByParameterizedCommand); + existingCache.setConflictsByTrigger(conflictsByTrigger); + return /+Collections.unmodifiableMap(+/commandIdsByTrigger; + } + + /** + *

+ * Computes the bindings for the current state of the application, but + * disregarding the current contexts. This can be useful when trying to + * display all the possible bindings. + *

+ *

+ * This method completes in O(n), where n is + * the number of bindings. + *

+ * + * @return A map of trigger (TriggerSequence) to bindings ( + * Seq!(Object) containing Binding). + * This map may be empty, but it is never null. + * @since 3.2 + */ + private final Map!(Object,Object) getActiveBindingsDisregardingContextByParameterizedCommand() { + if (bindings is null) { + // Not yet initialized. This is happening too early. Do nothing. + if( EMPTY_MAP is null ) EMPTY_MAP = new HashMap!(Object,Object); + return EMPTY_MAP; + } + + // Build a cached binding set for that state. + CachedBindingSet bindingCache = new CachedBindingSet(null, + locales, platforms, activeSchemeIds); + + /* + * Check if the cached binding set already exists. If so, simply set the + * active bindings and return. + */ + CachedBindingSet existingCache = cast(CachedBindingSet) cachedBindings + .get(bindingCache); + if (existingCache is null) { + existingCache = bindingCache; + cachedBindings.add(existingCache, existingCache); + } + auto triggersByParameterizedCommand = existingCache + .getTriggersByCommandId(); + if (triggersByParameterizedCommand !is null) { + if (DEBUG) { + Tracing.printTrace("BINDINGS", "Cache hit"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + return /+Collections.unmodifiableMap(+/triggersByParameterizedCommand; + } + + // There is no cached entry for this. + if (DEBUG) { + Tracing.printTrace("BINDINGS", "Cache miss"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + // Compute the active bindings. + auto commandIdsByTrigger = new HashMap!(Object,Object); + auto conflictsByTrigger = new HashMap!(Object,Object); + triggersByParameterizedCommand = new HashMap!(Object,Object); + computeBindings(null, commandIdsByTrigger, + triggersByParameterizedCommand, conflictsByTrigger); + existingCache.setBindingsByTrigger(commandIdsByTrigger); + existingCache.setTriggersByCommandId(triggersByParameterizedCommand); + existingCache.setConflictsByTrigger(conflictsByTrigger); + + return /+Collections.unmodifiableMap(+/triggersByParameterizedCommand; + } + + /** + *

+ * Computes the bindings for the current state of the application, but + * disregarding the current contexts. This can be useful when trying to + * display all the possible bindings. + *

+ *

+ * This method completes in O(n), where n is + * the number of bindings. + *

+ * + * @return All of the active bindings (Binding), not sorted + * in any fashion. This collection may be empty, but it is never + * null. + */ + public final View!(Object) getActiveBindingsDisregardingContextFlat() { + auto mergedBindings = new ArraySeq!(Object); + + foreach( k,v; getActiveBindingsDisregardingContext() ){ + auto bindingCollection = cast(View!(Object))v; + if ((bindingCollection !is null) && (!bindingCollection.drained())) { + foreach( e; bindingCollection ){ + mergedBindings.append(e); + } + } + } + + return mergedBindings; + } + + /** + *

+ * Returns the active bindings for a particular command identifier, but + * discounting the current contexts. This method operates in O(n) time over + * the number of bindings. + *

+ *

+ * This method completes in O(1). If the active bindings are + * not yet computed, then this completes in O(nn), where + * n is the number of bindings. + *

+ * + * @param parameterizedCommand + * The fully-parameterized command whose bindings are requested. + * This argument may be null. + * @return The array of active triggers (TriggerSequence) + * for a particular command identifier. This value is guaranteed to + * never be null, but it may be empty. + * @since 3.2 + */ + public final TriggerSequence[] getActiveBindingsDisregardingContextFor( + ParameterizedCommand parameterizedCommand) { + Object object = getActiveBindingsDisregardingContextByParameterizedCommand() + .get(parameterizedCommand); + if (auto collection = cast(Seq!(Object))object ) { + return arraycast!(TriggerSequence)(collection.toArray()); + } + + return EMPTY_TRIGGER_SEQUENCE; + } + + /** + *

+ * Returns the active bindings for a particular command identifier. This + * method operates in O(n) time over the number of bindings. + *

+ *

+ * This method completes in O(1). If the active bindings are + * not yet computed, then this completes in O(nn), where + * n is the number of bindings. + *

+ * + * @param parameterizedCommand + * The fully-parameterized command whose bindings are requested. + * This argument may be null. + * @return The array of active triggers (TriggerSequence) + * for a particular command identifier. This value is guaranteed to + * never be null, but it may be empty. + */ + public final TriggerSequence[] getActiveBindingsFor( + ParameterizedCommand parameterizedCommand) { + Object object = getActiveBindingsByParameterizedCommand().get( + parameterizedCommand); + if ( auto collection = cast(Seq!(Object))object ) { + return arraycast!(TriggerSequence)(collection.toArray()); + } + + return EMPTY_TRIGGER_SEQUENCE; + } + + /** + *

+ * Returns the active bindings for a particular command identifier. This + * method operates in O(n) time over the number of bindings. + *

+ *

+ * This method completes in O(1). If the active bindings are + * not yet computed, then this completes in O(nn), where + * n is the number of bindings. + *

+ * + * @param commandId + * The identifier of the command whose bindings are requested. + * This argument may be null. It is assumed that + * the command has no parameters. + * @return The array of active triggers (TriggerSequence) + * for a particular command identifier. This value is guaranteed not + * to be null, but it may be empty. + */ + public final TriggerSequence[] getActiveBindingsFor(String commandId) { + ParameterizedCommand parameterizedCommand = new ParameterizedCommand( + commandManager.getCommand(commandId), null); + Object object = getActiveBindingsByParameterizedCommand().get( + parameterizedCommand); + if ( auto collection = cast(Seq!(Object))object ) { + return arraycast!(TriggerSequence)(collection.toArray()); + } + + return EMPTY_TRIGGER_SEQUENCE; + } + + /** + * A variation on {@link BindingManager#getActiveBindingsFor(String)} that + * returns an array of bindings, rather than trigger sequences. This method + * is needed for doing "best" calculations on the active bindings. + * + * @param commandId + * The identifier of the command for which the active bindings + * should be retrieved; must not be null. + * @return The active bindings for the given command; this value may be + * null if there are no active bindings. + * @since 3.2 + */ + private final Binding[] getActiveBindingsFor1(String commandId) { + TriggerSequence[] triggers = getActiveBindingsFor(commandId); + if (triggers.length is 0) { + return null; + } + + auto activeBindings = getActiveBindings(); + if (activeBindings !is null) { + Binding[] bindings = new Binding[triggers.length]; + for (int i = 0; i < triggers.length; i++) { + TriggerSequence triggerSequence = triggers[i]; + Object object = activeBindings.get(triggerSequence); + Binding binding = cast(Binding) object; + bindings[i] = binding; + } + return bindings; + } + + return null; + } + + /** + *

+ * Gets the currently active scheme. + *

+ *

+ * This method completes in O(1). + *

+ * + * @return The active scheme; may be null if there is no + * active scheme. If a scheme is returned, it is guaranteed to be + * defined. + */ + public final Scheme getActiveScheme() { + return activeScheme; + } + + /** + * Gets the best active binding for a command. The best binding is the one + * that would be most appropriate to show in a menu. Bindings which belong + * to a child scheme are given preference over those in a parent scheme. + * Bindings which belong to a particular locale or platform are given + * preference over those that do not. The rest of the calculaton is based + * most on various concepts of "length", as well as giving some modifier + * keys preference (e.g., Alt is less likely to appear than + * Ctrl). + * + * @param commandId + * The identifier of the command for which the best active + * binding should be retrieved; must not be null. + * @return The trigger sequence for the best binding; may be + * null if no bindings are active for the given + * command. + * @since 3.2 + */ + public final TriggerSequence getBestActiveBindingFor(String commandId) { + Binding[] bindings = getActiveBindingsFor1(commandId); + if ((bindings is null) || (bindings.length is 0)) { + return null; + } + + Binding bestBinding = bindings[0]; + int compareTo; + for (int i = 1; i < bindings.length; i++) { + Binding currentBinding = bindings[i]; + + // Bindings in a child scheme are always given preference. + String bestSchemeId = bestBinding.getSchemeId(); + String currentSchemeId = currentBinding.getSchemeId(); + compareTo = compareSchemes(bestSchemeId, currentSchemeId); + if (compareTo > 0) { + bestBinding = currentBinding; + } + if (compareTo !is 0) { + continue; + } + + /* + * Bindings with a locale are given preference over those that do + * not. + */ + String bestLocale = bestBinding.getLocale(); + String currentLocale = currentBinding.getLocale(); + if ((bestLocale is null) && (currentLocale !is null)) { + bestBinding = currentBinding; + } + if (!(Util.opEquals(bestLocale, currentLocale))) { + continue; + } + + /* + * Bindings with a platform are given preference over those that do + * not. + */ + String bestPlatform = bestBinding.getPlatform(); + String currentPlatform = currentBinding.getPlatform(); + if ((bestPlatform is null) && (currentPlatform !is null)) { + bestBinding = currentBinding; + } + if (!(Util.opEquals(bestPlatform, currentPlatform))) { + continue; + } + + /* + * Check to see which has the least number of triggers in the + * trigger sequence. + */ + TriggerSequence bestTriggerSequence = bestBinding + .getTriggerSequence(); + TriggerSequence currentTriggerSequence = currentBinding + .getTriggerSequence(); + Trigger[] bestTriggers = bestTriggerSequence.getTriggers(); + Trigger[] currentTriggers = currentTriggerSequence + .getTriggers(); + compareTo = bestTriggers.length - currentTriggers.length; + if (compareTo > 0) { + bestBinding = currentBinding; + } + if (compareTo !is 0) { + continue; + } + + /* + * Compare the number of keys pressed in each trigger sequence. Some + * types of keys count less than others (i.e., some types of + * modifiers keys are less likely to be chosen). + */ + compareTo = countStrokes(bestTriggers) + - countStrokes(currentTriggers); + if (compareTo > 0) { + bestBinding = currentBinding; + } + if (compareTo !is 0) { + continue; + } + + // If this is still a tie, then just chose the shortest text. + compareTo = bestTriggerSequence.format().length + - currentTriggerSequence.format().length; + if (compareTo > 0) { + bestBinding = currentBinding; + } + } + + return bestBinding.getTriggerSequence(); + } + + /** + * Gets the formatted string representing the best active binding for a + * command. The best binding is the one that would be most appropriate to + * show in a menu. Bindings which belong to a child scheme are given + * preference over those in a parent scheme. The rest of the calculaton is + * based most on various concepts of "length", as well as giving some + * modifier keys preference (e.g., Alt is less likely to + * appear than Ctrl). + * + * @param commandId + * The identifier of the command for which the best active + * binding should be retrieved; must not be null. + * @return The formatted string for the best binding; may be + * null if no bindings are active for the given + * command. + * @since 3.2 + */ + public final String getBestActiveBindingFormattedFor(String commandId) { + TriggerSequence binding = getBestActiveBindingFor(commandId); + if (binding !is null) { + return binding.format(); + } + + return null; + } + + /** + *

+ * Returns the set of all bindings managed by this class. + *

+ *

+ * This method completes in O(1). + *

+ * + * @return The array of all bindings. This value may be null + * and it may be empty. + */ + public final Binding[] getBindings() { + if (bindings is null) { + return null; + } + + Binding[] returnValue = new Binding[bindingCount]; + System.arraycopy(bindings, 0, returnValue, 0, bindingCount); + return returnValue; + } + + /** + *

+ * Returns the array of schemes that are defined. + *

+ *

+ * This method completes in O(1). + *

+ * + * @return The array of defined schemes; this value may be empty or + * null. + */ + public final Scheme[] getDefinedSchemes() { + return arraycast!(Scheme)(definedHandleObjects.toArray()); + } + + /** + *

+ * Returns the active locale for this binding manager. The locale is in the + * same format as Locale.getDefault().toString(). + *

+ *

+ * This method completes in O(1). + *

+ * + * @return The active locale; never null. + */ + public final String getLocale() { + return locale; + } + + /** + *

+ * Returns all of the possible bindings that start with the given trigger + * (but are not equal to the given trigger). + *

+ *

+ * This method completes in O(1). If the bindings aren't + * currently computed, then this completes in O(n), where + * n is the number of bindings. + *

+ * + * @param trigger + * The prefix to look for; must not be null. + * @return A map of triggers (TriggerSequence) to bindings (Binding). + * This map may be empty, but it is never null. + */ + public final Map!(Object,Object) getPartialMatches(TriggerSequence trigger) { + auto partialMatches = cast(Map!(Object,Object)) getPrefixTable().get(trigger); + if (partialMatches is null) { + if( EMPTY_MAP is null ) EMPTY_MAP = new HashMap!(Object,Object); + return EMPTY_MAP; + } + + return partialMatches; + } + + /** + *

+ * Returns the command identifier for the active binding matching this + * trigger, if any. + *

+ *

+ * This method completes in O(1). If the bindings aren't + * currently computed, then this completes in O(n), where + * n is the number of bindings. + *

+ * + * @param trigger + * The trigger to match; may be null. + * @return The binding that matches, if any; null otherwise. + */ + public final Binding getPerfectMatch(TriggerSequence trigger) { + return cast(Binding) getActiveBindings().get(trigger); + } + + /** + *

+ * Returns the active platform for this binding manager. The platform is in + * the same format as DWT.getPlatform(). + *

+ *

+ * This method completes in O(1). + *

+ * + * @return The active platform; never null. + */ + public final String getPlatform() { + return platform; + } + + /** + *

+ * Returns the prefix table. The caller must not modify the returned map. + *

+ *

+ * This method completes in O(1). If the active bindings are + * not yet computed, then this completes in O(n), where + * n is the number of bindings. + *

+ * + * @return A map of prefixes (TriggerSequence) to a map of + * available completions (possibly null, which means + * there is an exact match). The available completions is a map of + * trigger (TriggerSequence) to binding (Binding). + * This value will never be null but may be empty. + */ + private final Map!(Object,Object) getPrefixTable() { + if (prefixTable is null) { + recomputeBindings(); + } + + return prefixTable; + } + + /** + *

+ * Gets the scheme with the given identifier. If the scheme does not already + * exist, then a new (undefined) scheme is created with that identifier. + * This guarantees that schemes will remain unique. + *

+ *

+ * This method completes in amortized O(1). + *

+ * + * @param schemeId + * The identifier for the scheme to retrieve; must not be + * null. + * @return A scheme with the given identifier. + */ + public final Scheme getScheme(String schemeId) { + checkId(schemeId); + + Scheme scheme = cast(Scheme) handleObjectsById.get(schemeId); + if (scheme is null) { + scheme = new Scheme(schemeId); + handleObjectsById.add(schemeId, scheme); + scheme.addSchemeListener(this); + } + + return scheme; + } + + /** + *

+ * Ascends all of the parents of the scheme until no more parents are found. + *

+ *

+ * This method completes in O(n), where n is + * the height of the context tree. + *

+ * + * @param schemeId + * The id of the scheme for which the parents should be found; + * may be null. + * @return The array of scheme ids (String) starting with + * schemeId and then ascending through its ancestors. + */ + private final String[] getSchemeIds(String schemeId) { + auto strings = new ArraySeq!(Object); + while (schemeId !is null) { + strings.append( stringcast(schemeId)); + try { + schemeId = getScheme(schemeId).getParentId(); + } catch (NotDefinedException e) { + Policy.getLog().log( new Status( + IStatus.ERROR, Policy.JFACE, IStatus.OK, + "Failed ascending scheme parents", //$NON-NLS-1$ + e)); + return null; + } + } + + return stringcast(strings.toArray()); + } + + /** + *

+ * Returns whether the given trigger sequence is a partial match for the + * given sequence. + *

+ *

+ * This method completes in O(1). If the bindings aren't + * currently computed, then this completes in O(n), where + * n is the number of bindings. + *

+ * + * @param trigger + * The sequence which should be the prefix for some binding; + * should not be null. + * @return true if the trigger can be found in the active + * bindings; false otherwise. + */ + public final bool isPartialMatch(TriggerSequence trigger) { + return (getPrefixTable().get(trigger) !is null); + } + + /** + *

+ * Returns whether the given trigger sequence is a perfect match for the + * given sequence. + *

+ *

+ * This method completes in O(1). If the bindings aren't + * currently computed, then this completes in O(n), where + * n is the number of bindings. + *

+ * + * @param trigger + * The sequence which should match exactly; should not be + * null. + * @return true if the trigger can be found in the active + * bindings; false otherwise. + */ + public final bool isPerfectMatch(TriggerSequence trigger) { + return getActiveBindings().containsKey(trigger); + } + + /** + *

+ * Tests whether the locale for the binding matches one of the active + * locales. + *

+ *

+ * This method completes in O(n), where n is + * the number of active locales. + *

+ * + * @param binding + * The binding with which to test; must not be null. + * @return true if the binding's locale matches; + * false otherwise. + */ + private final bool localeMatches(Binding binding) { + bool matches = false; + + String locale = binding.getLocale(); + if (locale is null) { + return true; // shortcut a common case + } + + for (int i = 0; i < locales.length; i++) { + if (Util.opEquals(locales[i], locale)) { + matches = true; + break; + } + } + + return matches; + } + + /** + *

+ * Tests whether the platform for the binding matches one of the active + * platforms. + *

+ *

+ * This method completes in O(n), where n is + * the number of active platforms. + *

+ * + * @param binding + * The binding with which to test; must not be null. + * @return true if the binding's platform matches; + * false otherwise. + */ + private final bool platformMatches(Binding binding) { + bool matches = false; + + String platform = binding.getPlatform(); + if (platform is null) { + return true; // shortcut a common case + } + + for (int i = 0; i < platforms.length; i++) { + if (Util.opEquals(platforms[i], platform)) { + matches = true; + break; + } + } + + return matches; + } + + /** + *

+ * This recomputes the bindings based on changes to the state of the world. + * This computation can be triggered by changes to contexts, the active + * scheme, the locale, or the platform. This method tries to use the cache + * of pre-computed bindings, if possible. When this method completes, + * activeBindings will be set to the current set of bindings + * and cachedBindings will contain an instance of + * CachedBindingSet representing these bindings. + *

+ *

+ * This method completes in O(n+pn), where n + * is the number of bindings, and p is the average number of + * triggers in a trigger sequence. + *

+ */ + private final void recomputeBindings() { + if (bindings is null) { + // Not yet initialized. This is happening too early. Do nothing. + if( EMPTY_MAP is null ) EMPTY_MAP = new HashMap!(Object,Object); + setActiveBindings(EMPTY_MAP, EMPTY_MAP, + EMPTY_MAP, EMPTY_MAP); + return; + } + + // Figure out the current state. + auto activeContextIds = new HashSet!(Object); + foreach( e; contextManager.getActiveContextIds()){ + activeContextIds.add(stringcast(e)); + } + auto activeContextTree = createFilteredContextTreeFor(activeContextIds); + + // Build a cached binding set for that state. + CachedBindingSet bindingCache = new CachedBindingSet( + activeContextTree, locales, platforms, activeSchemeIds); + + /* + * Check if the cached binding set already exists. If so, simply set the + * active bindings and return. + */ + CachedBindingSet existingCache = cast(CachedBindingSet) cachedBindings + .get(bindingCache); + if (existingCache is null) { + existingCache = bindingCache; + cachedBindings.add(existingCache, existingCache); + } + auto commandIdsByTrigger = existingCache.getBindingsByTrigger(); + if (commandIdsByTrigger !is null) { + if (DEBUG) { + Tracing.printTrace("BINDINGS", "Cache hit"); //$NON-NLS-1$ //$NON-NLS-2$ + } + setActiveBindings(commandIdsByTrigger, existingCache + .getTriggersByCommandId(), existingCache.getPrefixTable(), + existingCache.getConflictsByTrigger()); + return; + } + + // There is no cached entry for this. + if (DEBUG) { + Tracing.printTrace("BINDINGS", "Cache miss"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + // Compute the active bindings. + commandIdsByTrigger = new HashMap!(Object,Object); + auto triggersByParameterizedCommand = new HashMap!(Object,Object); + auto conflictsByTrigger = new HashMap!(Object,Object); + computeBindings(activeContextTree, commandIdsByTrigger, + triggersByParameterizedCommand, conflictsByTrigger); + existingCache.setBindingsByTrigger(commandIdsByTrigger); + existingCache.setTriggersByCommandId(triggersByParameterizedCommand); + existingCache.setConflictsByTrigger(conflictsByTrigger); + setActiveBindings(commandIdsByTrigger, triggersByParameterizedCommand, + buildPrefixTable(commandIdsByTrigger), + conflictsByTrigger); + existingCache.setPrefixTable(prefixTable); + } + + /** + *

+ * Remove the specific binding by identity. Does nothing if the binding is + * not in the manager. + *

+ *

+ * This method completes in O(n), where n is + * the number of bindings. + *

+ * + * @param binding + * The binding to be removed; must not be null. + * @since 3.2 + */ + public final void removeBinding(Binding binding) { + if (bindings is null || bindings.length < 1) { + return; + } + + Binding[] newBindings = new Binding[bindings.length]; + bool bindingsChanged = false; + int index = 0; + for (int i = 0; i < bindingCount; i++) { + Binding b = bindings[i]; + if (b is binding) { + bindingsChanged = true; + } else { + newBindings[index++] = b; + } + } + + if (bindingsChanged) { + this.bindings = newBindings; + bindingCount = index; + clearCache(); + } + } + + /** + *

+ * Removes a listener from this binding manager. + *

+ *

+ * This method completes in amortized O(1). + *

+ * + * @param listener + * The listener to be removed; must not be null. + */ + public final void removeBindingManagerListener( + IBindingManagerListener listener) { + removeListenerObject(cast(Object)listener); + } + + /** + *

+ * Removes any binding that matches the given values -- regardless of + * command identifier. + *

+ *

+ * This method completes in O(n), where n is + * the number of bindings. + *

+ * + * @param sequence + * The sequence to match; may be null. + * @param schemeId + * The scheme id to match; may be null. + * @param contextId + * The context id to match; may be null. + * @param locale + * The locale to match; may be null. + * @param platform + * The platform to match; may be null. + * @param windowManager + * The window manager to match; may be null. TODO + * Currently ignored. + * @param type + * The type to look for. + * + */ + public final void removeBindings(TriggerSequence sequence, + String schemeId, String contextId, String locale, + String platform, String windowManager, int type) { + if ((bindings is null) || (bindingCount < 1)) { + return; + } + + Binding[] newBindings = new Binding[bindings.length]; + bool bindingsChanged = false; + int index = 0; + for (int i = 0; i < bindingCount; i++) { + Binding binding = bindings[i]; + bool equals = true; + equals &= Util.opEquals(sequence, binding.getTriggerSequence()); + equals &= Util.opEquals(schemeId, binding.getSchemeId()); + equals &= Util.opEquals(contextId, binding.getContextId()); + equals &= Util.opEquals(locale, binding.getLocale()); + equals &= Util.opEquals(platform, binding.getPlatform()); + equals &= (type is binding.getType()); + if (equals) { + bindingsChanged = true; + } else { + newBindings[index++] = binding; + } + } + + if (bindingsChanged) { + this.bindings = newBindings; + bindingCount = index; + clearCache(); + } + } + + /** + *

+ * Attempts to remove deletion markers from the collection of bindings. + *

+ *

+ * This method completes in O(n), where n is + * the number of bindings. + *

+ * + * @param bindings + * The bindings from which the deleted items should be removed. + * This array should not be null, but may be + * empty. + * @return The array of bindings with the deletions removed; never + * null, but may be empty. Contains only instances + * of Binding. + */ + private final Binding[] removeDeletions(Binding[] bindings) { + auto deletions = new HashMap!(Object,Object); + Binding[] bindingsCopy = new Binding[bindingCount]; + System.arraycopy(bindings, 0, bindingsCopy, 0, bindingCount); + int deletedCount = 0; + + // Extract the deletions. + for (int i = 0; i < bindingCount; i++) { + Binding binding = bindingsCopy[i]; + if ((binding.getParameterizedCommand() is null) + && (localeMatches(binding)) && (platformMatches(binding))) { + TriggerSequence sequence = binding.getTriggerSequence(); + Object currentValue = deletions.get(sequence); + if (cast(Binding)currentValue ) { + auto collection = new ArraySeq!(Object); + collection.append(currentValue); + collection.append(binding); + deletions.add(sequence, collection); + } else if ( auto collection = cast(Seq!(Object))currentValue ) { + collection.append(binding); + } else { + deletions.add(sequence, binding); + } + bindingsCopy[i] = null; + deletedCount++; + } + } + + if (DEBUG) { + Tracing.printTrace("BINDINGS", Format("There are {} deletion markers", deletions.size()) //$NON-NLS-1$ //$NON-NLS-2$ + ); //$NON-NLS-1$ + } + + // Remove the deleted items. + for (int i = 0; i < bindingCount; i++) { + Binding binding = bindingsCopy[i]; + if (binding !is null) { + Object deletion = deletions.get(binding + .getTriggerSequence()); + if (cast(Binding)deletion ) { + if ((cast(Binding) deletion).deletes(binding)) { + bindingsCopy[i] = null; + deletedCount++; + } + + } else if (cast(Seq!(Object))deletion ) { + Seq!(Object) collection = cast(Seq!(Object)) deletion; + foreach( e; collection){ +// Iterator iterator = collection.iterator(); +// while (iterator.hasNext()) { + Object deletionBinding = e;//iterator.next(); + if (cast(Binding)deletionBinding ) { + if ((cast(Binding) deletionBinding).deletes(binding)) { + bindingsCopy[i] = null; + deletedCount++; + break; + } + } + } + + } + } + } + + // Compact the array. + Binding[] returnValue = new Binding[bindingCount - deletedCount]; + int index = 0; + for (int i = 0; i < bindingCount; i++) { + Binding binding = bindingsCopy[i]; + if (binding !is null) { + returnValue[index++] = binding; + } + } + + return returnValue; + } + + /** + *

+ * Attempts to resolve the conflicts for the given bindings. + *

+ *

+ * This method completes in O(n), where n is + * the number of bindings. + *

+ * + * @param bindings + * The bindings which all match the same trigger sequence; must + * not be null, and should contain at least two + * items. This collection should only contain instances of + * Binding (i.e., no null values). + * @param activeContextTree + * The tree of contexts to be used for all of the comparison. All + * of the keys should be active context identifiers (i.e., never + * null). The values will be their parents (i.e., + * possibly null). Both keys and values are + * context identifiers (String). This map should + * never be empty, and must never be null. + * @return The binding which best matches the current state. If there is a + * tie, then return null. + */ + private final Binding resolveConflicts(View!(Object) bindings, + Map!(Object,Object) activeContextTree) { + /* + * This flag is used to indicate when the bestMatch binding conflicts + * with another binding. We keep the best match binding so that we know + * if we find a better binding. However, if we don't find a better + * binding, then we known to return null. + */ + bool conflict = false; + +// Iterator bindingItr = bindings.iterator(); + Binding bestMatch; + bool first = true; + foreach( b; bindings ){ + if( first ){ + first = false; + bestMatch = cast(Binding) b;//bindingItr.next(); + continue; + } + + /* + * Iterate over each binding and compare it with the best match. If a + * better match is found, then replace the best match and set the + * conflict flag to false. If a conflict is found, then leave the best + * match and set the conflict flag. Otherwise, just continue. + */ +// while (bindingItr.hasNext()) { + Binding current = cast(Binding) b;//bindingItr.next(); + + /* + * SCHEME: Test whether the current is in a child scheme. Bindings + * defined in a child scheme will always take priority over bindings + * defined in a parent scheme. + */ + String currentSchemeId = current.getSchemeId(); + String bestSchemeId = bestMatch.getSchemeId(); + int compareTo = compareSchemes(bestSchemeId, currentSchemeId); + if (compareTo > 0) { + bestMatch = current; + conflict = false; + } + if (compareTo !is 0) { + continue; + } + + /* + * CONTEXTS: Check for context superiority. Bindings defined in a + * child context will take priority over bindings defined in a + * parent context -- assuming that the schemes lead to a conflict. + */ + String currentContext = current.getContextId(); + String bestContext = bestMatch.getContextId(); + if (!currentContext.equals(bestContext)) { + bool goToNextBinding = false; + + // Ascend the current's context tree. + String contextPointer = currentContext; + while (contextPointer !is null) { + if (contextPointer.equals(bestContext)) { + // the current wins + bestMatch = current; + conflict = false; + goToNextBinding = true; + break; + } + contextPointer = stringcast(activeContextTree + .get(stringcast(contextPointer))); + } + + // Ascend the best match's context tree. + contextPointer = bestContext; + while (contextPointer !is null) { + if (contextPointer.equals(currentContext)) { + // the best wins + goToNextBinding = true; + break; + } + contextPointer = stringcast( activeContextTree + .get(stringcast(contextPointer))); + } + + if (goToNextBinding) { + continue; + } + } + + /* + * TYPE: Test for type superiority. + */ + if (current.getType() > bestMatch.getType()) { + bestMatch = current; + conflict = false; + continue; + } else if (bestMatch.getType() > current.getType()) { + continue; + } + + // We could not resolve the conflict between these two. + conflict = true; + } + + // If the best match represents a conflict, then return null. + if (conflict) { + return null; + } + + // Otherwise, we have a winner.... + return bestMatch; + } + + /** + *

+ * Notifies this manager that a scheme has changed. This method is intended + * for internal use only. + *

+ *

+ * This method calls out to listeners, and so the time it takes to complete + * is dependent on third-party code. + *

+ * + * @param schemeEvent + * An event describing the change in the scheme. + */ + public final void schemeChanged(SchemeEvent schemeEvent) { + if (schemeEvent.isDefinedChanged()) { + Scheme scheme = schemeEvent.getScheme(); + bool schemeIdAdded = scheme.isDefined(); + bool activeSchemeChanged = false; + if (schemeIdAdded) { + definedHandleObjects.add(scheme); + } else { + definedHandleObjects.remove(scheme); + + if (activeScheme is scheme) { + activeScheme = null; + activeSchemeIds = null; + activeSchemeChanged = true; + + // Clear the binding solution. + clearSolution(); + } + } + + if (isListenerAttached()) { + fireBindingManagerChanged(new BindingManagerEvent(this, false, + null, activeSchemeChanged, scheme, schemeIdAdded, + false, false)); + } + } + } + + /** + * Sets the active bindings and the prefix table. This ensures that the two + * values change at the same time, and that any listeners are notified + * appropriately. + * + * @param activeBindings + * This is a map of triggers ( TriggerSequence) + * to bindings (Binding). This value will only + * be null if the active bindings have not yet + * been computed. Otherwise, this value may be empty. + * @param activeBindingsByCommandId + * This is a map of fully-parameterized commands (ParameterizedCommand) + * to triggers ( TriggerSequence). This value + * will only be null if the active bindings have + * not yet been computed. Otherwise, this value may be empty. + * @param prefixTable + * A map of prefixes (TriggerSequence) to a map + * of available completions (possibly null, which + * means there is an exact match). The available completions is a + * map of trigger (TriggerSequence) to binding (Binding). + * This value may be null if there is no existing + * solution. + */ + private final void setActiveBindings(Map!(Object,Object) activeBindings, + Map!(Object,Object) activeBindingsByCommandId, Map!(Object,Object) prefixTable, + Map!(Object,Object) conflicts) { + this.activeBindings = activeBindings; + Map!(Object,Object) previousBindingsByParameterizedCommand = this.activeBindingsByParameterizedCommand; + this.activeBindingsByParameterizedCommand = activeBindingsByCommandId; + this.prefixTable = prefixTable; + InternalPolicy.currentConflicts = conflicts; + + fireBindingManagerChanged(new BindingManagerEvent(this, true, + previousBindingsByParameterizedCommand, false, null, false, + false, false)); + } + + /** + *

+ * Selects one of the schemes as the active scheme. This scheme must be + * defined. + *

+ *

+ * This method completes in O(n), where n is + * the height of the context tree. + *

+ * + * @param scheme + * The scheme to become active; must not be null. + * @throws NotDefinedException + * If the given scheme is currently undefined. + */ + public final void setActiveScheme(Scheme scheme) { + if (scheme is null) { + throw new NullPointerException("Cannot activate a null scheme"); //$NON-NLS-1$ + } + + if ((scheme is null) || (!scheme.isDefined())) { + throw new NotDefinedException( + "Cannot activate an undefined scheme. " //$NON-NLS-1$ + ~ scheme.getId()); + } + + if (Util.opEquals(activeScheme, scheme)) { + return; + } + + activeScheme = scheme; + activeSchemeIds = getSchemeIds(activeScheme.getId()); + clearSolution(); + fireBindingManagerChanged(new BindingManagerEvent(this, false, null, + true, null, false, false, false)); + } + + /** + *

+ * Changes the set of bindings for this binding manager. Changing the set of + * bindings all at once ensures that: (1) duplicates are removed; and (2) + * avoids unnecessary intermediate computations. This method clears the + * existing bindings, but does not trigger a recomputation (other method + * calls are required to do that). + *

+ *

+ * This method completes in O(n), where n is + * the number of bindings. + *

+ * + * @param bindings + * The new array of bindings; may be null. This + * set is copied into a local data structure. + */ + public final void setBindings(Binding[] bindings) { + if (Arrays.equals(this.bindings, bindings)) { + return; // nothing has changed + } + + if ((bindings is null) || (bindings.length is 0)) { + this.bindings = null; + bindingCount = 0; + } else { + int bindingsLength = bindings.length; + this.bindings = new Binding[bindingsLength]; + System.arraycopy(bindings, 0, this.bindings, 0, bindingsLength); + bindingCount = bindingsLength; + } + clearCache(); + } + + /** + *

+ * Changes the locale for this binding manager. The locale can be used to + * provide locale-specific bindings. If the locale is different than the + * current locale, this will force a recomputation of the bindings. The + * locale is in the same format as + * Locale.getDefault().toString(). + *

+ *

+ * This method completes in O(1). + *

+ * + * @param locale + * The new locale; must not be null. + * @see Locale#getDefault() + */ + public final void setLocale(String locale) { + if (locale is null) { + throw new NullPointerException("The locale cannot be null"); //$NON-NLS-1$ + } + + if (!Util.opEquals(this.locale, locale)) { + this.locale = locale; + this.locales = expand(locale, LOCALE_SEPARATOR); + clearSolution(); + fireBindingManagerChanged(new BindingManagerEvent(this, false, + null, false, null, false, true, false)); + } + } + + /** + *

+ * Changes the platform for this binding manager. The platform can be used + * to provide platform-specific bindings. If the platform is different than + * the current platform, then this will force a recomputation of the + * bindings. The locale is in the same format as + * DWT.getPlatform(). + *

+ *

+ * This method completes in O(1). + *

+ * + * @param platform + * The new platform; must not be null. + * @see dwt.DWT#getPlatform() + */ + public final void setPlatform(String platform) { + if (platform is null) { + throw new NullPointerException("The platform cannot be null"); //$NON-NLS-1$ + } + + if (!Util.opEquals(this.platform, platform)) { + this.platform = platform; + this.platforms = expand(platform, Util.ZERO_LENGTH_STRING); + clearSolution(); + fireBindingManagerChanged(new BindingManagerEvent(this, false, + null, false, null, false, false, true)); + } + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/BindingManagerEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/BindingManagerEvent.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,256 @@ +/******************************************************************************* + * Copyright (c) 2004, 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 + *******************************************************************************/ + +module dwtx.jface.bindings.BindingManagerEvent; + +import dwtx.jface.bindings.BindingManager; +import dwtx.jface.bindings.Scheme; +import dwtx.jface.bindings.TriggerSequence; + +// import tango.util.collection.Seq!(Object); +import tango.util.collection.model.Map; +import tango.util.collection.model.Seq; + +import dwtx.core.commands.ParameterizedCommand; +import dwtx.core.commands.common.AbstractBitSetEvent; +import dwtx.jface.util.Util; + +import dwt.dwthelper.utils; + +/** + * An instance of this class describes changes to an instance of + * BindingManager. + *

+ * This class is not intended to be extended by clients. + *

+ * + * @since 3.1 + * @see IBindingManagerListener#bindingManagerChanged(BindingManagerEvent) + */ +public final class BindingManagerEvent : AbstractBitSetEvent { + + /** + * The bit used to represent whether the map of active bindings has changed. + */ + private static const int CHANGED_ACTIVE_BINDINGS = 1; + + /** + * The bit used to represent whether the active scheme has changed. + */ + private static const int CHANGED_ACTIVE_SCHEME = 1 << 1; + + /** + * The bit used to represent whether the active locale has changed. + */ + private static const int CHANGED_LOCALE = 1 << 2; + + /** + * The bit used to represent whether the active platform has changed. + */ + private static const int CHANGED_PLATFORM = 1 << 3; + + /** + * The bit used to represent whether the scheme's defined state has changed. + */ + private static const int CHANGED_SCHEME_DEFINED = 1 << 4; + + /** + * The binding manager that has changed; this value is never + * null. + */ + private const BindingManager manager; + + /** + * The map of triggers (Seq!(Object) of + * TriggerSequence) by parameterized command (ParameterizedCommand) + * before the change occurred. This map may be empty and it may be + * null. + */ + private const Map!(Object,Object) previousTriggersByParameterizedCommand; + + /** + * The scheme that became defined or undefined. This value may be + * null if no scheme changed its defined state. + */ + private const Scheme scheme; + + /** + * Creates a new instance of this class. + * + * @param manager + * the instance of the binding manager that changed; must not be + * null. + * @param activeBindingsChanged + * Whether the active bindings have changed. + * @param previousTriggersByParameterizedCommand + * The map of triggers (TriggerSequence) by + * fully-parameterized command (ParameterizedCommand) + * before the change occured. This map may be null + * or empty. + * @param activeSchemeChanged + * true, iff the active scheme changed. + * @param scheme + * The scheme that became defined or undefined; null + * if no scheme changed state. + * @param schemeDefined + * true if the given scheme became defined; + * false otherwise. + * @param localeChanged + * true iff the active locale changed + * @param platformChanged + * true iff the active platform changed + */ + public this(BindingManager manager, + bool activeBindingsChanged, + Map!(Object,Object) previousTriggersByParameterizedCommand, + bool activeSchemeChanged, Scheme scheme, + bool schemeDefined, bool localeChanged, + bool platformChanged) { + if (manager is null) { + throw new NullPointerException( + "A binding manager event needs a binding manager"); //$NON-NLS-1$ + } + this.manager = manager; + + if (schemeDefined && (scheme is null)) { + throw new NullPointerException( + "If a scheme changed defined state, then there should be a scheme identifier"); //$NON-NLS-1$ + } + this.scheme = scheme; + + this.previousTriggersByParameterizedCommand = previousTriggersByParameterizedCommand; + + if (activeBindingsChanged) { + changedValues |= CHANGED_ACTIVE_BINDINGS; + } + if (activeSchemeChanged) { + changedValues |= CHANGED_ACTIVE_SCHEME; + } + if (localeChanged) { + changedValues |= CHANGED_LOCALE; + } + if (platformChanged) { + changedValues |= CHANGED_PLATFORM; + } + if (schemeDefined) { + changedValues |= CHANGED_SCHEME_DEFINED; + } + } + + /** + * Returns the instance of the manager that changed. + * + * @return the instance of the manager that changed. Guaranteed not to be + * null. + */ + public final BindingManager getManager() { + return manager; + } + + /** + * Returns the scheme that changed. + * + * @return The changed scheme + */ + public final Scheme getScheme() { + return scheme; + } + + /** + * Returns whether the active bindings have changed. + * + * @return true if the active bindings have changed; + * false otherwise. + */ + public final bool isActiveBindingsChanged() { + return ((changedValues & CHANGED_ACTIVE_BINDINGS) !is 0); + } + + /** + * Computes whether the active bindings have changed for a given command + * identifier. + * + * @param parameterizedCommand + * The fully-parameterized command whose bindings might have + * changed; must not be null. + * @return true if the active bindings have changed for the + * given command identifier; false otherwise. + */ + public final bool isActiveBindingsChangedFor( + ParameterizedCommand parameterizedCommand) { + TriggerSequence[] currentBindings = manager + .getActiveBindingsFor(parameterizedCommand); + TriggerSequence[] previousBindings; + if (previousTriggersByParameterizedCommand !is null) { + Seq!(Object) previousBindingCollection = cast(Seq!(Object)) previousTriggersByParameterizedCommand + .get(parameterizedCommand); + if (previousBindingCollection is null) { + previousBindings = null; + } else { + previousBindings = cast(TriggerSequence[])previousBindingCollection.toArray(); + } + } else { + previousBindings = null; + } + + return !Util.opEquals(currentBindings, previousBindings); + } + + /** + * Returns whether or not the active scheme changed. + * + * @return true, iff the active scheme property changed. + */ + public bool isActiveSchemeChanged() { + return ((changedValues & CHANGED_ACTIVE_SCHEME) !is 0); + } + + /** + * Returns whether the locale has changed + * + * @return true if the locale changed; false + * otherwise. + */ + public bool isLocaleChanged() { + return ((changedValues & CHANGED_LOCALE) !is 0); + } + + /** + * Returns whether the platform has changed + * + * @return true if the platform changed; false + * otherwise. + */ + public bool isPlatformChanged() { + return ((changedValues & CHANGED_PLATFORM) !is 0); + } + + /** + * Returns whether the list of defined scheme identifiers has changed. + * + * @return true if the list of scheme identifiers has + * changed; false otherwise. + */ + public final bool isSchemeChanged() { + return (scheme !is null); + } + + /** + * Returns whether or not the scheme became defined + * + * @return true if the scheme became defined. + */ + public final bool isSchemeDefined() { + return (((changedValues & CHANGED_SCHEME_DEFINED) !is 0) && (scheme !is null)); + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/CachedBindingSet.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/CachedBindingSet.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,372 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 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 + *******************************************************************************/ +module dwtx.jface.bindings.CachedBindingSet; + +import dwtx.jface.bindings.TriggerSequence; +import dwtx.jface.bindings.Binding; + +import tango.util.collection.model.Map; + +import dwtx.jface.util.Util; + +import dwt.dwthelper.utils; + +/** + *

+ * A resolution of bindings for a given state. To see if we already have a + * cached binding set, just create one of these binding sets and then look it up + * in a map. If it is not already there, then add it and set the cached binding + * resolution. + *

+ * + * @since 3.1 + */ +final class CachedBindingSet { + + /** + * A factor for computing the hash code for all cached binding sets. + */ + private const static int HASH_FACTOR = 89; + + /** + *

+ * A representation of the tree of active contexts at the time this cached + * binding set was computed. It is a map of context id (String) + * to context id (String). Each key represents one of the + * active contexts or one of its ancestors, while each value represents its + * parent. This is a way of perserving information about what the hierarchy + * looked like. + *

+ *

+ * This value will be null if the contexts were disregarded + * in the computation. It may also be empty. All of the keys are guaranteed + * to be non- null, but the values can be null + * (i.e., no parent). + *

+ */ + private const Map!(Object,Object) activeContextTree; + + /** + * The map representing the resolved state of the bindings. This is a map of + * a trigger (TriggerSequence) to binding (Binding). + * This value may be null if it has not yet been initialized. + */ + private Map!(Object,Object) bindingsByTrigger = null; + + /** + * A map of triggers to collections of bindings. If this binding set + * contains conflicts, they are logged here. + * + * @since 3.3 + */ + private Map!(Object,Object) conflictsByTrigger = null; + + /** + * The hash code for this object. This value is computed lazily, and marked + * as invalid when one of the values on which it is based changes. + */ + private /+transient+/ int hashCode; + + /** + * Whether hashCode still contains a valid value. + */ + private /+transient+/ bool hashCodeComputed = false; + + /** + *

+ * The list of locales that were active at the time this binding set was + * computed. This list starts with the most specific representation of the + * locale, and moves to more general representations. For example, this + * array might look like ["en_US", "en", "", null]. + *

+ *

+ * This value will never be null, and it will never be + * empty. It must contain at least one element, but its elements can be + * null. + *

+ */ + private const String[] locales; + + /** + *

+ * The list of platforms that were active at the time this binding set was + * computed. This list starts with the most specific representation of the + * platform, and moves to more general representations. For example, this + * array might look like ["gtk", "", null]. + *

+ *

+ * This value will never be null, and it will never be + * empty. It must contain at least one element, but its elements can be + * null. + *

+ */ + private const String[] platforms; + + /** + * A map of prefixes (TriggerSequence) to a map of + * available completions (possibly null, which means there + * is an exact match). The available completions is a map of trigger (TriggerSequence) + * to command identifier (String). This value is + * null if it has not yet been initialized. + */ + private Map!(Object,Object) prefixTable = null; + + /** + *

+ * The list of schemes that were active at the time this binding set was + * computed. This list starts with the active scheme, and then continues + * with all of its ancestors -- in order. For example, this might look like + * ["emacs", "default"]. + *

+ *

+ * This value will never be null, and it will never be + * empty. It must contain at least one element. Its elements cannot be + * null. + *

+ */ + private const String[] schemeIds; + + /** + * The map representing the resolved state of the bindings. This is a map of + * a command id (String) to triggers (Collection + * of TriggerSequence). This value may be null + * if it has not yet been initialized. + */ + private Map!(Object,Object) triggersByCommandId = null; + + /** + * Constructs a new instance of CachedBindingSet. + * + * @param activeContextTree + * The set of context identifiers that were active when this + * binding set was calculated; may be empty. If it is + * null, then the contexts were disregarded in + * the computation. This is a map of context id ( + * String) to parent context id ( + * String). This is a way of caching the look of + * the context tree at the time the binding set was computed. + * @param locales + * The locales that were active when this binding set was + * calculated. The first element is the currently active locale, + * and it is followed by increasingly more general locales. This + * must not be null and must contain at least one + * element. The elements can be null, though. + * @param platforms + * The platform that were active when this binding set was + * calculated. The first element is the currently active + * platform, and it is followed by increasingly more general + * platforms. This must not be null and must + * contain at least one element. The elements can be + * null, though. + * @param schemeIds + * The scheme that was active when this binding set was + * calculated, followed by its ancestors. This may be + * nullnull
. + */ + this(Map!(Object,Object) activeContextTree, String[] locales, + String[] platforms, String[] schemeIds) { + if (locales is null) { + throw new NullPointerException("The locales cannot be null."); //$NON-NLS-1$ + } + + if (locales.length is 0) { + throw new NullPointerException("The locales cannot be empty."); //$NON-NLS-1$ + } + + if (platforms is null) { + throw new NullPointerException("The platforms cannot be null."); //$NON-NLS-1$ + } + + if (platforms.length is 0) { + throw new NullPointerException("The platforms cannot be empty."); //$NON-NLS-1$ + } + + this.activeContextTree = activeContextTree; + this.locales = locales; + this.platforms = platforms; + this.schemeIds = schemeIds; + } + + /** + * Compares this binding set with another object. The objects will be equal + * if they are both instance of CachedBindingSet and have + * equivalent values for all of their properties. + * + * @param object + * The object with which to compare; may be null. + * @return true if they are both instances of + * CachedBindingSet and have the same values for all + * of their properties; false otherwise. + */ + public final override int opEquals(Object object) { + if (!(cast(CachedBindingSet)object )) { + return false; + } + + CachedBindingSet other = cast(CachedBindingSet) object; + + if (!Util.opEquals(cast(Object)activeContextTree, cast(Object)other.activeContextTree)) { + return false; + } + if (!Util.opEquals(locales, other.locales)) { + return false; + } + if (!Util.opEquals(platforms, other.platforms)) { + return false; + } + return Util.opEquals(schemeIds, other.schemeIds); + } + + /** + * Returns the map of command identifiers indexed by trigger sequence. + * + * @return A map of triggers (TriggerSequence) to bindings (Binding). + * This value may be null if this was not yet + * initialized. + */ + final Map!(Object,Object) getBindingsByTrigger() { + return bindingsByTrigger; + } + + /** + * Returns a map of conflicts for this set of contexts. + * + * @return A map of trigger to a collection of Bindings. May be + * null. + * @since 3.3 + */ + final Map!(Object,Object) getConflictsByTrigger() { + return conflictsByTrigger; + } + + /** + * Returns the map of prefixes to a map of trigger sequence to command + * identifiers. + * + * @return A map of prefixes (TriggerSequence) to a map of + * available completions (possibly null, which means + * there is an exact match). The available completions is a map of + * trigger (TriggerSequence) to command identifier (String). + * This value may be null if it has not yet been + * initialized. + */ + final Map!(Object,Object) getPrefixTable() { + return prefixTable; + } + + /** + * Returns the map of triggers indexed by command identifiers. + * + * @return A map of command identifiers (String) to + * triggers (Collection of + * TriggerSequence). This value may be + * null if this was not yet initialized. + */ + final Map!(Object,Object) getTriggersByCommandId() { + return triggersByCommandId; + } + + /** + * Computes the hash code for this cached binding set. The hash code is + * based only on the immutable values. This allows the set to be created and + * checked for in a hashed collection before doing any + * computation. + * + * @return The hash code for this cached binding set. + */ + public final override hash_t toHash() { + if (!hashCodeComputed) { + + auto HASH_INITIAL = dwt.dwthelper.utils.toHash(CachedBindingSet.classinfo.name ); + hashCode = HASH_INITIAL; + hashCode = hashCode * HASH_FACTOR + + Util.toHash(cast(Object)activeContextTree); + hashCode = hashCode * HASH_FACTOR + Util.toHash(locales); + hashCode = hashCode * HASH_FACTOR + Util.toHash(platforms); + hashCode = hashCode * HASH_FACTOR + Util.toHash(schemeIds); + hashCodeComputed = true; + } + + return hashCode; + } + + /** + * Sets the map of command identifiers indexed by trigger. + * + * @param commandIdsByTrigger + * The map to set; must not be null. This is a + * map of triggers (TriggerSequence) to binding (Binding). + */ + final void setBindingsByTrigger(Map!(Object,Object) commandIdsByTrigger) { + if (commandIdsByTrigger is null) { + throw new NullPointerException( + "Cannot set a null binding resolution"); //$NON-NLS-1$ + } + + this.bindingsByTrigger = commandIdsByTrigger; + } + + /** + * Sets the map of conflicting bindings by trigger. + * + * @param conflicts + * The map to set; must not be null. + * @since 3.3 + */ + final void setConflictsByTrigger(Map!(Object,Object) conflicts) { + if (conflicts is null) { + throw new NullPointerException( + "Cannot set a null binding conflicts"); //$NON-NLS-1$ + } + conflictsByTrigger = conflicts; + } + + /** + * Sets the map of prefixes to a map of trigger sequence to command + * identifiers. + * + * @param prefixTable + * A map of prefixes (TriggerSequence) to a map + * of available completions (possibly null, which + * means there is an exact match). The available completions is a + * map of trigger (TriggerSequence) to command + * identifier (String). Must not be + * null. + */ + final void setPrefixTable(Map!(Object,Object) prefixTable) { + if (prefixTable is null) { + throw new NullPointerException("Cannot set a null prefix table"); //$NON-NLS-1$ + } + + this.prefixTable = prefixTable; + } + + /** + * Sets the map of triggers indexed by command identifiers. + * + * @param triggersByCommandId + * The map to set; must not be null. This is a + * map of command identifiers (String) to + * triggers (Collection of + * TriggerSequence). + */ + final void setTriggersByCommandId(Map!(Object,Object) triggersByCommandId) { + if (triggersByCommandId is null) { + throw new NullPointerException( + "Cannot set a null binding resolution"); //$NON-NLS-1$ + } + + this.triggersByCommandId = triggersByCommandId; + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/IBindingManagerListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/IBindingManagerListener.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2004, 2005 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 + *******************************************************************************/ + +module dwtx.jface.bindings.IBindingManagerListener; + +import dwtx.jface.bindings.BindingManagerEvent; + +import dwt.dwthelper.utils; + +/** + *

+ * An instance of BindingManagerListener can be used by clients to + * receive notification of changes to an instance of + * BindingManager. + *

+ *

+ * This interface may be implemented by clients. + *

+ * + * @since 3.1 + * @see BindingManager#addBindingManagerListener(IBindingManagerListener) + * @see dwtx.jface.bindings.BindingManager#addBindingManagerListener(IBindingManagerListener) + * @see BindingManagerEvent + */ +public interface IBindingManagerListener { + + /** + * Notifies that attributes inside an instance of BindingManager have changed. + * Specific details are described in the BindingManagerEvent. Changes in the + * binding manager can cause the set of defined or active schemes or bindings to change. + * + * @param event + * the binding manager event. Guaranteed not to be null. + */ + void bindingManagerChanged(BindingManagerEvent event); +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/ISchemeListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/ISchemeListener.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2004, 2005 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 + *******************************************************************************/ + +module dwtx.jface.bindings.ISchemeListener; + +import dwtx.jface.bindings.SchemeEvent; + +import dwt.dwthelper.utils; + +/** + *

+ * An instance of ISchemeListener can be used by clients to + * receive notification of changes to one or more instances of + * IScheme. + *

+ *

+ * This interface may be implemented by clients. + *

+ * + * @since 3.1 + * @see Scheme#addSchemeListener(ISchemeListener) + * @see Scheme#removeSchemeListener(ISchemeListener) + * @see SchemeEvent + */ +public interface ISchemeListener { + + /** + * Notifies that one or more attributes of an instance of + * IScheme have changed. Specific details are described in + * the SchemeEvent. + * + * @param schemeEvent + * the scheme event. Guaranteed not to be null. + */ + void schemeChanged(SchemeEvent schemeEvent); +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/Scheme.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/Scheme.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,282 @@ +/******************************************************************************* + * Copyright (c) 2004, 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 + *******************************************************************************/ + +module dwtx.jface.bindings.Scheme; + +import dwtx.jface.bindings.ISchemeListener; +import dwtx.jface.bindings.SchemeEvent; + +import tango.util.collection.HashSet; +import tango.util.collection.model.Set; + +import dwtx.core.commands.common.NamedHandleObject; +import dwtx.core.commands.common.NotDefinedException; +import dwtx.jface.util.Util; + +import dwt.dwthelper.utils; +import tango.text.convert.Format; + +/** + *

+ * An instance of IScheme is a handle representing a binding + * scheme as defined by the extension point dwtx.ui.bindings. + * The identifier of the handle is the identifier of the scheme being represented. + *

+ *

+ * An instance of IScheme can be obtained from an instance of + * ICommandManager for any identifier, whether or not a scheme + * with that identifier is defined in the plugin registry. + *

+ *

+ * The handle-based nature of this API allows it to work well with runtime + * plugin activation and deactivation. If a scheme is defined, that means that + * its corresponding plug-in is active. If the plug-in is then deactivated, the + * scheme will still exist but it will be undefined. An attempt to use an + * undefined scheme will result in a NotDefinedException + * being thrown. + *

+ *

+ * This class is not intended to be extended by clients. + *

+ * + * @since 3.1 + * @see ISchemeListener + * @see dwtx.core.commands.CommandManager + */ +public final class Scheme : NamedHandleObject, Comparable { + + /** + * The collection of all objects listening to changes on this scheme. This + * value is null if there are no listeners. + */ + private Set!(ISchemeListener) listeners = null; + + /** + * The parent identifier for this scheme. This is the identifier of the + * scheme from which this scheme inherits some of its bindings. This value + * can be null if the scheme has no parent. + */ + private String parentId = null; + + /** + * Constructs a new instance of Scheme with an identifier. + * + * @param id + * The identifier to create; must not be null. + */ + this(String id) { + super(id); + } + + /** + * Registers an instance of ISchemeListener to listen for + * changes to attributes of this instance. + * + * @param schemeListener + * the instance of ISchemeListener to register. + * Must not be null. If an attempt is made to + * register an instance of ISchemeListener which + * is already registered with this instance, no operation is + * performed. + */ + public final void addSchemeListener(ISchemeListener schemeListener) { + if (schemeListener is null) { + throw new NullPointerException("Can't add a null scheme listener."); //$NON-NLS-1$ + } + + if (listeners is null) { + listeners = new HashSet!(ISchemeListener); + } + + listeners.add(schemeListener); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public final int compareTo(Object object) { + Scheme scheme = cast(Scheme) object; + int compareTo = Util.compare(this.id, scheme.id); + if (compareTo is 0) { + compareTo = Util.compare(this.name, scheme.name); + if (compareTo is 0) { + compareTo = Util.compare(this.parentId, scheme.parentId); + if (compareTo is 0) { + compareTo = Util.compare(this.description, + scheme.description); + if (compareTo is 0) { + compareTo = Util.compare(this.defined, scheme.defined); + } + } + } + } + + return compareTo; + } + + /** + *

+ * Defines this scheme by giving it a name, and possibly a description and a + * parent identifier as well. The defined property for the scheme automatically + * becomes true. + *

+ *

+ * Notification is sent to all listeners that something has changed. + *

+ * + * @param name + * The name of this scheme; must not be null. + * @param description + * The description for this scheme; may be null. + * @param parentId + * The parent identifier for this scheme; may be + * null. + */ + public final void define(String name, String description, + String parentId) { + if (name is null) { + throw new NullPointerException( + "The name of a scheme cannot be null"); //$NON-NLS-1$ + } + + bool definedChanged = !this.defined; + this.defined = true; + + bool nameChanged = !Util.opEquals(this.name, name); + this.name = name; + + bool descriptionChanged = !Util.opEquals(this.description, + description); + this.description = description; + + bool parentIdChanged = !Util.opEquals(this.parentId, parentId); + this.parentId = parentId; + + fireSchemeChanged(new SchemeEvent(this, definedChanged, nameChanged, + descriptionChanged, parentIdChanged)); + } + + /** + * Notifies all listeners that this scheme has changed. This sends the given + * event to all of the listeners, if any. + * + * @param event + * The event to send to the listeners; must not be + * null. + */ + private final void fireSchemeChanged(SchemeEvent event) { + if (event is null) { + throw new NullPointerException( + "Cannot send a null event to listeners."); //$NON-NLS-1$ + } + + if (listeners is null) { + return; + } + foreach( e; listeners ){ + ISchemeListener listener = cast(ISchemeListener)e; + listener.schemeChanged(event); + } + } + + /** + *

+ * Returns the identifier of the parent of the scheme represented by this + * handle. + *

+ *

+ * Notification is sent to all registered listeners if this attribute + * changes. + *

+ * + * @return the identifier of the parent of the scheme represented by this + * handle. May be null. + * @throws NotDefinedException + * if the scheme represented by this handle is not defined. + */ + public final String getParentId() { + if (!defined) { + throw new NotDefinedException( + "Cannot get the parent identifier from an undefined scheme. " //$NON-NLS-1$ + ~ id); + } + + return parentId; + } + + /** + * Unregisters an instance of ISchemeListener listening for + * changes to attributes of this instance. + * + * @param schemeListener + * the instance of ISchemeListener to unregister. + * Must not be null. If an attempt is made to + * unregister an instance of ISchemeListener which + * is not already registered with this instance, no operation is + * performed. + */ + public final void removeSchemeListener(ISchemeListener schemeListener) { + if (schemeListener is null) { + throw new NullPointerException("Cannot remove a null listener."); //$NON-NLS-1$ + } + + if (listeners is null) { + return; + } + + listeners.remove(schemeListener); + + if (listeners.drained()) { + listeners = null; + } + } + + /** + * The string representation of this command -- for debugging purposes only. + * This string should not be shown to an end user. + * + * @return The string representation; never null. + */ + public final String toString() { + if (string is null) { + string = Format("Scheme({},{},{},{},{})",id,name,description,parentId,defined); + } + return string; + } + + /** + * Makes this scheme become undefined. This has the side effect of changing + * the name, description and parent identifier to null. + * Notification is sent to all listeners. + */ + public final void undefine() { + string = null; + + bool definedChanged = defined; + defined = false; + + bool nameChanged = name !is null; + name = null; + + bool descriptionChanged = description !is null; + description = null; + + bool parentIdChanged = parentId !is null; + parentId = null; + + fireSchemeChanged(new SchemeEvent(this, definedChanged, nameChanged, + descriptionChanged, parentIdChanged)); + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/SchemeEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/SchemeEvent.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2004, 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 + *******************************************************************************/ + +module dwtx.jface.bindings.SchemeEvent; + +import dwtx.jface.bindings.Scheme; + +import dwtx.core.commands.common.AbstractNamedHandleEvent; + +import dwt.dwthelper.utils; + +/** + * An instance of this class describes changes to an instance of + * IScheme. + *

+ * This class is not intended to be extended by clients. + *

+ * + * @since 3.1 + * @see ISchemeListener#schemeChanged(SchemeEvent) + */ +public final class SchemeEvent : AbstractNamedHandleEvent { + + /** + * The bit used to represent whether the scheme has changed its parent. + */ + private static const int CHANGED_PARENT_ID = LAST_USED_BIT << 1; + + /** + * The scheme that has changed; this value is never null. + */ + private const Scheme scheme; + + /** + * Creates a new instance of this class. + * + * @param scheme + * the instance of the interface that changed; must not be + * null. + * @param definedChanged + * true, iff the defined property changed. + * @param nameChanged + * true, iff the name property changed. + * @param descriptionChanged + * true if the description property changed; + * false otherwise. + * @param parentIdChanged + * true, iff the parentId property changed. + */ + public this(Scheme scheme, bool definedChanged, + bool nameChanged, bool descriptionChanged, + bool parentIdChanged) { + super(definedChanged, descriptionChanged, nameChanged); + + if (scheme is null) { + throw new NullPointerException(); + } + this.scheme = scheme; + + if (parentIdChanged) { + changedValues |= CHANGED_PARENT_ID; + } + } + + /** + * Returns the instance of the scheme that changed. + * + * @return the instance of the scheme that changed. Guaranteed not to be + * null. + */ + public final Scheme getScheme() { + return scheme; + } + + /** + * Returns whether or not the parentId property changed. + * + * @return true, iff the parentId property changed. + */ + public final bool isParentIdChanged() { + return ((changedValues & CHANGED_PARENT_ID) !is 0); + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/Trigger.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/Trigger.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2004, 2005 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 + *******************************************************************************/ +module dwtx.jface.bindings.Trigger; + +import dwt.dwthelper.utils; + +/** + *

+ * The abstract class for any object that can be used as a trigger for a binding. + * This ensures that trigger conform to certain minimum requirements. Namely, triggers + * need to be hashable. + *

+ * + * @since 3.1 + */ +public abstract class Trigger : Comparable { + + /** + * Tests whether this object is equal to another object. A handle object is + * only equal to another trigger with the same properties. + * + * @param object + * The object with which to compare; may be null. + * @return true if the objects are equal; false + * otherwise. + */ + public override abstract int opEquals(Object object); + + /** + * Computes the hash code for this object. + * + * @return The hash code for this object. + */ + public override abstract hash_t toHash(); +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/TriggerSequence.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/TriggerSequence.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,204 @@ +/******************************************************************************* + * Copyright (c) 2004, 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 + *******************************************************************************/ +module dwtx.jface.bindings.TriggerSequence; + +import dwtx.jface.bindings.Trigger; + +import dwtx.jface.util.Util; + +import dwt.dwthelper.utils; + +/** + *

+ * A sequence of one or more triggers. None of these triggers may be + * null. + *

+ * + * @since 3.1 + */ +public abstract class TriggerSequence { + + /** + * The value to see that hash code to if the hash code is not yet computed. + */ + private static const int HASH_CODE_NOT_COMPUTED = -1; + + /** + * A factor for computing the hash code for all trigger sequences. + */ + private static const int HASH_FACTOR = 89; + + /** + * The hash code for this object. This value is computed lazily, and marked + * as invalid when one of the values on which it is based changes. This + * values is HASH_CODE_NOT_COMPUTED iff the hash code has not + * yet been computed. + */ + protected /+transient+/ int hashCode = HASH_CODE_NOT_COMPUTED; + + /** + * The list of trigger in this sequence. This value is never + * null, and never contains null elements. + */ + protected const Trigger[] triggers; + + /** + * Constructs a new instance of TriggerSequence. + * + * @param triggers + * The triggers contained within this sequence; must not be + * null or contain null elements. + * May be empty. + */ + public this(Trigger[] triggers) { + if (triggers is null) { + throw new NullPointerException("The triggers cannot be null"); //$NON-NLS-1$ + } + + for (int i = 0; i < triggers.length; i++) { + if (triggers[i] is null) { + throw new IllegalArgumentException( + "All triggers in a trigger sequence must be an instance of Trigger"); //$NON-NLS-1$ + } + } + + int triggerLength = triggers.length; + this.triggers = new Trigger[triggerLength]; + System.arraycopy(triggers, 0, this.triggers, 0, triggerLength); + } + + /** + * Returns whether or not this key sequence ends with the given key + * sequence. + * + * @param triggerSequence + * a trigger sequence. Must not be null. + * @param equals + * whether or not an identical trigger sequence should be + * considered as a possible match. + * @return true, iff the given trigger sequence ends with + * this trigger sequence. + */ + public final bool endsWith(TriggerSequence triggerSequence, + bool equals) { + if (triggerSequence is null) { + throw new NullPointerException( + "Cannot end with a null trigger sequence"); //$NON-NLS-1$ + } + + return Util.endsWith(triggers, triggerSequence.triggers, equals); + } + + public final override int opEquals(Object object) { + // Check if they're the same. + if (object is this) { + return true; + } + + // Check if they're the same type. + if (!(cast(TriggerSequence)object )) { + return false; + } + + TriggerSequence triggerSequence = cast(TriggerSequence) object; + return Util.opEquals(triggers, triggerSequence.triggers); + } + + /** + * Formats this trigger sequence into the current default look. + * + * @return A string representation for this trigger sequence using the + * default look; never null. + */ + public abstract String format(); + + /** + *

+ * Returns a list of prefixes for the current sequence. A prefix is any + * leading subsequence in a TriggerSequence. A prefix is + * also an instance of TriggerSequence. + *

+ *

+ * For example, consider a trigger sequence that consists of four triggers: + * A, B, C and D. The prefixes would be "", "A", "A B", and "A B C". The + * list of prefixes must always be the same as the size of the trigger list. + *

+ * + * @return The array of possible prefixes for this sequence. This array must + * not be null, but may be empty. It must only + * contains instances of TriggerSequence. + */ + public abstract TriggerSequence[] getPrefixes(); + + /** + * Returns the list of triggers. + * + * @return The triggers; never null and guaranteed to only + * contain instances of Trigger. + */ + public final Trigger[] getTriggers() { + int triggerLength = triggers.length; + Trigger[] triggerCopy = new Trigger[triggerLength]; + System.arraycopy(triggers, 0, triggerCopy, 0, triggerLength); + return triggerCopy; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + public final override hash_t toHash() { + if (hashCode is HASH_CODE_NOT_COMPUTED) { + auto HASH_INITIAL = dwt.dwthelper.utils.toHash( TriggerSequence.classinfo.name ); + hashCode = HASH_INITIAL; + hashCode = hashCode * HASH_FACTOR + Util.toHash(triggers); + if (hashCode is HASH_CODE_NOT_COMPUTED) { + hashCode++; + } + } + + return hashCode; + } + + /** + * Returns whether or not this trigger sequence is empty. + * + * @return true, iff the trigger sequence is empty. + */ + public final bool isEmpty() { + return (triggers.length is 0); + } + + /** + * Returns whether or not this trigger sequence starts with the given + * trigger sequence. + * + * @param triggerSequence + * a trigger sequence. Must not be null. + * @param equals + * whether or not an identical trigger sequence should be + * considered as a possible match. + * @return true, iff the given trigger sequence starts with + * this key sequence. + */ + public final bool startsWith(TriggerSequence triggerSequence, + bool equals) { + if (triggerSequence is null) { + throw new NullPointerException( + "A trigger sequence cannot start with null"); //$NON-NLS-1$ + } + + return Util.startsWith(triggers, triggerSequence.triggers, equals); + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/keys/IKeyLookup.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/keys/IKeyLookup.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,462 @@ +/******************************************************************************* + * Copyright (c) 2005 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 + *******************************************************************************/ +module dwtx.jface.bindings.keys.IKeyLookup; + +import dwt.dwthelper.utils; +import dwt.dwthelper.Integer; + +/** + *

+ * A facilitiy for converting the formal representation for key strokes + * (i.e., used in persistence) into real key stroke instances. + *

+ * + * @since 3.1 + */ +public interface IKeyLookup { + /** + * The formal name of the 'Alt' key. + */ + public static const String ALT_NAME = "ALT"; //$NON-NLS-1$ + + /** + * The formal name of the 'Arrow Down' key. + */ + public static const String ARROW_DOWN_NAME = "ARROW_DOWN"; //$NON-NLS-1$ + + /** + * The formal name of the 'Arrow Left' key. + */ + public static const String ARROW_LEFT_NAME = "ARROW_LEFT"; //$NON-NLS-1$ + + /** + * The formal name of the 'Arrow Right' key. + */ + public static const String ARROW_RIGHT_NAME = "ARROW_RIGHT"; //$NON-NLS-1$ + + /** + * The formal name of the 'Arrow Up' key. + */ + public static const String ARROW_UP_NAME = "ARROW_UP"; //$NON-NLS-1$ + + /** + * An alternate name for the backspace key. + */ + public static const String BACKSPACE_NAME = "BACKSPACE"; //$NON-NLS-1$ + + /** + * The formal name for the 'Break' key. + */ + public static const String BREAK_NAME = "BREAK"; //$NON-NLS-1$ + + /** + * The formal name of the backspace key. + */ + public static const String BS_NAME = "BS"; //$NON-NLS-1$ + + /** + * The formal name for the 'Caps Lock' key. + */ + public static const String CAPS_LOCK_NAME = "CAPS_LOCK"; //$NON-NLS-1$ + + /** + * The formal name of the 'Command' key. + */ + public static const String COMMAND_NAME = "COMMAND"; //$NON-NLS-1$ + + /** + * The formal name of the carriage return (U+000D) + */ + public static const String CR_NAME = "CR"; //$NON-NLS-1$ + + /** + * The formal name of the 'Ctrl' key. + */ + public static const String CTRL_NAME = "CTRL"; //$NON-NLS-1$ + + /** + * The formal name of the delete (U+007F) key + */ + public static const String DEL_NAME = "DEL"; //$NON-NLS-1$ + + /** + * An alternative name for the delete key. + */ + public static const String DELETE_NAME = "DELETE"; //$NON-NLS-1$ + + /** + * The formal name of the 'End' key. + */ + public static const String END_NAME = "END"; //$NON-NLS-1$ + + /** + * An alternative name for the enter key. + */ + public static const String ENTER_NAME = "ENTER"; //$NON-NLS-1$ + + /** + * The formal name of the escape (U+001B) key. + */ + public static const String ESC_NAME = "ESC"; //$NON-NLS-1$ + + /** + * An alternative name for the escape key. + */ + public static const String ESCAPE_NAME = "ESCAPE"; //$NON-NLS-1$ + + /** + * The formal name of the 'F1' key. + */ + public static const String F1_NAME = "F1"; //$NON-NLS-1$ + + /** + * The formal name of the 'F10' key. + */ + public static const String F10_NAME = "F10"; //$NON-NLS-1$ + + /** + * The formal name of the 'F11' key. + */ + public static const String F11_NAME = "F11"; //$NON-NLS-1$ + + /** + * The formal name of the 'F12' key. + */ + public static const String F12_NAME = "F12"; //$NON-NLS-1$ + + /** + * The formal name of the 'F13' key. + */ + public static const String F13_NAME = "F13"; //$NON-NLS-1$ + + /** + * The formal name of the 'F14' key. + */ + public static const String F14_NAME = "F14"; //$NON-NLS-1$ + + /** + * The formal name of the 'F15' key. + */ + public static const String F15_NAME = "F15"; //$NON-NLS-1$ + + /** + * The formal name of the 'F2' key. + */ + public static const String F2_NAME = "F2"; //$NON-NLS-1$ + + /** + * The formal name of the 'F3' key. + */ + public static const String F3_NAME = "F3"; //$NON-NLS-1$ + + /** + * The formal name of the 'F4' key. + */ + public static const String F4_NAME = "F4"; //$NON-NLS-1$ + + /** + * The formal name of the 'F5' key. + */ + public static const String F5_NAME = "F5"; //$NON-NLS-1$ + + /** + * The formal name of the 'F6' key. + */ + public static const String F6_NAME = "F6"; //$NON-NLS-1$ + + /** + * The formal name of the 'F7' key. + */ + public static const String F7_NAME = "F7"; //$NON-NLS-1$ + + /** + * The formal name of the 'F8' key. + */ + public static const String F8_NAME = "F8"; //$NON-NLS-1$ + + /** + * The formal name of the 'F9' key. + */ + public static const String F9_NAME = "F9"; //$NON-NLS-1$ + + /** + * The formal name of the form feed (U+000C) key. + */ + public static const String FF_NAME = "FF"; //$NON-NLS-1$ + + /** + * The formal name of the 'Home' key. + */ + public static const String HOME_NAME = "HOME"; //$NON-NLS-1$ + + /** + * The formal name of the 'Insert' key. + */ + public static const String INSERT_NAME = "INSERT"; //$NON-NLS-1$ + + /** + * The formal name of the line feed (U+000A) key. + */ + public static const String LF_NAME = "LF"; //$NON-NLS-1$ + + /** + * The formal name of the 'M1' key. + */ + public static const String M1_NAME = "M1"; //$NON-NLS-1$ + + /** + * The formal name of the 'M2' key. + */ + public static const String M2_NAME = "M2"; //$NON-NLS-1$ + + /** + * The formal name of the 'M3' key. + */ + public static const String M3_NAME = "M3"; //$NON-NLS-1$ + + /** + * The formal name of the 'M4' key. + */ + public static const String M4_NAME = "M4"; //$NON-NLS-1$ + + /** + * The formal name of the null (U+0000) key. + */ + public static const String NUL_NAME = "NUL"; //$NON-NLS-1$ + + /** + * The formal name of the 'NumLock' key. + */ + public static const String NUM_LOCK_NAME = "NUM_LOCK"; //$NON-NLS-1$ + + /** + * The formal name of the '0' key on the numpad. + */ + public static const String NUMPAD_0_NAME = "NUMPAD_0"; //$NON-NLS-1$ + + /** + * The formal name of the '1' key on the numpad. + */ + public static const String NUMPAD_1_NAME = "NUMPAD_1"; //$NON-NLS-1$ + + /** + * The formal name of the '2' key on the numpad. + */ + public static const String NUMPAD_2_NAME = "NUMPAD_2"; //$NON-NLS-1$ + + /** + * The formal name of the '3' key on the numpad. + */ + public static const String NUMPAD_3_NAME = "NUMPAD_3"; //$NON-NLS-1$ + + /** + * The formal name of the '4' key on the numpad. + */ + public static const String NUMPAD_4_NAME = "NUMPAD_4"; //$NON-NLS-1$ + + /** + * The formal name of the '5' key on the numpad. + */ + public static const String NUMPAD_5_NAME = "NUMPAD_5"; //$NON-NLS-1$ + + /** + * The formal name of the '6' key on the numpad. + */ + public static const String NUMPAD_6_NAME = "NUMPAD_6"; //$NON-NLS-1$ + + /** + * The formal name of the '7' key on the numpad. + */ + public static const String NUMPAD_7_NAME = "NUMPAD_7"; //$NON-NLS-1$ + + /** + * The formal name of the '8' key on the numpad. + */ + public static const String NUMPAD_8_NAME = "NUMPAD_8"; //$NON-NLS-1$ + + /** + * The formal name of the '9' key on the numpad. + */ + public static const String NUMPAD_9_NAME = "NUMPAD_9"; //$NON-NLS-1$ + + /** + * The formal name of the 'Add' key on the numpad. + */ + public static const String NUMPAD_ADD_NAME = "NUMPAD_ADD"; //$NON-NLS-1$ + + /** + * The formal name of the 'Decimal' key on the numpad. + */ + public static const String NUMPAD_DECIMAL_NAME = "NUMPAD_DECIMAL"; //$NON-NLS-1$ + + /** + * The formal name of the 'Divide' key on the numpad. + */ + public static const String NUMPAD_DIVIDE_NAME = "NUMPAD_DIVIDE"; //$NON-NLS-1$ + + /** + * The formal name of the 'Enter' key on the numpad. + */ + public static const String NUMPAD_ENTER_NAME = "NUMPAD_ENTER"; //$NON-NLS-1$ + + /** + * The formal name of the '=' key on the numpad. + */ + public static const String NUMPAD_EQUAL_NAME = "NUMPAD_EQUAL"; //$NON-NLS-1$ + + /** + * The formal name of the 'Multiply' key on the numpad. + */ + public static const String NUMPAD_MULTIPLY_NAME = "NUMPAD_MULTIPLY"; //$NON-NLS-1$ + + /** + * The formal name of the 'Subtract' key on the numpad. + */ + public static const String NUMPAD_SUBTRACT_NAME = "NUMPAD_SUBTRACT"; //$NON-NLS-1$ + + /** + * The formal name of the 'Page Down' key. + */ + public static const String PAGE_DOWN_NAME = "PAGE_DOWN"; //$NON-NLS-1$ + + /** + * The formal name of the 'Page Up' key. + */ + public static const String PAGE_UP_NAME = "PAGE_UP"; //$NON-NLS-1$ + + /** + * The formal name for the 'Pause' key. + */ + public static const String PAUSE_NAME = "PAUSE"; //$NON-NLS-1$ + + /** + * The formal name for the 'Print Screen' key. + */ + public static const String PRINT_SCREEN_NAME = "PRINT_SCREEN"; //$NON-NLS-1$ + + /** + * An alternative name for the enter key. + */ + public static const String RETURN_NAME = "RETURN"; //$NON-NLS-1$ + + /** + * The formal name for the 'Scroll Lock' key. + */ + public static const String SCROLL_LOCK_NAME = "SCROLL_LOCK"; //$NON-NLS-1$ + + /** + * The formal name of the 'Shift' key. + */ + public static const String SHIFT_NAME = "SHIFT"; //$NON-NLS-1$ + + /** + * The formal name of the space (U+0020) key. + */ + public static const String SPACE_NAME = "SPACE"; //$NON-NLS-1$ + + /** + * The formal name of the tab (U+0009) key. + */ + public static const String TAB_NAME = "TAB"; //$NON-NLS-1$ + + /** + * The formal name of the vertical tab (U+000B) key. + */ + public static const String VT_NAME = "VT"; //$NON-NLS-1$ + + /** + * Looks up a single natural key by its formal name, and returns the integer + * representation for this natural key + * + * @param name + * The formal name of the natural key to look-up; must not be + * null. + * @return The integer representation of this key. If the natural key cannot + * be found, then this method returns 0. + */ + public int formalKeyLookup(String name); + + /** + * Looks up a single natural key by its formal name, and returns the integer + * representation for this natural key + * + * @param name + * The formal name of the natural key to look-up; must not be + * null. + * @return The integer representation of this key. If the natural key cannot + * be found, then this method returns 0. + */ + public Integer formalKeyLookupInteger(String name); + + /** + * Looks up a single modifier key by its formal name, and returns the integer + * representation for this modifier key + * + * @param name + * The formal name of the modifier key to look-up; must not be + * null. + * @return The integer representation of this key. If the modifier key + * cannot be found, then this method returns 0. + */ + public int formalModifierLookup(String name); + + /** + * Looks up a key value, and returns the formal string representation for + * that key + * + * @param key + * The key to look-up. + * @return The formal string representation of this key. If this key cannot + * be found, then it is simply the character corresponding to that + * integer value. + */ + public String formalNameLookup(int key); + + /** + * Returns the integer representation of the ALT key. + * + * @return The ALT key + */ + public int getAlt(); + + /** + * Returns the integer representation of the COMMAND key. + * + * @return The COMMAND key + */ + public int getCommand(); + + /** + * Returns the integer representation of the CTRL key. + * + * @return The CTRL key + */ + public int getCtrl(); + + /** + * Returns the integer representation of the SHIFT key. + * + * @return The SHIFT key + */ + public int getShift(); + + /** + * Returns whether the given key is a modifier key. + * + * @param key + * The integer value of the key to check. + * @return true if the key is one of the modifier keys; + * false otherwise. + */ + public bool isModifierKey(int key); +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/keys/KeyBinding.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/keys/KeyBinding.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2004, 2005 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 + *******************************************************************************/ +module dwtx.jface.bindings.keys.KeyBinding; + +import dwtx.jface.bindings.keys.KeySequence; + +import dwtx.core.commands.ParameterizedCommand; +import dwtx.jface.bindings.Binding; +import dwtx.jface.bindings.TriggerSequence; + +import dwt.dwthelper.utils; + +/** + *

+ * A keyboard shortcut. This is a binding between some keyboard input and the + * triggering of a command. This object is immutable. + *

+ * + * @since 3.1 + */ +public final class KeyBinding : Binding { + + /** + * The key sequence which triggers this binding. This sequence is never + * null. + */ + private const KeySequence keySequence; + + /** + * Constructs a new instance of KeyBinding. + * + * @param keySequence + * The key sequence which should trigger this binding. This value + * must not be null. It also must be a complete, + * non-empty key sequence. + * @param command + * The parameterized command to which this binding applies; this + * value may be null if the binding is meant to + * "unbind" a previously defined binding. + * @param schemeId + * The scheme to which this binding belongs; this value must not + * be null. + * @param contextId + * The context to which this binding applies; this value must not + * be null. + * @param locale + * The locale to which this binding applies; this value may be + * null if it applies to all locales. + * @param platform + * The platform to which this binding applies; this value may be + * null if it applies to all platforms. + * @param windowManager + * The window manager to which this binding applies; this value + * may be null if it applies to all window + * managers. This value is currently ignored. + * @param type + * The type of binding. This should be either SYSTEM + * or USER. + */ + public this(KeySequence keySequence, + ParameterizedCommand command, String schemeId, + String contextId, String locale, String platform, + String windowManager, int type) { + super(command, schemeId, contextId, locale, platform, windowManager, + type); + + if (keySequence is null) { + throw new NullPointerException("The key sequence cannot be null"); //$NON-NLS-1$ + } + + if (!keySequence.isComplete()) { + throw new IllegalArgumentException( + "Cannot bind to an incomplete key sequence"); //$NON-NLS-1$ + } + + if (keySequence.isEmpty()) { + throw new IllegalArgumentException( + "Cannot bind to an empty key sequence"); //$NON-NLS-1$ + } + + this.keySequence = keySequence; + } + + /** + * Returns the key sequence which triggers this binding. The key sequence + * will not be null, empty or incomplete. + * + * @return The key sequence; never null. + */ + public final KeySequence getKeySequence() { + return keySequence; + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.Binding#getTriggerSequence() + */ + public TriggerSequence getTriggerSequence() { + return getKeySequence(); + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/keys/KeyLookupFactory.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/keys/KeyLookupFactory.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + *******************************************************************************/ +module dwtx.jface.bindings.keys.KeyLookupFactory; + +import dwtx.jface.bindings.keys.SWTKeyLookup; +import dwtx.jface.bindings.keys.IKeyLookup; + +import dwt.dwthelper.utils; + +/** + *

+ * A factory class for ILookup instances. This factory can be + * used to retrieve instances of look-ups defined by this package. It also + * allows you to define your own look-up for use in the classes. + *

+ * + * @since 3.1 + */ +public final class KeyLookupFactory { + + /** + * The DWT key look-up defined by this package. + */ + private static SWTKeyLookup SWT_KEY_LOOKUP; + + /** + * The instance that should be used by KeyStroke in + * converting string representations to instances. + */ + private static IKeyLookup defaultLookup; + + private static void check_staticthis(){ + if( SWT_KEY_LOOKUP is null ){ + synchronized{ + if( SWT_KEY_LOOKUP is null ){ + SWT_KEY_LOOKUP = new SWTKeyLookup(); + defaultLookup = SWT_KEY_LOOKUP; + } + } + } + } + + /** + * Provides an instance of SWTKeyLookup. + * + * @return The DWT look-up table for key stroke format information; never + * null. + */ + public static final IKeyLookup getSWTKeyLookup() { + check_staticthis(); + return SWT_KEY_LOOKUP; + } + + /** + * An accessor for the current default look-up. + * + * @return The default look-up; never null. + */ + public static final IKeyLookup getDefault() { + check_staticthis(); + return defaultLookup; + } + + /** + * Sets the default look-up. + * + * @param defaultLookup + * the default look-up. Must not be null. + */ + public static final void setDefault(IKeyLookup defaultLookup) { + check_staticthis(); + if (defaultLookup is null) { + throw new NullPointerException("The look-up must not be null"); //$NON-NLS-1$ + } + + KeyLookupFactory.defaultLookup = defaultLookup; + } + + /** + * This class should not be instantiated. + */ + private this() { + // Not to be constructred. + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/keys/KeySequence.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/keys/KeySequence.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,294 @@ +/******************************************************************************* + * Copyright (c) 2004, 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 + *******************************************************************************/ + +module dwtx.jface.bindings.keys.KeySequence; + +import dwtx.jface.bindings.keys.KeyStroke; + +import tango.util.collection.ArraySeq; +import tango.util.collection.model.Seq; +import tango.util.collection.model.SeqView; + +import dwtx.jface.bindings.TriggerSequence; +import dwtx.jface.bindings.keys.formatting.KeyFormatterFactory; + +import dwtx.jface.util.Util; + +import dwt.dwthelper.utils; +static import tango.text.Util; +/** + *

+ * A KeySequence is defined as a list of zero or more + * KeyStrokes, with the stipulation that all + * KeyStroke objects must be complete, save for the last one, + * whose completeness is optional. A KeySequence is said to be + * complete if all of its KeyStroke objects are complete. + *

+ *

+ * All KeySequence objects have a formal string representation + * available via the toString() method. There are a number of + * methods to get instances of KeySequence objects, including one + * which can parse this formal string representation. + *

+ *

+ * All KeySequence objects, via the format() + * method, provide a version of their formal string representation translated by + * platform and locale, suitable for display to a user. + *

+ *

+ * KeySequence objects are immutable. Clients are not permitted + * to extend this class. + *

+ * + * @since 3.1 + */ +public final class KeySequence : TriggerSequence, Comparable { + + /** + * An empty key sequence instance for use by everyone. + */ + private const static KeySequence EMPTY_KEY_SEQUENCE; + + static this(){ + EMPTY_KEY_SEQUENCE = new KeySequence(null); + } + + /** + * The delimiter between multiple key strokes in a single key sequence -- + * expressed in the formal key stroke grammar. This is not to be displayed + * to the user. It is only intended as an internal representation. + */ + public const static String KEY_STROKE_DELIMITER = "\u0020"; //$NON-NLS-1$ + + /** + * The set of delimiters for KeyStroke objects allowed during + * parsing of the formal string representation. + */ + public const static String KEY_STROKE_DELIMITERS = KEY_STROKE_DELIMITER + ~ "\b\r\u007F\u001B\f\n\0\t\u000B"; //$NON-NLS-1$ + + /** + * Gets an instance of KeySequence. + * + * @return a key sequence. This key sequence will have no key strokes. + * Guaranteed not to be null. + */ + public static final KeySequence getInstance() { + return EMPTY_KEY_SEQUENCE; + } + + /** + * Creates an instance of KeySequence given a key sequence + * and a key stroke. + * + * @param keySequence + * a key sequence. Must not be null. + * @param keyStroke + * a key stroke. Must not be null. + * @return a key sequence that is equal to the given key sequence with the + * given key stroke appended to the end. Guaranteed not to be + * null. + */ + public static final KeySequence getInstance(KeySequence keySequence, + KeyStroke keyStroke) { + if (keySequence is null || keyStroke is null) { + throw new NullPointerException(); + } + + KeyStroke[] oldKeyStrokes = keySequence.getKeyStrokes(); + int oldKeyStrokeLength = oldKeyStrokes.length; + KeyStroke[] newKeyStrokes = new KeyStroke[oldKeyStrokeLength + 1]; + System.arraycopy(oldKeyStrokes, 0, newKeyStrokes, 0, + oldKeyStrokeLength); + newKeyStrokes[oldKeyStrokeLength] = keyStroke; + return new KeySequence(newKeyStrokes); + } + + /** + * Creates an instance of KeySequence given a single key + * stroke. + * + * @param keyStroke + * a single key stroke. Must not be null. + * @return a key sequence. Guaranteed not to be null. + */ + public static final KeySequence getInstance(KeyStroke keyStroke) { + return new KeySequence([ keyStroke ]); + } + + /** + * Creates an instance of KeySequence given an array of key + * strokes. + * + * @param keyStrokes + * the array of key strokes. This array may be empty, but it must + * not be null. This array must not contain + * null elements. + * @return a key sequence. Guaranteed not to be null. + */ + public static final KeySequence getInstance(KeyStroke[] keyStrokes) { + return new KeySequence(keyStrokes); + } + + /** + * Creates an instance of KeySequence given a list of key + * strokes. + * + * @param keyStrokes + * the list of key strokes. This list may be empty, but it must + * not be null. If this list is not empty, it + * must only contain instances of KeyStroke. + * @return a key sequence. Guaranteed not to be null. + */ + public static final KeySequence getInstance(SeqView!(KeyStroke) keyStrokes) { + return new KeySequence(keyStrokes.toArray()); + } + + /** + * Creates an instance of KeySequence by parsing a given + * formal string representation. + * + * @param string + * the formal string representation to parse. + * @return a key sequence. Guaranteed not to be null. + * @throws ParseException + * if the given formal string representation could not be parsed + * to a valid key sequence. + */ + public static final KeySequence getInstance(String string) { + if (string is null) { + throw new NullPointerException(); + } + + auto keyStrokes = new ArraySeq!(KeyStroke); + + try { + auto tokens = tango.text.Util.delimit( string, KEY_STROKE_DELIMITERS ); + KeyStroke[] keyStrokeArray = new KeyStroke[ tokens.length ]; + foreach( idx, tok; tokens ){ + keyStrokeArray[idx] = KeyStroke.getInstance(tok); + } + return new KeySequence(keyStrokeArray); + } catch (IllegalArgumentException e) { + throw new ParseException( + "Could not construct key sequence with these key strokes: " //$NON-NLS-1$ + /+~ keyStrokes+/); + } catch (NullPointerException e) { + throw new ParseException( + "Could not construct key sequence with these key strokes: " //$NON-NLS-1$ + /+~ keyStrokes+/); + } + } + + /** + * Constructs an instance of KeySequence given a list of key + * strokes. + * + * @param keyStrokes + * the list of key strokes. This list may be empty, but it must + * not be null. If this list is not empty, it + * must only contain instances of KeyStroke. + */ + protected this(KeyStroke[] keyStrokes) { + super(keyStrokes); + + for (int i = 0; i < triggers.length - 1; i++) { + KeyStroke keyStroke = cast(KeyStroke) triggers[i]; + + if (!keyStroke.isComplete()) { + throw new IllegalArgumentException(null); + } + } + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#compareTo(java.lang.Object) + */ + public final int compareTo(Object object) { + KeySequence castedObject = cast(KeySequence) object; + return Util.compare( arraycast!(Comparable)(triggers), arraycast!(Comparable)(castedObject.triggers)); + } + + /** + * Formats this key sequence into the current default look. + * + * @return A string representation for this key sequence using the default + * look; never null. + */ + public final String format() { + return KeyFormatterFactory.getDefault().format(this); + } + + /** + * Returns the list of key strokes for this key sequence. + * + * @return the list of key strokes keys. This list may be empty, but is + * guaranteed not to be null. If this list is not + * empty, it is guaranteed to only contain instances of + * KeyStroke. + */ + public final KeyStroke[] getKeyStrokes() { + int triggerLength = triggers.length; + KeyStroke[] keyStrokes = new KeyStroke[triggerLength]; + System.arraycopy(triggers, 0, keyStrokes, 0, triggerLength); + return keyStrokes; + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.TriggerSequence#getPrefixes() + */ + public final TriggerSequence[] getPrefixes() { + int numberOfPrefixes = triggers.length; + TriggerSequence[] prefixes = new TriggerSequence[numberOfPrefixes]; + prefixes[0] = KeySequence.getInstance(); + for (int i = 0; i < numberOfPrefixes - 1; i++) { + final KeyStroke[] prefixKeyStrokes = new KeyStroke[i + 1]; + System.arraycopy(triggers, 0, prefixKeyStrokes, 0, i + 1); + prefixes[i + 1] = KeySequence.getInstance(prefixKeyStrokes); + } + + return prefixes; + } + + /** + * Returns whether or not this key sequence is complete. Key sequences are + * complete iff all of their key strokes are complete. + * + * @return true, iff the key sequence is complete. + */ + public final bool isComplete() { + int triggersLength = triggers.length; + for (int i = 0; i < triggersLength; i++) { + if (!(cast(KeyStroke) triggers[i]).isComplete()) { + return false; + } + } + + return true; + } + + /** + * Returns the formal string representation for this key sequence. + * + * @return The formal string representation for this key sequence. + * Guaranteed not to be null. + * @see java.lang.Object#toString() + */ + public final String toString() { + return KeyFormatterFactory.getFormalKeyFormatter().format(this); + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/keys/KeySequenceText.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/keys/KeySequenceText.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,964 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 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 + *******************************************************************************/ + +module dwtx.jface.bindings.keys.KeySequenceText; + +import dwtx.jface.bindings.keys.KeyStroke; +import dwtx.jface.bindings.keys.SWTKeySupport; +import dwtx.jface.bindings.keys.KeySequence; + +import tango.util.collection.ArraySeq; +import tango.util.collection.HashSet; +import tango.util.collection.model.Seq; +import tango.util.collection.model.View; +import tango.util.collection.TreeMap; + +import dwt.DWT; +import dwt.events.DisposeEvent; +import dwt.events.DisposeListener; +import dwt.events.FocusEvent; +import dwt.events.FocusListener; +import dwt.events.ModifyEvent; +import dwt.events.ModifyListener; +import dwt.graphics.Font; +import dwt.graphics.Device; +import dwt.graphics.Point; +import dwt.widgets.Display; +import dwt.widgets.Event; +import dwt.widgets.Listener; +import dwt.widgets.Text; +import dwtx.jface.util.IPropertyChangeListener; +import dwtx.jface.util.PropertyChangeEvent; + +import dwt.dwthelper.utils; + +/** + *

+ * A wrapper around the DWT text widget that traps literal key presses and + * converts them into key sequences for display. There are two types of key + * strokes that are displayed: complete and incomplete. A complete key stroke is + * one with a natural key, while an incomplete one has no natural key. + * Incomplete key strokes are only displayed until they are made complete or + * their component key presses are released. + *

+ * + * @since 3.1 + */ +public final class KeySequenceText { + + /** + * A key listener that traps incoming events and displays them in the + * wrapped text field. It has no effect on traversal operations. + */ + private class KeyTrapListener : Listener { + /** + * The index at which insertion should occur. This is used if there is a + * replacement occurring in the middle of the stroke, and the first key + * stroke was incomplete. + */ + private int insertionIndex = -1; + + /** + * Resets the insertion index to point nowhere. In other words, it is + * set to -1. + */ + void clearInsertionIndex() { + insertionIndex = -1; + } + + /** + * Deletes the current selection. If there is no selection, then it + * deletes the last key stroke. + * + * @param keyStrokes + * The key strokes from which to delete. This list must not + * be null, and must represent a valid key + * sequence. + */ + private final KeyStroke[] deleteKeyStroke(KeyStroke[] keyStrokes) { + clearInsertionIndex(); + + if (hasSelection()) { + /* + * Delete the current selection -- disallowing incomplete + * strokes in the middle of the sequence. + */ + KeyStroke[][] deletedKeyStrokes = new KeyStroke[][](1); + deleteSelection(keyStrokes, false, deletedKeyStrokes); + return deletedKeyStrokes[0]; + } + + // Remove the last key stroke. + if (keyStrokes.length > 0) { + int newKeyStrokesLength = keyStrokes.length - 1; + KeyStroke[] newKeyStrokes = new KeyStroke[newKeyStrokesLength]; + System.arraycopy(keyStrokes, 0, newKeyStrokes, 0, + newKeyStrokesLength); + return newKeyStrokes; + } + + return keyStrokes; + } + + /** + * Handles the key pressed and released events on the wrapped text + * widget. This makes sure to either add the pressed key to the + * temporary key stroke, or complete the current temporary key stroke + * and prompt for the next. In the case of a key release, this makes + * sure that the temporary stroke is correctly displayed -- + * corresponding with modifier keys that may have been released. + * + * @param event + * The triggering event; must not be null. + */ + public void handleEvent(Event event) { + KeyStroke[] keyStrokes = getKeySequence().getKeyStrokes(); + + // Dispatch the event to the correct handler. + if (event.type is DWT.KeyDown) { + keyStrokes = handleKeyDown(event, keyStrokes); + } else if (event.type is DWT.KeyUp) { + keyStrokes = handleKeyUp(event, keyStrokes); + } + + // Update the underlying widget. + setKeySequence(KeySequence.getInstance(keyStrokes)); + + // Prevent the event from reaching the widget. + event.doit = false; + } + + /** + * Handles the case where the key event is an DWT.KeyDown + * event. This either causes a deletion (if it is an unmodified + * backspace key stroke), or an insertion (if it is any other key). + * + * @param event + * The trigger key down event; must not be null. + * @param keyStrokes + * The current list of key strokes. This valud must not be + * null, and it must represent a valid key + * sequence. + */ + private KeyStroke[] handleKeyDown(Event event, KeyStroke[] keyStrokes) { + // Is it an unmodified backspace character? + if ((event.character is DWT.BS) && (event.stateMask is 0)) { + return deleteKeyStroke(keyStrokes); + } + + return insertKeyStroke(event, keyStrokes); + } + + /** + * Handles the case where the key event is an DWT.KeyUp + * event. This resets the insertion index. If there is an incomplete + * stroke, then that incomplete stroke is modified to match the keys + * that are still held. If no keys are held, then the incomplete stroke + * is removed. + * + * @param event + * The triggering event; must not be null + * @param keyStrokes + * The key strokes that are part of the current key sequence; + * these key strokes are guaranteed to represent a valid key + * sequence. This value must not be null. + */ + private final KeyStroke[] handleKeyUp(Event event, + KeyStroke[] keyStrokes) { + if (hasIncompleteStroke()) { + /* + * Figure out the DWT integer representation of the remaining + * values. + */ + Event mockEvent = new Event(); + if ((event.keyCode & DWT.MODIFIER_MASK) !is 0) { + // This key up is a modifier key being released. + mockEvent.stateMask = event.stateMask - event.keyCode; + } else { + /* + * This key up is the other end of a key down that was + * trapped by the operating system or window manager. + */ + mockEvent.stateMask = event.stateMask; + } + + /* + * Get a reasonable facsimile of the stroke that is still + * pressed. + */ + int key = SWTKeySupport + .convertEventToUnmodifiedAccelerator(mockEvent); + KeyStroke remainingStroke = SWTKeySupport + .convertAcceleratorToKeyStroke(key); + int keyStrokesLength = keyStrokes.length; + KeyStroke[] newKeyStrokes; + if ((keyStrokesLength > 0) + && (remainingStroke.getModifierKeys() !is 0)) { + newKeyStrokes = new KeyStroke[keyStrokesLength]; + System.arraycopy(keyStrokes, 0, newKeyStrokes, 0, + keyStrokesLength - 1); + newKeyStrokes[keyStrokesLength - 1] = remainingStroke; + + } else if (keyStrokesLength > 0) { + newKeyStrokes = new KeyStroke[keyStrokesLength - 1]; + System.arraycopy(keyStrokes, 0, newKeyStrokes, 0, + keyStrokesLength - 1); + + } else if (remainingStroke.getModifierKeys() !is 0) { + newKeyStrokes = new KeyStroke[keyStrokesLength + 1]; + System.arraycopy(keyStrokes, 0, newKeyStrokes, 0, + keyStrokesLength); + newKeyStrokes[keyStrokesLength] = remainingStroke; + + } else { + newKeyStrokes = keyStrokes; + + } + + return newKeyStrokes; + } + + return keyStrokes; + } + + /** + *

+ * Handles the case where a key down event is leading to a key stroke + * being inserted. The current selection is deleted, and an invalid + * remanents of the stroke are also removed. The insertion is carried + * out at the cursor position. + *

+ *

+ * If only a natural key is selected (as part of a larger key stroke), + * then it is possible for the user to press a natural key to replace + * the old natural key. In this situation, pressing any modifier keys + * will replace the whole thing. + *

+ *

+ * If the insertion point is not at the end of the sequence, then + * incomplete strokes will not be immediately inserted. Only when the + * sequence is completed is the stroke inserted. This is a requirement + * as the widget must always represent a valid key sequence. The + * insertion point is tracked using insertionIndex, + * which is an index into the key stroke array. + *

+ * + * @param event + * The triggering key down event; must not be + * null. + * @param keyStrokes + * The key strokes into which the current stroke should be + * inserted. This value must not be null, and + * must represent a valid key sequence. + */ + private final KeyStroke[] insertKeyStroke(Event event, + KeyStroke[] keyStrokes) { + // Compute the key stroke to insert. + int key = SWTKeySupport.convertEventToUnmodifiedAccelerator(event); + KeyStroke stroke = SWTKeySupport.convertAcceleratorToKeyStroke(key); + + /* + * Only insert the stroke if it is *not ScrollLock. Let's not get + * silly + */ + if ((DWT.NUM_LOCK is stroke.getNaturalKey()) + || (DWT.CAPS_LOCK is stroke.getNaturalKey()) + || (DWT.SCROLL_LOCK is stroke.getNaturalKey())) { + return keyStrokes; + } + + if (insertionIndex !is -1) { + // There is a previous replacement still going on. + if (stroke.isComplete()) { + keyStrokes = insertStrokeAt(keyStrokes, stroke, + insertionIndex); + clearInsertionIndex(); + } + + } else if (hasSelection()) { + // There is a selection that needs to be replaced. + KeyStroke[][] deletedKeyStrokes = new KeyStroke[][](1); + insertionIndex = deleteSelection(keyStrokes, stroke + .isComplete(), deletedKeyStrokes); + keyStrokes = deletedKeyStrokes[0]; + if ((stroke.isComplete()) + || (insertionIndex >= keyStrokes.length)) { + keyStrokes = insertStrokeAt(keyStrokes, stroke, + insertionIndex); + clearInsertionIndex(); + } + + } else { + // No selection, so remove the incomplete stroke, if any + if ((hasIncompleteStroke()) && (keyStrokes.length > 0)) { + KeyStroke[] newKeyStrokes = new KeyStroke[keyStrokes.length - 1]; + System.arraycopy(keyStrokes, 0, newKeyStrokes, 0, + keyStrokes.length - 1); + keyStrokes = newKeyStrokes; + } + + // And then add the new stroke. + if ((keyStrokes.length is 0) + || (insertionIndex >= keyStrokes.length) + || (isCursorInLastPosition())) { + keyStrokes = insertStrokeAt(keyStrokes, stroke, + keyStrokes.length); + clearInsertionIndex(); + } else { + /* + * I'm just getting the insertionIndex here. No actual + * deletion should occur. + */ + KeyStroke[][] deletedKeyStrokes = new KeyStroke[][](1); + insertionIndex = deleteSelection(keyStrokes, stroke + .isComplete(), deletedKeyStrokes); + keyStrokes = deletedKeyStrokes[0]; + if (stroke.isComplete()) { + keyStrokes = insertStrokeAt(keyStrokes, stroke, + insertionIndex); + clearInsertionIndex(); + } + } + + } + + return keyStrokes; + } + } + + /** + * A traversal listener that blocks all traversal except for tabs and arrow + * keys. + */ + private class TraversalFilter : Listener { + /** + * Handles the traverse event on the text field wrapped by this class. + * It swallows all traverse events example for tab and arrow key + * navigation. The other forms of navigation can be reached by tabbing + * off of the control. + * + * @param event + * The trigger event; must not be null. + */ + public void handleEvent(Event event) { + switch (event.detail) { + case DWT.TRAVERSE_ESCAPE: + case DWT.TRAVERSE_MNEMONIC: + case DWT.TRAVERSE_NONE: + case DWT.TRAVERSE_PAGE_NEXT: + case DWT.TRAVERSE_PAGE_PREVIOUS: + case DWT.TRAVERSE_RETURN: + event.type = DWT.None; + event.doit = false; + break; + + case DWT.TRAVERSE_TAB_NEXT: + case DWT.TRAVERSE_TAB_PREVIOUS: + // Check if modifiers other than just 'Shift' were + // down. + if ((event.stateMask & (DWT.MODIFIER_MASK ^ DWT.SHIFT)) !is 0) { + // Modifiers other than shift were down. + event.type = DWT.None; + event.doit = false; + break; + } + + // fall through -- either no modifiers, or just shift. + + case DWT.TRAVERSE_ARROW_NEXT: + case DWT.TRAVERSE_ARROW_PREVIOUS: + default: + // Let the traversal happen, but clear the incomplete + // stroke + if (hasIncompleteStroke()) { + KeyStroke[] oldKeyStrokes = getKeySequence() + .getKeyStrokes(); + int newKeyStrokesLength = oldKeyStrokes.length - 1; + if (newKeyStrokesLength >= 1) { + KeyStroke[] newKeyStrokes = new KeyStroke[newKeyStrokesLength]; + System.arraycopy(oldKeyStrokes, 0, newKeyStrokes, 0, + newKeyStrokesLength); + setKeySequence(KeySequence.getInstance(newKeyStrokes)); + } else { + setKeySequence(KeySequence.getInstance()); + } + } + } + + } + } + + /** + * The manager resposible for installing and removing the traversal filter + * when the key sequence entry widget gains and loses focus. + */ + private class TraversalFilterManager : FocusListener { + /** The managed filter. We only need one instance. */ + private TraversalFilter filter; + + private bool filtering = false; + + this(){ + filter = new TraversalFilter(); + } + + /** + * Attaches the global traversal filter. + * + * @param event + * Ignored. + */ + public void focusGained(FocusEvent event) { + Display.getCurrent().addFilter(DWT.Traverse, filter); + filtering = true; + } + + /** + * Detaches the global traversal filter. + * + * @param event + * Ignored. + */ + public void focusLost(FocusEvent event) { + Display.getCurrent().removeFilter(DWT.Traverse, filter); + filtering = false; + } + + /** + * Remove the traverse filter if we close without focusOut. + */ + public void dispose() { + if (filtering) { + Display.getCurrent().removeFilter(DWT.Traverse, filter); + } + } + } + + /** + * A modification listener that makes sure that external events to this + * class (i.e., direct modification of the underlying text) do not break + * this class' view of the world. + */ + private class UpdateSequenceListener : ModifyListener { + /** + * Handles the modify event on the underlying text widget. + * + * @param event + * The triggering event; ignored. + */ + public void modifyText(ModifyEvent event) { + try { + // The original sequence. + KeySequence originalSequence = getKeySequence(); + + // The new sequence drawn from the text. + String contents = getText(); + KeySequence newSequence = KeySequence.getInstance(contents); + + // Check to see if they're the same. + if (!originalSequence.opEquals(newSequence)) { + setKeySequence(newSequence); + } + + } catch (ParseException e) { + // Abort any cut/paste-driven modifications + setKeySequence(getKeySequence()); + } + } + } + + static this(){ + HashSet!(KeyStroke) trappedKeys = new HashSet!(KeyStroke); + trappedKeys.add(SWTKeySupport.convertAcceleratorToKeyStroke(DWT.TAB)); + trappedKeys.add(SWTKeySupport.convertAcceleratorToKeyStroke(DWT.TAB + | DWT.SHIFT)); + trappedKeys.add(SWTKeySupport.convertAcceleratorToKeyStroke(DWT.BS)); + //List trappedKeyList = new ArrayList(trappedKeys); + TRAPPED_KEYS = trappedKeys;// Collections.unmodifiableList(trappedKeyList); + } + + /** An empty string instance for use in clearing text values. */ + private static const String EMPTY_STRING = ""; //$NON-NLS-1$ + + /** + * The special integer value for the maximum number of strokes indicating + * that an infinite number should be allowed. + */ + public static const int INFINITE = -1; + + /** + * The name of the property representing the current key sequence in this + * key sequence widget. + * + * @since 3.2 + */ + public static const String P_KEY_SEQUENCE = "dwtx.jface.bindings.keys.KeySequenceText.KeySequence"; //$NON-NLS-1$ + + /** + * The keys trapped by this widget. This list is guaranteed to be roughly + * accurate. Perfection is not possible, as DWT does not export traversal + * keys as constants. + */ + public static const View!(KeyStroke) TRAPPED_KEYS; + + /** + * The key filter attached to the underlying widget that traps key events. + */ + private const KeyTrapListener keyFilter; + + /** + * The text of the key sequence -- containing only the complete key strokes. + */ + private KeySequence keySequence; + + /** + * Those listening to changes to the key sequence in this widget. This value + * may be null if there are no listeners. + */ + private ArraySeq!(IPropertyChangeListener) listeners = null; + + /** The maximum number of key strokes permitted in the sequence. */ + private int maxStrokes = INFINITE; + + /** The text widget that is wrapped for this class. */ + private const Text text; + + /** + * The listener that makes sure that the text widget remains up-to-date with + * regards to external modification of the text (e.g., cut & pasting). + */ + private const UpdateSequenceListener updateSequenceListener; + + /** + * Constructs an instance of KeySequenceTextField with the + * text field to use. If the platform is carbon (MacOS X), then the font is + * set to be the same font used to display accelerators in the menus. + * + * @param wrappedText + * The text widget to wrap; must not be null. + */ + public this(Text wrappedText) { + keyFilter = new KeyTrapListener(); + keySequence = KeySequence.getInstance(); + updateSequenceListener = new UpdateSequenceListener(); + + text = wrappedText; + + // Set the font if the platform is carbon. + if ("carbon".equals(DWT.getPlatform())) { //$NON-NLS-1$ + // Don't worry about this font name here; it is the official menu + // font and point size on the Mac. + Font font = new Font(cast(Device)text.getDisplay(), + "Lucida Grande", 13, DWT.NORMAL); //$NON-NLS-1$ + text.setFont(font); + text.addDisposeListener(new class DisposeListener { + Font font_; + this(){ + font_=font; + } + public void widgetDisposed(DisposeEvent e) { + font_.dispose(); + } + }); + } + + // Add the key listener. + text.addListener(DWT.KeyUp, keyFilter); + text.addListener(DWT.KeyDown, keyFilter); + + TraversalFilterManager traversalFilterManager = new TraversalFilterManager(); + text.addFocusListener(traversalFilterManager); + text.addDisposeListener(new class DisposeListener { + TraversalFilterManager traversalFilterManager_; + this(){ + traversalFilterManager_=traversalFilterManager; + } + public void widgetDisposed(DisposeEvent e) { + traversalFilterManager_.dispose(); + } + }); + + // Add an internal modify listener. + text.addModifyListener(updateSequenceListener); + } + + /** + * Adds a property change listener to this key sequence widget. It will be + * notified when the key sequence changes. + * + * @param listener + * The listener to be notified when changes occur; must not be + * null. + * @since 3.2 + */ + public final void addPropertyChangeListener( + IPropertyChangeListener listener) { + if (listener is null) { + return; + } + + if (listeners is null) { + listeners = new ArraySeq!(IPropertyChangeListener); + } + + listeners.append(listener); + } + + /** + * Clears the text field and resets all the internal values. + */ + public void clear() { + KeySequence oldKeySequence = keySequence; + keySequence = KeySequence.getInstance(); + text.setText(EMPTY_STRING); + firePropertyChangeEvent(oldKeySequence); + } + + /** + * Removes the key strokes from the list corresponding the selection. If + * allowIncomplete, then invalid key sequences will be + * allowed (i.e., those with incomplete strokes in the non-terminal + * position). Otherwise, incomplete strokes will be removed. This modifies + * keyStrokes in place, and has no effect on the text widget + * this class wraps. + * + * @param keyStrokes + * The list of key strokes from which the selection should be + * removed; must not be null. + * @param allowIncomplete + * Whether incomplete strokes should be allowed to exist in the + * list after the deletion. + * @return The index at which a subsequent insert should occur. This index + * only has meaning to the insertStrokeAt method. + */ + private final int deleteSelection(KeyStroke[] keyStrokes, + bool allowIncomplete, KeyStroke[][] deletedKeyStrokes) { + // Get the current selection. + Point selection = text.getSelection(); + int start = selection.x; + int end = selection.y; + + /* + * Using the key sequence format method, discover the point at which + * adding key strokes passes or equals the start of the selection. In + * other words, find the first stroke that is part of the selection. + * Keep track of the text range under which the stroke appears (i.e., + * startTextIndex->string.length() is the first selected stroke). + */ + String string; + auto currentStrokes = new ArraySeq!(KeyStroke); + int startTextIndex = 0; // keeps track of the start of the stroke + int keyStrokesLength = keyStrokes.length; + int i; + for (i = 0; (i < keyStrokesLength) && (string.length < start); i++) { + startTextIndex = string.length; + currentStrokes.append(keyStrokes[i]); + string = KeySequence.getInstance(currentStrokes).format(); + } + + /* + * If string.length() is start, then the cursor is positioned between + * strokes (i.e., selection is outside of a stroke). + */ + int startStrokeIndex; + if (string.length is start) { + startStrokeIndex = currentStrokes.size(); + } else { + startStrokeIndex = currentStrokes.size() - 1; + } + + /* + * Check to see if the cursor is only positioned, rather than actually + * selecting something. We only need to compute the end if there is a + * selection. + */ + int endStrokeIndex; + if (start is end) { + return startStrokeIndex; + } + + for (; (i < keyStrokesLength) && (string.length < end); i++) { + currentStrokes.append(keyStrokes[i]); + string = KeySequence.getInstance(currentStrokes).format(); + } + endStrokeIndex = currentStrokes.size() - 1; + if (endStrokeIndex < 0) { + endStrokeIndex = 0; + } + + /* + * Remove the strokes that are touched by the selection. Keep track of + * the first stroke removed. + */ + int newLength = endStrokeIndex - startStrokeIndex + 1; + deletedKeyStrokes[0] = new KeyStroke[newLength]; + KeyStroke startStroke = keyStrokes[startStrokeIndex]; + System.arraycopy(keyStrokes, 0, keyStrokes, 0, newLength); + + /* + * Allow the first stroke removed to be replaced by an incomplete + * stroke. + */ + if (allowIncomplete) { + int modifierKeys = startStroke.getModifierKeys(); + KeyStroke incompleteStroke = KeyStroke.getInstance(modifierKeys, + KeyStroke.NO_KEY); + int incompleteStrokeLength = incompleteStroke.format().length; + if ((startTextIndex + incompleteStrokeLength) <= start) { + KeyStroke[] added = new KeyStroke[newLength + 1]; + System.arraycopy(deletedKeyStrokes[0], 0, added, 0, + startStrokeIndex); + added[startStrokeIndex] = incompleteStroke; + System.arraycopy(deletedKeyStrokes[0], startStrokeIndex, added, + startStrokeIndex + 1, newLength); + deletedKeyStrokes[0] = added; + } + } + + return startStrokeIndex; + } + + /** + * Fires a property change event to all of the listeners. + * + * @param oldKeySequence + * The old key sequence; must not be null. + * @since 3.2 + */ + protected final void firePropertyChangeEvent( + KeySequence oldKeySequence) { + if (listeners !is null) { + PropertyChangeEvent event = new PropertyChangeEvent(this, + P_KEY_SEQUENCE, oldKeySequence, getKeySequence()); + foreach( listener; listeners ){ + listener.propertyChange(event); + } + } + } + + /** + * An accessor for the KeySequence that corresponds to the + * current state of the text field. This includes incomplete strokes. + * + * @return The key sequence representation; never null. + */ + public KeySequence getKeySequence() { + return keySequence; + } + + /** + * An accessor for the underlying text widget's contents. + * + * @return The text contents of this entry; never null. + */ + private String getText() { + return text.getText(); + } + + /** + * Tests whether the current key sequence has a stroke with no natural key. + * + * @return true is there is an incomplete stroke; + * false otherwise. + */ + private bool hasIncompleteStroke() { + return !keySequence.isComplete(); + } + + /** + * Tests whether the current text widget has some text selection. + * + * @return true if the number of selected characters it + * greater than zero; false otherwise. + */ + private bool hasSelection() { + return (text.getSelectionCount() > 0); + } + + /** + * Inserts the key stroke at the current insertion point. This does a + * regular delete and insert, as if the key had been pressed. + * + * @param stroke + * The key stroke to insert; must not be null. + */ + public void insert(KeyStroke stroke) { + if (!stroke.isComplete()) { + return; + } + + // Copy the key strokes in the current key sequence. + KeySequence keySequence = getKeySequence(); + KeyStroke[] oldKeyStrokes = keySequence.getKeyStrokes(); + KeyStroke[] newKeyStrokes; + if ((hasIncompleteStroke()) && (!keySequence.isEmpty())) { + int newKeyStrokesLength = oldKeyStrokes.length - 1; + newKeyStrokes = new KeyStroke[newKeyStrokesLength]; + System.arraycopy(oldKeyStrokes, 0, newKeyStrokes, 0, + newKeyStrokesLength); + } else { + newKeyStrokes = oldKeyStrokes; + } + + KeyStroke[][] deletedKeyStrokes = new KeyStroke[][](1); + int index = deleteSelection(newKeyStrokes, false, deletedKeyStrokes); + if (index is -1) { + index = 0; + } + + KeyStroke[] keyStrokes = insertStrokeAt(newKeyStrokes, stroke, index); + keyFilter.clearInsertionIndex(); + setKeySequence(KeySequence.getInstance(keyStrokes)); + } + + /** + * Inserts the stroke at the given index in the list of strokes. If the + * stroke currently at that index is incomplete, then it tries to merge the + * two strokes. If merging is a complete failure (unlikely), then it will + * simply overwrite the incomplete stroke. If the stroke at the index is + * complete, then it simply inserts the stroke independently. + * + * @param keyStrokes + * The list of key strokes in which the key stroke should be + * appended; must not be null. + * @param stroke + * The stroke to insert; should not be null. + * @param index + * The index at which to insert; must be a valid index into the + * list of key strokes. + */ + private final KeyStroke[] insertStrokeAt(KeyStroke[] keyStrokes, + KeyStroke stroke, int index) { + int keyStrokesLength = keyStrokes.length; + KeyStroke currentStroke = (index >= keyStrokesLength) ? null + : keyStrokes[index]; + if ((currentStroke !is null) && (!currentStroke.isComplete())) { + int modifierKeys = currentStroke.getModifierKeys(); + int naturalKey = stroke.getNaturalKey(); + modifierKeys |= stroke.getModifierKeys(); + keyStrokes[index] = KeyStroke.getInstance(modifierKeys, naturalKey); + return keyStrokes; + } + + KeyStroke[] newKeyStrokes = new KeyStroke[keyStrokesLength + 1]; + System.arraycopy(keyStrokes, 0, newKeyStrokes, 0, index); + newKeyStrokes[index] = stroke; + if (index < keyStrokesLength) { + System.arraycopy(keyStrokes, index, newKeyStrokes, index + 1, + keyStrokesLength-index); + } + return newKeyStrokes; + } + + /** + * Tests whether the cursor is in the last position. This means that the + * selection extends to the last position. + * + * @return true if the selection extends to the last + * position; false otherwise. + */ + private bool isCursorInLastPosition() { + return (text.getSelection().y >= getText().length); + } + + /** + * Removes the given listener from this key sequence widget. + * + * @param listener + * The listener to be removed; must not be null. + * @since 3.2 + */ + public final void removePropertyChangeListener( + IPropertyChangeListener listener) { + if ((listener is null) || (listeners is null)) { + return; + } + + listeners.remove(listener); + } + + /** + *

+ * A mutator for the key sequence stored within this widget. The text and + * caret position are updated. + *

+ *

+ * All sequences are limited to maxStrokes number of strokes in length. If + * there are already that number of strokes, then it does not show + * incomplete strokes, and does not keep track of them. + *

+ * + * @param newKeySequence + * The new key sequence for this widget; may be null + * if none. + */ + public void setKeySequence(KeySequence newKeySequence) { + KeySequence oldKeySequence = keySequence; + keySequence = newKeySequence; + + // Trim any extra strokes. + if (maxStrokes !is INFINITE) { + KeyStroke[] oldKeyStrokes = keySequence.getKeyStrokes(); + if (maxStrokes < oldKeyStrokes.length) { + KeyStroke[] newKeyStrokes = new KeyStroke[maxStrokes]; + System + .arraycopy(oldKeyStrokes, 0, newKeyStrokes, 0, + maxStrokes); + keySequence = KeySequence.getInstance(newKeyStrokes); + } + } + + // Check to see if the text has changed. + String currentString = getText(); + String newString = keySequence.format(); + if (!currentString.equals(newString)) { + // We need to update the text + text.removeModifyListener(updateSequenceListener); + text.setText(keySequence.format()); + text.addModifyListener(updateSequenceListener); + text.setSelection(getText().length); + } + + firePropertyChangeEvent(oldKeySequence); + } + + /** + * Returns the maximum number of strokes that are permitted in this widget + * at one time. + * + * @return The maximum number of strokes; will be a positive integer or + * INFINITE. + */ + public int getKeyStrokeLimit() { + return maxStrokes; + } + + /** + * A mutator for the maximum number of strokes that are permitted in this + * widget at one time. + * + * @param keyStrokeLimit + * The maximum number of strokes; must be a positive integer or + * INFINITE. + */ + public void setKeyStrokeLimit(int keyStrokeLimit) { + if (keyStrokeLimit > 0 || keyStrokeLimit is INFINITE) { + this.maxStrokes = keyStrokeLimit; + } else { + throw new IllegalArgumentException(null); + } + + // Make sure we are obeying the new limit. + setKeySequence(getKeySequence()); + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/keys/KeyStroke.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/keys/KeyStroke.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,283 @@ +/******************************************************************************* + * Copyright (c) 2004, 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 + *******************************************************************************/ + +module dwtx.jface.bindings.keys.KeyStroke; + +import dwtx.jface.bindings.keys.IKeyLookup; +import dwtx.jface.bindings.keys.KeyLookupFactory; + +import dwtx.jface.bindings.Trigger; +import dwtx.jface.bindings.keys.formatting.KeyFormatterFactory; +import dwtx.jface.util.Util; + +import dwt.dwthelper.utils; +static import tango.text.Util; +/** + *

+ * A KeyStroke is defined as an optional set of modifier keys + * followed optionally by a natural key. A KeyStroke is said to + * be complete if it contains a natural key. A natural key is any Unicode + * character (e.g., "backspace", etc.), any character belonging to a natural + * language (e.g., "A", "1", "[", etc.), or any special control character + * specific to computers (e.g., "F10", "PageUp", etc.). + *

+ *

+ * All KeyStroke objects have a formal string representation + * available via the toString() method. There are a number of + * methods to get instances of KeyStroke objects, including one + * which can parse this formal string representation. + *

+ *

+ * All KeyStroke objects, via the format() method, + * provide a version of their formal string representation translated by + * platform and locale, suitable for display to a user. + *

+ *

+ * KeyStroke objects are immutable. Clients are not permitted to + * extend this class. + *

+ * + * @since 3.1 + */ +public final class KeyStroke : Trigger, Comparable { + + /** + * The delimiter between multiple keys in a single key strokes -- expressed + * in the formal key stroke grammar. This is not to be displayed to the + * user. It is only intended as an internal representation. + */ + public static const String KEY_DELIMITER = "\u002B"; //$NON-NLS-1$ + + /** + * The set of delimiters for Key objects allowed during + * parsing of the formal string representation. + */ + public static const String KEY_DELIMITERS = KEY_DELIMITER; + + /** + * The representation for no key. + */ + public static const int NO_KEY = 0; + + /** + * Creates an instance of KeyStroke given a natural key. + * + * @param naturalKey + * the natural key. The format of this integer is defined by + * whichever widget toolkit you are using; NO_KEY + * always means no natural key. + * @return a key stroke. This key stroke will have no modifier keys. + * Guaranteed not to be null. + * @see SWTKeySupport + */ + public static final KeyStroke getInstance(int naturalKey) { + return new KeyStroke(NO_KEY, naturalKey); + } + + /** + * Creates an instance of KeyStroke given a set of modifier keys + * and a natural key. + * + * @param modifierKeys + * the modifier keys. The format of this integer is defined by + * whichever widget toolkit you are using; NO_KEY + * always means no modifier keys. + * @param naturalKey + * the natural key. The format of this integer is defined by + * whichever widget toolkit you are using; NO_KEY + * always means no natural key. + * @return a key stroke. Guaranteed not to be null. + * @see SWTKeySupport + */ + public static final KeyStroke getInstance(int modifierKeys, + int naturalKey) { + return new KeyStroke(modifierKeys, naturalKey); + } + + /** + * Creates an instance of KeyStroke by parsing a given a formal + * string representation. + * + * @param string + * the formal string representation to parse. + * @return a key stroke. Guaranteed not to be null. + * @throws ParseException + * if the given formal string representation could not be parsed + * to a valid key stroke. + */ + public static final KeyStroke getInstance(String string) { + if (string is null) { + throw new NullPointerException("Cannot parse a null string"); //$NON-NLS-1$ + } + + IKeyLookup lookup = KeyLookupFactory.getDefault(); + int modifierKeys = NO_KEY; + int naturalKey = NO_KEY; + int i = 0; + + auto tokens = tango.text.Util.delimit( string, KEY_DELIMITERS ); + foreach( idx, token; tokens ){ + + if (i % 2 is 0) { + if ( idx+1 < tokens.length ) { + token = token.toUpperCase(); + int modifierKey = lookup.formalModifierLookup(token); + if (modifierKey is NO_KEY) { + throw new ParseException( + "Cannot create key stroke with duplicate or non-existent modifier key: " //$NON-NLS-1$ + ~ token); + } + + modifierKeys |= modifierKey; + + } else if (token.length is 1) { + naturalKey = token.charAt(0); + + } else { + token = token.toUpperCase(); + naturalKey = lookup.formalKeyLookup(token); + } + } + + i++; + } + + return new KeyStroke(modifierKeys, naturalKey); + } + + /** + * An integer representation of the modifier keys; NO_KEY + * means that there is no modifier key. + */ + private const int modifierKeys; + + /** + * The natural key for this key stroke. This value is NO_KEY + * if the key stroke is incomplete (i.e., has no natural key). + */ + private const int naturalKey; + + /** + * Constructs an instance of KeyStroke given a set of + * modifier keys and a natural key. + * + * @param modifierKeys + * the modifier keys. The format of this integer is defined by + * whichever widget toolkit you are using; NO_KEY + * always means no modifier keys. + * @param naturalKey + * the natural key. The format of this integer is defined by + * whichever widget toolkit you are using; NO_KEY + * always means no natural key. + * @see SWTKeySupport + */ + private this(int modifierKeys, int naturalKey) { + this.modifierKeys = modifierKeys; + this.naturalKey = naturalKey; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public final int compareTo(Object object) { + KeyStroke keyStroke = cast(KeyStroke) object; + int compareTo = Util.compare(modifierKeys, keyStroke.modifierKeys); + + if (compareTo is 0) { + compareTo = Util.compare(naturalKey, keyStroke.naturalKey); + } + + return compareTo; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public final override int opEquals(Object object) { + if (!(cast(KeyStroke)object )) { + return false; + } + + KeyStroke keyStroke = cast(KeyStroke) object; + if (modifierKeys !is keyStroke.modifierKeys) { + return false; + } + + return (naturalKey is keyStroke.naturalKey); + } + + /** + * Formats this key stroke into the current default look. + * + * @return A string representation for this key stroke using the default + * look; never null. + */ + public final String format() { + return KeyFormatterFactory.getDefault().format(this); + } + + /** + * Returns the modifier keys for this key stroke. + * + * @return the bit mask of modifier keys; NO_KEY means that + * there is no modifier key. + */ + public final int getModifierKeys() { + return modifierKeys; + } + + /** + * Returns the natural key for this key stroke. + * + * @return The natural key for this key stroke. This value is + * NO_KEY if the key stroke is incomplete (i.e., has + * no natural key). + */ + public final int getNaturalKey() { + return naturalKey; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#hashCode() + */ + public final override hash_t toHash() { + return modifierKeys << 4 + naturalKey; + } + + /** + * Returns whether or not this key stroke is complete. Key strokes are + * complete iff they have a natural key which is not NO_KEY. + * + * @return true, iff the key stroke is complete. + */ + public final bool isComplete() { + return (naturalKey !is NO_KEY); + } + + /** + * Returns the formal string representation for this key stroke. + * + * @return The formal string representation for this key stroke. Guaranteed + * not to be null. + * @see java.lang.Object#toString() + */ + public final override String toString() { + return KeyFormatterFactory.getFormalKeyFormatter().format(this); + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/keys/ParseException.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/keys/ParseException.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2004, 2005 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 + *******************************************************************************/ + +module dwtx.jface.bindings.keys.ParseException; + +import dwt.dwthelper.utils; + +/** + *

+ * An exception indicating problems while parsing formal string representations + * of either KeyStroke or KeySequence objects. + *

+ *

+ * ParseException objects are immutable. Clients are not + * permitted to extend this class. + *

+ * + * @since 3.1 + */ +public final class ParseException : Exception { + + /** + * Generated serial version UID for this class. + * + * @since 3.1 + */ + private static const long serialVersionUID = 3257009864814376241L; + + /** + * Constructs a ParseException with the specified detail + * message. + * + * @param s + * the detail message. + */ + public this(String s) { + super(s); + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/keys/SWTKeyLookup.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/keys/SWTKeyLookup.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,379 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 + *******************************************************************************/ +module dwtx.jface.bindings.keys.SWTKeyLookup; + +import dwtx.jface.bindings.keys.IKeyLookup; + +import tango.util.collection.HashMap; +import tango.util.collection.model.Map; + +import dwt.DWT; +import dwtx.jface.util.Util; + +import dwt.dwthelper.utils; +import dwt.dwthelper.Integer; + +/** + *

+ * A look-up table for the formal grammar for keys, and the integer values they + * represent. This look-up table is hard-coded to use DWT representations. By + * replacing this class (and + * {@link dwtx.jface.bindings.keys.SWTKeySupport}), you can remove the + * dependency on DWT. + *

+ * + * @since 3.1 + * @see dwtx.jface.bindings.keys.KeyLookupFactory + */ +public final class SWTKeyLookup : IKeyLookup { + + /** + * The look-up table for modifier keys. This is a map of formal name (String) + * to integer value (Integer). + */ + private const Map!(String,Integer) modifierKeyTable; + + /** + * The look-up table for formal names. This is a map of integer value (Integer) + * to formal name (String). + */ + private const Map!(Integer,String) nameTable; + + /** + * The look-up table for natural keys. This is a map of formal name (String) + * to integer value (Integer). + */ + private const Map!(String,Integer) naturalKeyTable; + + /** + * Constructs a new look-up class. This should only be done by the look-up + * factory. + * + * @see KeyLookupFactory + */ + this() { + modifierKeyTable = new HashMap!(String,Integer); + nameTable = new HashMap!(Integer,String); + naturalKeyTable = new HashMap!(String,Integer); + Integer alt = new Integer(DWT.ALT); + Integer command = new Integer(DWT.COMMAND); + Integer ctrl = new Integer(DWT.CTRL); + Integer shift = new Integer(DWT.SHIFT); + modifierKeyTable.add(ALT_NAME, alt); + nameTable.add(alt, ALT_NAME); + modifierKeyTable.add(COMMAND_NAME, command); + nameTable.add(command, COMMAND_NAME); + modifierKeyTable.add(CTRL_NAME, ctrl); + nameTable.add(ctrl, CTRL_NAME); + modifierKeyTable.add(SHIFT_NAME, shift); + nameTable.add(shift, SHIFT_NAME); + modifierKeyTable.add(M1_NAME, + "carbon".equals(DWT.getPlatform()) ? command : ctrl); //$NON-NLS-1$ + modifierKeyTable.add(M2_NAME, shift); + modifierKeyTable.add(M3_NAME, alt); + modifierKeyTable.add(M4_NAME, "carbon".equals(DWT.getPlatform()) ? ctrl //$NON-NLS-1$ + : command); + + Integer arrowDown = new Integer(DWT.ARROW_DOWN); + naturalKeyTable.add(ARROW_DOWN_NAME, arrowDown); + nameTable.add(arrowDown, ARROW_DOWN_NAME); + Integer arrowLeft = new Integer(DWT.ARROW_LEFT); + naturalKeyTable.add(ARROW_LEFT_NAME, arrowLeft); + nameTable.add(arrowLeft, ARROW_LEFT_NAME); + Integer arrowRight = new Integer(DWT.ARROW_RIGHT); + naturalKeyTable.add(ARROW_RIGHT_NAME, arrowRight); + nameTable.add(arrowRight, ARROW_RIGHT_NAME); + Integer arrowUp = new Integer(DWT.ARROW_UP); + naturalKeyTable.add(ARROW_UP_NAME, arrowUp); + nameTable.add(arrowUp, ARROW_UP_NAME); + Integer breakKey = new Integer(DWT.BREAK); + naturalKeyTable.add(BREAK_NAME, breakKey); + nameTable.add(breakKey, BREAK_NAME); + Integer bs = new Integer(DWT.BS); + naturalKeyTable.add(BS_NAME, bs); + nameTable.add(bs, BS_NAME); + naturalKeyTable.add(BACKSPACE_NAME, bs); + Integer capsLock = new Integer(DWT.CAPS_LOCK); + naturalKeyTable.add(CAPS_LOCK_NAME, capsLock); + nameTable.add(capsLock, CAPS_LOCK_NAME); + Integer cr = new Integer(DWT.CR); + naturalKeyTable.add(CR_NAME, cr); + nameTable.add(cr, CR_NAME); + naturalKeyTable.add(ENTER_NAME, cr); + naturalKeyTable.add(RETURN_NAME, cr); + Integer del = new Integer(DWT.DEL); + naturalKeyTable.add(DEL_NAME, del); + nameTable.add(del, DEL_NAME); + naturalKeyTable.add(DELETE_NAME, del); + Integer end = new Integer(DWT.END); + naturalKeyTable.add(END_NAME, end); + nameTable.add(end, END_NAME); + Integer esc = new Integer(DWT.ESC); + naturalKeyTable.add(ESC_NAME, esc); + nameTable.add(esc, ESC_NAME); + naturalKeyTable.add(ESCAPE_NAME, esc); + Integer f1 = new Integer(DWT.F1); + naturalKeyTable.add(F1_NAME, f1); + nameTable.add(f1, F1_NAME); + Integer f2 = new Integer(DWT.F2); + naturalKeyTable.add(F2_NAME, new Integer(DWT.F2)); + nameTable.add(f2, F2_NAME); + Integer f3 = new Integer(DWT.F3); + naturalKeyTable.add(F3_NAME, new Integer(DWT.F3)); + nameTable.add(f3, F3_NAME); + Integer f4 = new Integer(DWT.F4); + naturalKeyTable.add(F4_NAME, new Integer(DWT.F4)); + nameTable.add(f4, F4_NAME); + Integer f5 = new Integer(DWT.F5); + naturalKeyTable.add(F5_NAME, new Integer(DWT.F5)); + nameTable.add(f5, F5_NAME); + Integer f6 = new Integer(DWT.F6); + naturalKeyTable.add(F6_NAME, new Integer(DWT.F6)); + nameTable.add(f6, F6_NAME); + Integer f7 = new Integer(DWT.F7); + naturalKeyTable.add(F7_NAME, new Integer(DWT.F7)); + nameTable.add(f7, F7_NAME); + Integer f8 = new Integer(DWT.F8); + naturalKeyTable.add(F8_NAME, new Integer(DWT.F8)); + nameTable.add(f8, F8_NAME); + Integer f9 = new Integer(DWT.F9); + naturalKeyTable.add(F9_NAME, new Integer(DWT.F9)); + nameTable.add(f9, F9_NAME); + Integer f10 = new Integer(DWT.F10); + naturalKeyTable.add(F10_NAME, new Integer(DWT.F10)); + nameTable.add(f10, F10_NAME); + Integer f11 = new Integer(DWT.F11); + naturalKeyTable.add(F11_NAME, new Integer(DWT.F11)); + nameTable.add(f11, F11_NAME); + Integer f12 = new Integer(DWT.F12); + naturalKeyTable.add(F12_NAME, new Integer(DWT.F12)); + nameTable.add(f12, F12_NAME); + Integer f13 = new Integer(DWT.F13); + naturalKeyTable.add(F13_NAME, new Integer(DWT.F13)); + nameTable.add(f13, F13_NAME); + Integer f14 = new Integer(DWT.F14); + naturalKeyTable.add(F14_NAME, new Integer(DWT.F14)); + nameTable.add(f14, F14_NAME); + Integer f15 = new Integer(DWT.F15); + naturalKeyTable.add(F15_NAME, new Integer(DWT.F15)); + nameTable.add(f15, F15_NAME); + Integer ff = new Integer(12); // ASCII 0x0C + naturalKeyTable.add(FF_NAME, ff); + nameTable.add(ff, FF_NAME); + Integer home = new Integer(DWT.HOME); + naturalKeyTable.add(HOME_NAME, home); + nameTable.add(home, HOME_NAME); + Integer insert = new Integer(DWT.INSERT); + naturalKeyTable.add(INSERT_NAME, insert); + nameTable.add(insert, INSERT_NAME); + Integer lf = new Integer(DWT.LF); + naturalKeyTable.add(LF_NAME, lf); + nameTable.add(lf, LF_NAME); + Integer nul = new Integer(DWT.NULL); + naturalKeyTable.add(NUL_NAME, nul); + nameTable.add(nul, NUL_NAME); + Integer numLock = new Integer(DWT.NUM_LOCK); + naturalKeyTable.add(NUM_LOCK_NAME, numLock); + nameTable.add(numLock, NUM_LOCK_NAME); + Integer keypad0 = new Integer(DWT.KEYPAD_0); + naturalKeyTable.add(NUMPAD_0_NAME, keypad0); + nameTable.add(keypad0, NUMPAD_0_NAME); + Integer keypad1 = new Integer(DWT.KEYPAD_1); + naturalKeyTable.add(NUMPAD_1_NAME, keypad1); + nameTable.add(keypad1, NUMPAD_1_NAME); + Integer keypad2 = new Integer(DWT.KEYPAD_2); + naturalKeyTable.add(NUMPAD_2_NAME, keypad2); + nameTable.add(keypad2, NUMPAD_2_NAME); + Integer keypad3 = new Integer(DWT.KEYPAD_3); + naturalKeyTable.add(NUMPAD_3_NAME, keypad3); + nameTable.add(keypad3, NUMPAD_3_NAME); + Integer keypad4 = new Integer(DWT.KEYPAD_4); + naturalKeyTable.add(NUMPAD_4_NAME, keypad4); + nameTable.add(keypad4, NUMPAD_4_NAME); + Integer keypad5 = new Integer(DWT.KEYPAD_5); + naturalKeyTable.add(NUMPAD_5_NAME, keypad5); + nameTable.add(keypad5, NUMPAD_5_NAME); + Integer keypad6 = new Integer(DWT.KEYPAD_6); + naturalKeyTable.add(NUMPAD_6_NAME, keypad6); + nameTable.add(keypad6, NUMPAD_6_NAME); + Integer keypad7 = new Integer(DWT.KEYPAD_7); + naturalKeyTable.add(NUMPAD_7_NAME, keypad7); + nameTable.add(keypad7, NUMPAD_7_NAME); + Integer keypad8 = new Integer(DWT.KEYPAD_8); + naturalKeyTable.add(NUMPAD_8_NAME, keypad8); + nameTable.add(keypad8, NUMPAD_8_NAME); + Integer keypad9 = new Integer(DWT.KEYPAD_9); + naturalKeyTable.add(NUMPAD_9_NAME, keypad9); + nameTable.add(keypad9, NUMPAD_9_NAME); + Integer keypadAdd = new Integer(DWT.KEYPAD_ADD); + naturalKeyTable.add(NUMPAD_ADD_NAME, keypadAdd); + nameTable.add(keypadAdd, NUMPAD_ADD_NAME); + Integer keypadDecimal = new Integer(DWT.KEYPAD_DECIMAL); + naturalKeyTable.add(NUMPAD_DECIMAL_NAME, keypadDecimal); + nameTable.add(keypadDecimal, NUMPAD_DECIMAL_NAME); + Integer keypadDivide = new Integer(DWT.KEYPAD_DIVIDE); + naturalKeyTable.add(NUMPAD_DIVIDE_NAME, keypadDivide); + nameTable.add(keypadDivide, NUMPAD_DIVIDE_NAME); + Integer keypadCr = new Integer(DWT.KEYPAD_CR); + naturalKeyTable.add(NUMPAD_ENTER_NAME, keypadCr); + nameTable.add(keypadCr, NUMPAD_ENTER_NAME); + Integer keypadEqual = new Integer(DWT.KEYPAD_EQUAL); + naturalKeyTable.add(NUMPAD_EQUAL_NAME, keypadEqual); + nameTable.add(keypadEqual, NUMPAD_EQUAL_NAME); + Integer keypadMultiply = new Integer(DWT.KEYPAD_MULTIPLY); + naturalKeyTable.add(NUMPAD_MULTIPLY_NAME, keypadMultiply); + nameTable.add(keypadMultiply, NUMPAD_MULTIPLY_NAME); + Integer keypadSubtract = new Integer(DWT.KEYPAD_SUBTRACT); + naturalKeyTable.add(NUMPAD_SUBTRACT_NAME, keypadSubtract); + nameTable.add(keypadSubtract, NUMPAD_SUBTRACT_NAME); + Integer pageDown = new Integer(DWT.PAGE_DOWN); + naturalKeyTable.add(PAGE_DOWN_NAME, pageDown); + nameTable.add(pageDown, PAGE_DOWN_NAME); + Integer pageUp = new Integer(DWT.PAGE_UP); + naturalKeyTable.add(PAGE_UP_NAME, pageUp); + nameTable.add(pageUp, PAGE_UP_NAME); + Integer pause = new Integer(DWT.PAUSE); + naturalKeyTable.add(PAUSE_NAME, pause); + nameTable.add(pause, PAUSE_NAME); + Integer printScreen = new Integer(DWT.PRINT_SCREEN); + naturalKeyTable.add(PRINT_SCREEN_NAME, printScreen); + nameTable.add(printScreen, PRINT_SCREEN_NAME); + Integer scrollLock = new Integer(DWT.SCROLL_LOCK); + naturalKeyTable.add(SCROLL_LOCK_NAME, scrollLock); + nameTable.add(scrollLock, SCROLL_LOCK_NAME); + Integer space = new Integer(' '); + naturalKeyTable.add(SPACE_NAME, space); + nameTable.add(space, SPACE_NAME); + Integer tab = new Integer(DWT.TAB); + naturalKeyTable.add(TAB_NAME, tab); + nameTable.add(tab, TAB_NAME); + Integer vt = new Integer(11); // ASCII 0x0B + naturalKeyTable.add(VT_NAME, vt); + nameTable.add(vt, VT_NAME); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.IKeyLookup#formalKeyLookup(java.lang.String) + * + */ + public final int formalKeyLookup(String name) { + Object value = naturalKeyTable.get(name); + if (cast(Integer)value ) { + return (cast(Integer) value).intValue(); + } + + if (name.length > 0) { + throw new IllegalArgumentException("Unrecognized formal key name: " //$NON-NLS-1$ + ~ name); + } + + return name.charAt(0); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.IKeyLookup#formalKeyLookupInteger(java.lang.String) + * + */ + public final Integer formalKeyLookupInteger(String name) { + Object value = naturalKeyTable.get(name); + if (cast(Integer)value ) { + return cast(Integer) value; + } + + return new Integer(name.charAt(0)); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.IKeyLookup#formalModifierLookup(java.lang.String) + * + */ + public final int formalModifierLookup(String name) { + Object value = modifierKeyTable.get(name); + if (cast(Integer)value ) { + return (cast(Integer) value).intValue(); + } + + return 0; + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.IKeyLookup#formalNameLookup(int) + * + */ + public final String formalNameLookup(int key) { + Integer keyObject = new Integer(key); + if (nameTable.containsKey(keyObject) ) { + return nameTable.get(keyObject); + } + + return dcharToString( cast(dchar) key ); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.IKeyLookup#getAlt() + * + */ + public final int getAlt() { + return DWT.ALT; + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.IKeyLookup#getCommand() + * + */ + public final int getCommand() { + return DWT.COMMAND; + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.IKeyLookup#getCtrl() + * + */ + public final int getCtrl() { + return DWT.CTRL; + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.IKeyLookup#getShift() + * + */ + public final int getShift() { + return DWT.SHIFT; + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.IKeyLookup#isModifierKey(int) + * + */ + public final bool isModifierKey(int key) { + return ((key & DWT.MODIFIER_MASK) !is 0); + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/keys/SWTKeySupport.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/keys/SWTKeySupport.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,255 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 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 + *******************************************************************************/ + +module dwtx.jface.bindings.keys.SWTKeySupport; + +import dwtx.jface.bindings.keys.KeyStroke; + +import dwt.DWT; +import dwt.events.KeyEvent; +import dwt.widgets.Event; +import dwtx.jface.bindings.keys.formatting.IKeyFormatter; +import dwtx.jface.bindings.keys.formatting.NativeKeyFormatter; + +import dwt.dwthelper.utils; + +/** + *

+ * A utility class for converting DWT events into key strokes. + *

+ * + * @since 3.1 + */ +public final class SWTKeySupport { + + /** + * A formatter that displays key sequences in a style native to the + * platform. + */ + private static const IKeyFormatter NATIVE_FORMATTER; + + static this(){ + NATIVE_FORMATTER = new NativeKeyFormatter(); + } + + /** + * Given an DWT accelerator value, provide the corresponding key stroke. + * + * @param accelerator + * The accelerator to convert; should be a valid DWT accelerator + * value. + * @return The equivalent key stroke; never null. + */ + public static final KeyStroke convertAcceleratorToKeyStroke(int accelerator) { + int modifierKeys = accelerator & DWT.MODIFIER_MASK; + int naturalKey; + if (accelerator is modifierKeys) { + naturalKey = KeyStroke.NO_KEY; + } else { + naturalKey = accelerator - modifierKeys; + } + + return KeyStroke.getInstance(modifierKeys, naturalKey); + } + + /** + *

+ * Converts the given event into an DWT accelerator value -- considering the + * modified character with the shift modifier. This is the third accelerator + * value that should be checked when processing incoming key events. + *

+ *

+ * For example, on a standard US keyboard, "Ctrl+Shift+5" would be viewed as + * "Ctrl+Shift+%". + *

+ * + * @param event + * The event to be converted; must not be null. + * @return The combination of the state mask and the unmodified character. + */ + public static final int convertEventToModifiedAccelerator(Event event) { + int modifiers = event.stateMask & DWT.MODIFIER_MASK; + char character = topKey(event); + return modifiers + toUpperCase(character); + } + + /** + *

+ * Converts the given event into an DWT accelerator value -- considering the + * unmodified character with all modifier keys. This is the first + * accelerator value that should be checked when processing incoming key + * events. However, all alphabetic characters are considered as their + * uppercase equivalents. + *

+ *

+ * For example, on a standard US keyboard, "Ctrl+Shift+5" would be viewed as + * "Ctrl+Shift+5". + *

+ * + * @param event + * The event to be converted; must not be null. + * @return The combination of the state mask and the unmodified character. + */ + public static final int convertEventToUnmodifiedAccelerator( + Event event) { + return convertEventToUnmodifiedAccelerator(event.stateMask, + event.keyCode); + } + + /** + *

+ * Converts the given state mask and key code into an DWT accelerator value -- + * considering the unmodified character with all modifier keys. All + * alphabetic characters are considered as their uppercase equivalents. + *

+ *

+ * For example, on a standard US keyboard, "Ctrl+Shift+5" would be viewed as + * "Ctrl+Shift+5". + *

+ * + * @param stateMask + * The integer mask of modifiers keys depressed when this was + * pressed. + * @param keyCode + * The key that was pressed, before being modified. + * @return The combination of the state mask and the unmodified character. + */ + private static final int convertEventToUnmodifiedAccelerator( + int stateMask, int keyCode) { + int modifiers = stateMask & DWT.MODIFIER_MASK; + int character = keyCode; + return modifiers + toUpperCase(character); + } + + /** + *

+ * Converts the given event into an DWT accelerator value -- considering the + * unmodified character with all modifier keys. This is the first + * accelerator value that should be checked. However, all alphabetic + * characters are considered as their uppercase equivalents. + *

+ *

+ * For example, on a standard US keyboard, "Ctrl+Shift+5" would be viewed as + * "Ctrl+%". + *

+ * + * @param event + * The event to be converted; must not be null. + * @return The combination of the state mask and the unmodified character. + */ + public static final int convertEventToUnmodifiedAccelerator( + KeyEvent event) { + return convertEventToUnmodifiedAccelerator(event.stateMask, + event.keyCode); + } + + /** + * Converts the given event into an DWT accelerator value -- considering the + * modified character without the shift modifier. This is the second + * accelerator value that should be checked when processing incoming key + * events. Key strokes with alphabetic natural keys are run through + * convertEventToUnmodifiedAccelerator. + * + * @param event + * The event to be converted; must not be null. + * @return The combination of the state mask without shift, and the modified + * character. + */ + public static final int convertEventToUnshiftedModifiedAccelerator( + Event event) { + // Disregard alphabetic key strokes. + if (CharacterIsLetter(cast(dchar) event.keyCode)) { + return convertEventToUnmodifiedAccelerator(event); + } + + int modifiers = event.stateMask & (DWT.MODIFIER_MASK ^ DWT.SHIFT); + char character = topKey(event); + return modifiers + toUpperCase(character); + } + + /** + * Given a key stroke, this method provides the equivalent DWT accelerator + * value. The functional inverse of + * convertAcceleratorToKeyStroke. + * + * @param keyStroke + * The key stroke to convert; must not be null. + * @return The DWT accelerator value + */ + public static final int convertKeyStrokeToAccelerator( + KeyStroke keyStroke) { + return keyStroke.getModifierKeys() + keyStroke.getNaturalKey(); + } + + /** + * Provides an instance of IKeyFormatter appropriate for the + * current instance. + * + * @return an instance of IKeyFormatter appropriate for the + * current instance; never null. + */ + public static IKeyFormatter getKeyFormatterForPlatform() { + return NATIVE_FORMATTER; + } + + /** + * Makes sure that a fully-modified character is converted to the normal + * form. This means that "Ctrl+" key strokes must reverse the modification + * caused by control-escaping. Also, all lower case letters are converted to + * uppercase. + * + * @param event + * The event from which the fully-modified character should be + * pulled. + * @return The modified character, uppercase and without control-escaping. + */ + private static final char topKey(Event event) { + char character = event.character; + bool ctrlDown = (event.stateMask & DWT.CTRL) !is 0; + + if (ctrlDown && event.character !is event.keyCode + && event.character < 0x20 + && (event.keyCode & DWT.KEYCODE_BIT) is 0) { + character += 0x40; + } + + return character; + } + + /** + * Makes the given character uppercase if it is a letter. + * + * @param keyCode + * The character to convert. + * @return The uppercase equivalent, if any; otherwise, the character + * itself. + */ + private static final int toUpperCase(int keyCode) { + // Will this key code be truncated? + if (keyCode > 0xFFFF) { + return keyCode; + } + + // Downcast in safety. Only make characters uppercase. + char character = cast(char) keyCode; + return CharacterIsLetter(character) ? CharacterToUpper(character) + : keyCode; + } + + /** + * This class should never be instantiated. + */ + protected this() { + // This class should never be instantiated. + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/keys/formatting/AbstractKeyFormatter.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/keys/formatting/AbstractKeyFormatter.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,182 @@ +/******************************************************************************* + * Copyright (c) 2004, 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 + *******************************************************************************/ + +module dwtx.jface.bindings.keys.formatting.AbstractKeyFormatter; + +import dwtx.jface.bindings.keys.formatting.IKeyFormatter; + +import tango.util.collection.HashSet; +import tango.util.collection.model.Set; + +import dwtx.jface.bindings.keys.IKeyLookup; +import dwtx.jface.bindings.keys.KeyLookupFactory; +import dwtx.jface.bindings.keys.KeySequence; +import dwtx.jface.bindings.keys.KeyStroke; +import dwtx.jface.util.Util; + +import dwt.dwthelper.utils; +import dwt.dwthelper.ResourceBundle; + +/** + *

+ * An abstract implementation of a key formatter that provides a lot of common + * key formatting functionality. It is recommended that implementations of + * IKeyFormatter subclass from here, rather than implementing + * IKeyFormatter directly. + *

+ * + * @since 3.1 + */ +public abstract class AbstractKeyFormatter : IKeyFormatter { + + /** + * The key for the delimiter between keys. This is used in the + * internationalization bundles. + */ + protected static const String KEY_DELIMITER_KEY = "KEY_DELIMITER"; //$NON-NLS-1$ + + /** + * The key for the delimiter between key strokes. This is used in the + * internationalization bundles. + */ + protected static const String KEY_STROKE_DELIMITER_KEY = "KEY_STROKE_DELIMITER"; //$NON-NLS-1$ + + /** + * An empty integer array that can be used in + * sortModifierKeys(int). + */ + protected static const int[] NO_MODIFIER_KEYS = null; + + /** + * The bundle in which to look up the internationalized text for all of the + * individual keys in the system. This is the platform-agnostic version of + * the internationalized strings. Some platforms (namely Carbon) provide + * special Unicode characters and glyphs for some keys. + */ + private static const ResourceBundle RESOURCE_BUNDLE; + + /** + * The keys in the resource bundle. This is used to avoid missing resource + * exceptions when they aren't necessary. + */ + private static const Set!(String) resourceBundleKeys; + + static this() { + RESOURCE_BUNDLE = ResourceBundle.getBundle(AbstractKeyFormatter.classinfo.name); + resourceBundleKeys = new HashSet!(String); + foreach( key; RESOURCE_BUNDLE.getKeys()){ + resourceBundleKeys.add(key); + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keysKeyFormatter#format(dwtx.jface.bindings.keys.KeySequence) + */ + public String format(int key) { + IKeyLookup lookup = KeyLookupFactory.getDefault(); + String name = lookup.formalNameLookup(key); + + if (resourceBundleKeys.contains(name)) { + return Util.translateString(RESOURCE_BUNDLE, name, name); + } + + return name; + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.KeyFormatter#format(dwtx.jface.bindings.keys.KeySequence) + */ + public String format(KeySequence keySequence) { + StringBuffer stringBuffer = new StringBuffer(); + + KeyStroke[] keyStrokes = keySequence.getKeyStrokes(); + int keyStrokesLength = keyStrokes.length; + for (int i = 0; i < keyStrokesLength; i++) { + stringBuffer.append(format(keyStrokes[i])); + + if (i + 1 < keyStrokesLength) { + stringBuffer.append(getKeyStrokeDelimiter()); + } + } + + return stringBuffer.toString(); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.KeyFormatter#formatKeyStroke(dwtx.jface.bindings.keys.KeyStroke) + */ + public String format(KeyStroke keyStroke) { + String keyDelimiter = getKeyDelimiter(); + + // Format the modifier keys, in sorted order. + int modifierKeys = keyStroke.getModifierKeys(); + int[] sortedModifierKeys = sortModifierKeys(modifierKeys); + StringBuffer stringBuffer = new StringBuffer(); + if (sortedModifierKeys !is null) { + for (int i = 0; i < sortedModifierKeys.length; i++) { + int modifierKey = sortedModifierKeys[i]; + if (modifierKey !is KeyStroke.NO_KEY) { + stringBuffer.append(format(modifierKey)); + stringBuffer.append(keyDelimiter); + } + } + } + + // Format the natural key, if any. + int naturalKey = keyStroke.getNaturalKey(); + if (naturalKey !is 0) { + stringBuffer.append(format(naturalKey)); + } + + return stringBuffer.toString(); + + } + + /** + * An accessor for the delimiter you wish to use between keys. This is used + * by the default format implementations to determine the key delimiter. + * + * @return The delimiter to use between keys; should not be + * null. + */ + protected abstract String getKeyDelimiter(); + + /** + * An accessor for the delimiter you wish to use between key strokes. This + * used by the default format implementations to determine the key stroke + * delimiter. + * + * @return The delimiter to use between key strokes; should not be + * null. + */ + protected abstract String getKeyStrokeDelimiter(); + + /** + * Separates the modifier keys from each other, and then places them in an + * array in some sorted order. The sort order is dependent on the type of + * formatter. + * + * @param modifierKeys + * The modifier keys from the key stroke. + * @return An array of modifier key values -- separated and sorted in some + * order. Any values in this array that are + * KeyStroke.NO_KEY should be ignored. + */ + protected abstract int[] sortModifierKeys(int modifierKeys); +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/keys/formatting/EmacsKeyFormatter.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/keys/formatting/EmacsKeyFormatter.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2004, 2005 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 + *******************************************************************************/ + +module dwtx.jface.bindings.keys.formatting.EmacsKeyFormatter; + +import dwtx.jface.bindings.keys.formatting.AbstractKeyFormatter; + +import dwtx.jface.bindings.keys.IKeyLookup; +import dwtx.jface.bindings.keys.KeyLookupFactory; +import dwtx.jface.bindings.keys.KeySequence; +import dwtx.jface.bindings.keys.KeyStroke; +import dwtx.jface.util.Util; + +import dwt.dwthelper.utils; +import dwt.dwthelper.ResourceBundle; + +/** + *

+ * A key formatter providing the Emacs-style accelerators using single letters + * to represent the modifier keys. + *

+ * + * @since 3.1 + */ +public final class EmacsKeyFormatter : AbstractKeyFormatter { + + /** + * The resource bundle used by format() to translate formal + * string representations by locale. + */ + private const static ResourceBundle RESOURCE_BUNDLE; + + static this(){ + RESOURCE_BUNDLE = ResourceBundle.getBundle(EmacsKeyFormatter.classinfo.name); + } + /** + * Formats an individual key into a human readable format. This converts the + * key into a format similar to Xemacs. + * + * @param key + * The key to format; must not be null. + * @return The key formatted as a string; should not be null. + */ + public String format(int key) { + IKeyLookup lookup = KeyLookupFactory.getDefault(); + if (lookup.isModifierKey(key)) { + String formattedName = Util.translateString(RESOURCE_BUNDLE, lookup + .formalNameLookup(key), null); + if (formattedName !is null) { + return formattedName; + } + } + + return super.format(key).toLowerCase(); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.AbstractKeyFormatter#getKeyDelimiter() + */ + protected String getKeyDelimiter() { + return Util.translateString(RESOURCE_BUNDLE, KEY_DELIMITER_KEY, + KeyStroke.KEY_DELIMITER); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.AbstractKeyFormatter#getKeyStrokeDelimiter() + */ + protected String getKeyStrokeDelimiter() { + return Util.translateString(RESOURCE_BUNDLE, KEY_STROKE_DELIMITER_KEY, + KeySequence.KEY_STROKE_DELIMITER); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.AbstractKeyFormatter#sortModifierKeys(int) + */ + protected int[] sortModifierKeys(int modifierKeys) { + IKeyLookup lookup = KeyLookupFactory.getDefault(); + int[] sortedKeys = new int[4]; + int index = 0; + + if ((modifierKeys & lookup.getAlt()) !is 0) { + sortedKeys[index++] = lookup.getAlt(); + } + if ((modifierKeys & lookup.getCommand()) !is 0) { + sortedKeys[index++] = lookup.getCommand(); + } + if ((modifierKeys & lookup.getCtrl()) !is 0) { + sortedKeys[index++] = lookup.getCtrl(); + } + if ((modifierKeys & lookup.getShift()) !is 0) { + sortedKeys[index++] = lookup.getShift(); + } + + return sortedKeys; + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/keys/formatting/FormalKeyFormatter.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/keys/formatting/FormalKeyFormatter.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2004, 2005 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 + *******************************************************************************/ + +module dwtx.jface.bindings.keys.formatting.FormalKeyFormatter; + +import dwtx.jface.bindings.keys.formatting.AbstractKeyFormatter; + +import dwtx.jface.bindings.keys.IKeyLookup; +import dwtx.jface.bindings.keys.KeyLookupFactory; +import dwtx.jface.bindings.keys.KeySequence; +import dwtx.jface.bindings.keys.KeyStroke; + +import dwt.dwthelper.utils; + +/** + *

+ * Formats the keys in the internal key sequence grammar. This is used for + * persistence, and is not really intended for display to the user. + *

+ * + * @since 3.1 + */ +public final class FormalKeyFormatter : AbstractKeyFormatter { + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.KeyFormatter#format(dwtx.ui.keys.KeySequence) + */ + public String format(int key) { + IKeyLookup lookup = KeyLookupFactory.getDefault(); + return lookup.formalNameLookup(key); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.AbstractKeyFormatter#getKeyDelimiter() + */ + protected String getKeyDelimiter() { + return KeyStroke.KEY_DELIMITER; + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.AbstractKeyFormatter#getKeyStrokeDelimiter() + */ + protected String getKeyStrokeDelimiter() { + return KeySequence.KEY_STROKE_DELIMITER; + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.AbstractKeyFormatter#sortModifierKeys(int) + */ + protected int[] sortModifierKeys(int modifierKeys) { + IKeyLookup lookup = KeyLookupFactory.getDefault(); + int[] sortedKeys = new int[4]; + int index = 0; + + if ((modifierKeys & lookup.getAlt()) !is 0) { + sortedKeys[index++] = lookup.getAlt(); + } + if ((modifierKeys & lookup.getCommand()) !is 0) { + sortedKeys[index++] = lookup.getCommand(); + } + if ((modifierKeys & lookup.getCtrl()) !is 0) { + sortedKeys[index++] = lookup.getCtrl(); + } + if ((modifierKeys & lookup.getShift()) !is 0) { + sortedKeys[index++] = lookup.getShift(); + } + + return sortedKeys; + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/keys/formatting/IKeyFormatter.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/keys/formatting/IKeyFormatter.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2004, 2005 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 + *******************************************************************************/ + +module dwtx.jface.bindings.keys.formatting.IKeyFormatter; + +import dwtx.jface.bindings.keys.KeySequence; +import dwtx.jface.bindings.keys.KeyStroke; + +import dwt.dwthelper.utils; + +/** + *

+ * Any formatter capable of taking a key sequence or a key stroke and converting + * it into a string. These formatters are used to produce the strings that the + * user sees in the keys preference page and the menus, as well as the strings + * that are used for persistent storage. + *

+ * + * @since 3.1 + */ +public interface IKeyFormatter { + + /** + * Formats an individual key into a human readable format. This uses an + * internationalization resource bundle to look up the key. This does not do + * any platform-specific formatting (e.g., Carbon's command character). + * + * @param key + * The key to format. + * @return The key formatted as a string; should not be null. + */ + String format(int key); + + /** + * Format the given key sequence into a string. The manner of the conversion + * is dependent on the formatter. It is required that unequal key sequences + * return unequal strings. + * + * @param keySequence + * The key sequence to convert; must not be null. + * @return A string representation of the key sequence; must not be + * null. + */ + String format(KeySequence keySequence); + + /** + * Format the given key strokes into a string. The manner of the conversion + * is dependent on the formatter. It is required that unequal key strokes + * return unequal strings. + * + * @param keyStroke + * The key stroke to convert; must not be null. + * @return A string representation of the key stroke; must not be + * null + */ + String format(KeyStroke keyStroke); +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/keys/formatting/KeyFormatterFactory.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/keys/formatting/KeyFormatterFactory.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 2004, 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 + *******************************************************************************/ + +module dwtx.jface.bindings.keys.formatting.KeyFormatterFactory; + +import dwtx.jface.bindings.keys.formatting.FormalKeyFormatter; +import dwtx.jface.bindings.keys.formatting.EmacsKeyFormatter; +import dwtx.jface.bindings.keys.formatting.IKeyFormatter; + +import dwt.dwthelper.utils; + +/** + *

+ * A cache for formatters. It keeps a few instances of pre-defined instances of + * IKeyFormatter available for use. It also allows the default + * formatter to be changed. + *

+ * + * @since 3.1 + * @see dwtx.jface.bindings.keys.formatting.IKeyFormatter + */ +public final class KeyFormatterFactory { + + /** + * The formatter that renders key bindings in a platform-dependent manner. + */ + private static const IKeyFormatter FORMAL_KEY_FORMATTER; + + /** + * The formatter that renders key bindings in a form similar to XEmacs + */ + private static const IKeyFormatter EMACS_KEY_FORMATTER; + + /** + * The default formatter. This is normally the formal key formatter, but can + * be changed by users of this API. + */ + private static IKeyFormatter defaultKeyFormatter; + + static this(){ + FORMAL_KEY_FORMATTER = new FormalKeyFormatter(); + EMACS_KEY_FORMATTER = new EmacsKeyFormatter(); + defaultKeyFormatter = FORMAL_KEY_FORMATTER; + } + + /** + * An accessor for the current default key formatter. + * + * @return The default formatter; never null. + */ + public static final IKeyFormatter getDefault() { + return defaultKeyFormatter; + } + + /** + * Provides an instance of EmacsKeyFormatter. + * + * @return The Xemacs formatter; never null. + */ + public static final IKeyFormatter getEmacsKeyFormatter() { + return EMACS_KEY_FORMATTER; + } + + /** + * Provides an instance of FormalKeyFormatter. + * + * @return The formal formatter; never null. + */ + public static final IKeyFormatter getFormalKeyFormatter() { + return FORMAL_KEY_FORMATTER; + } + + /** + * Sets the default key formatter. + * + * @param defaultKeyFormatter + * the default key formatter. Must not be null. + */ + public static final void setDefault(IKeyFormatter defaultKeyFormatter) { + if (defaultKeyFormatter is null) { + throw new NullPointerException(); + } + + KeyFormatterFactory.defaultKeyFormatter = defaultKeyFormatter; + } + + /** + * This class should not be instantiated. + */ + private this() { + // Not to be constructred. + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/bindings/keys/formatting/NativeKeyFormatter.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/bindings/keys/formatting/NativeKeyFormatter.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,199 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 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 + *******************************************************************************/ + +module dwtx.jface.bindings.keys.formatting.NativeKeyFormatter; + +import dwtx.jface.bindings.keys.formatting.AbstractKeyFormatter; + +import tango.util.collection.HashMap; + +import dwt.DWT; +import dwtx.jface.bindings.keys.IKeyLookup; +import dwtx.jface.bindings.keys.KeyLookupFactory; +import dwtx.jface.bindings.keys.KeySequence; +import dwtx.jface.bindings.keys.KeyStroke; +import dwtx.jface.util.Util; + +import dwt.dwthelper.utils; +import dwt.dwthelper.ResourceBundle; + +/** + *

+ * Formats the key sequences and key strokes into the native human-readable + * format. This is typically what you would see on the menus for the given + * platform and locale. + *

+ * + * @since 3.1 + */ +public final class NativeKeyFormatter : AbstractKeyFormatter { + + /** + * The key into the internationalization resource bundle for the delimiter + * to use between keys (on the Carbon platform). + */ + private const static String CARBON_KEY_DELIMITER_KEY = "CARBON_KEY_DELIMITER"; //$NON-NLS-1$ + + /** + * A look-up table for the string representations of various carbon keys. + */ + private const static HashMap!(String,String) CARBON_KEY_LOOK_UP; + + /** + * The resource bundle used by format() to translate formal + * string representations by locale. + */ + private const static ResourceBundle RESOURCE_BUNDLE; + + /** + * The key into the internationalization resource bundle for the delimiter + * to use between key strokes (on the Win32 platform). + */ + private const static String WIN32_KEY_STROKE_DELIMITER_KEY = "WIN32_KEY_STROKE_DELIMITER"; //$NON-NLS-1$ + + static this() { + CARBON_KEY_LOOK_UP = new HashMap!(String,String); + RESOURCE_BUNDLE = ResourceBundle.getBundle(NativeKeyFormatter.classinfo.name); + + String carbonBackspace = "\u232B"; //$NON-NLS-1$ + CARBON_KEY_LOOK_UP.add(IKeyLookup.BS_NAME, carbonBackspace); + CARBON_KEY_LOOK_UP.add(IKeyLookup.BACKSPACE_NAME, carbonBackspace); + CARBON_KEY_LOOK_UP.add(IKeyLookup.CR_NAME, "\u21A9"); //$NON-NLS-1$ + String carbonDelete = "\u2326"; //$NON-NLS-1$ + CARBON_KEY_LOOK_UP.add(IKeyLookup.DEL_NAME, carbonDelete); + CARBON_KEY_LOOK_UP.add(IKeyLookup.DELETE_NAME, carbonDelete); + CARBON_KEY_LOOK_UP.add(IKeyLookup.SPACE_NAME, "\u2423"); //$NON-NLS-1$ + CARBON_KEY_LOOK_UP.add(IKeyLookup.ALT_NAME, "\u2325"); //$NON-NLS-1$ + CARBON_KEY_LOOK_UP.add(IKeyLookup.COMMAND_NAME, "\u2318"); //$NON-NLS-1$ + CARBON_KEY_LOOK_UP.add(IKeyLookup.CTRL_NAME, "\u2303"); //$NON-NLS-1$ + CARBON_KEY_LOOK_UP.add(IKeyLookup.SHIFT_NAME, "\u21E7"); //$NON-NLS-1$ + CARBON_KEY_LOOK_UP.add(IKeyLookup.ARROW_DOWN_NAME, "\u2193"); //$NON-NLS-1$ + CARBON_KEY_LOOK_UP.add(IKeyLookup.ARROW_LEFT_NAME, "\u2190"); //$NON-NLS-1$ + CARBON_KEY_LOOK_UP.add(IKeyLookup.ARROW_RIGHT_NAME, "\u2192"); //$NON-NLS-1$ + CARBON_KEY_LOOK_UP.add(IKeyLookup.ARROW_UP_NAME, "\u2191"); //$NON-NLS-1$ + CARBON_KEY_LOOK_UP.add(IKeyLookup.END_NAME, "\u2198"); //$NON-NLS-1$ + CARBON_KEY_LOOK_UP.add(IKeyLookup.NUMPAD_ENTER_NAME, "\u2324"); //$NON-NLS-1$ + CARBON_KEY_LOOK_UP.add(IKeyLookup.HOME_NAME, "\u2196"); //$NON-NLS-1$ + CARBON_KEY_LOOK_UP.add(IKeyLookup.PAGE_DOWN_NAME, "\u21DF"); //$NON-NLS-1$ + CARBON_KEY_LOOK_UP.add(IKeyLookup.PAGE_UP_NAME, "\u21DE"); //$NON-NLS-1$ + } + + /** + * Formats an individual key into a human readable format. This uses an + * internationalization resource bundle to look up the key. This does the + * platform-specific formatting for Carbon. + * + * @param key + * The key to format. + * @return The key formatted as a string; should not be null. + */ + public final String format(int key) { + IKeyLookup lookup = KeyLookupFactory.getDefault(); + String name = lookup.formalNameLookup(key); + + // TODO consider platform-specific resource bundles + if ("carbon".equals(DWT.getPlatform())) { //$NON-NLS-1$ + String formattedName = cast(String) CARBON_KEY_LOOK_UP.get(name); + if (formattedName !is null) { + return formattedName; + } + } + + return super.format(key); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.AbstractKeyFormatter#getKeyDelimiter() + */ + protected String getKeyDelimiter() { + // We must do the look up every time, as our locale might change. + if ("carbon".equals(DWT.getPlatform())) { //$NON-NLS-1$ + return Util.translateString(RESOURCE_BUNDLE, + CARBON_KEY_DELIMITER_KEY, Util.ZERO_LENGTH_STRING); + } + + return Util.translateString(RESOURCE_BUNDLE, KEY_DELIMITER_KEY, + KeyStroke.KEY_DELIMITER); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.AbstractKeyFormatter#getKeyStrokeDelimiter() + */ + protected String getKeyStrokeDelimiter() { + // We must do the look up every time, as our locale might change. + if ("win32".equals(DWT.getPlatform())) { //$NON-NLS-1$ + return Util.translateString(RESOURCE_BUNDLE, + WIN32_KEY_STROKE_DELIMITER_KEY, + KeySequence.KEY_STROKE_DELIMITER); + } + + return Util.translateString(RESOURCE_BUNDLE, KEY_STROKE_DELIMITER_KEY, + KeySequence.KEY_STROKE_DELIMITER); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.bindings.keys.AbstractKeyFormatter#sortModifierKeys(int) + */ + protected int[] sortModifierKeys(int modifierKeys) { + IKeyLookup lookup = KeyLookupFactory.getDefault(); + String platform = DWT.getPlatform(); + int[] sortedKeys = new int[4]; + int index = 0; + + if ("win32".equals(platform) || "wpf".equals(platform)) { //$NON-NLS-1$ //$NON-NLS-2$ + if ((modifierKeys & lookup.getCtrl()) !is 0) { + sortedKeys[index++] = lookup.getCtrl(); + } + if ((modifierKeys & lookup.getAlt()) !is 0) { + sortedKeys[index++] = lookup.getAlt(); + } + if ((modifierKeys & lookup.getShift()) !is 0) { + sortedKeys[index++] = lookup.getShift(); + } + + } else if ("gtk".equals(platform) || "motif".equals(platform)) { //$NON-NLS-1$ //$NON-NLS-2$ + if ((modifierKeys & lookup.getShift()) !is 0) { + sortedKeys[index++] = lookup.getShift(); + } + if ((modifierKeys & lookup.getCtrl()) !is 0) { + sortedKeys[index++] = lookup.getCtrl(); + } + if ((modifierKeys & lookup.getAlt()) !is 0) { + sortedKeys[index++] = lookup.getAlt(); + } + + } else if ("carbon".equals(platform)) { //$NON-NLS-1$ + if ((modifierKeys & lookup.getShift()) !is 0) { + sortedKeys[index++] = lookup.getShift(); + } + if ((modifierKeys & lookup.getCtrl()) !is 0) { + sortedKeys[index++] = lookup.getCtrl(); + } + if ((modifierKeys & lookup.getAlt()) !is 0) { + sortedKeys[index++] = lookup.getAlt(); + } + if ((modifierKeys & lookup.getCommand()) !is 0) { + sortedKeys[index++] = lookup.getCommand(); + } + + } + + return sortedKeys; + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/dialogs/PopupDialog.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/dialogs/PopupDialog.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,1244 @@ +/******************************************************************************* + * Copyright (c) 2005, 2007 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 + * Stefan Xenos, IBM - bug 156790: Adopt GridLayoutFactory within JFace + * Port to the D programming language: + * Frank Benoit + *******************************************************************************/ +module dwtx.jface.dialogs.PopupDialog; + +import dwtx.jface.dialogs.IDialogConstants; +import dwtx.jface.dialogs.IDialogSettings; +import dwtx.jface.dialogs.Dialog; + +import tango.util.collection.ArraySeq; +import tango.util.collection.model.Seq; +import tango.util.collection.model.SeqView; + +import dwt.DWT; +import dwt.events.DisposeEvent; +import dwt.events.DisposeListener; +import dwt.events.MouseAdapter; +import dwt.events.MouseEvent; +import dwt.events.SelectionAdapter; +import dwt.events.SelectionEvent; +import dwt.graphics.Color; +import dwt.graphics.Font; +import dwt.graphics.FontData; +import dwt.graphics.Image; +import dwt.graphics.Point; +import dwt.graphics.Rectangle; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Display; +import dwt.widgets.Event; +import dwt.widgets.Label; +import dwt.widgets.Listener; +import dwt.widgets.Menu; +import dwt.widgets.Shell; +import dwt.widgets.ToolBar; +import dwt.widgets.ToolItem; +import dwt.widgets.Tracker; +import dwtx.jface.action.Action; +import dwtx.jface.action.GroupMarker; +import dwtx.jface.action.IAction; +import dwtx.jface.action.IMenuManager; +import dwtx.jface.action.MenuManager; +import dwtx.jface.action.Separator; +import dwtx.jface.layout.GridDataFactory; +import dwtx.jface.layout.GridLayoutFactory; +import dwtx.jface.resource.ImageDescriptor; +import dwtx.jface.resource.JFaceResources; +import dwtx.jface.window.Window; + +import dwt.dwthelper.utils; + +/** + * A lightweight, transient dialog that is popped up to show contextual or + * temporal information and is easily dismissed. Clients control whether the + * dialog should be able to receive input focus. An optional title area at the + * top and an optional info area at the bottom can be used to provide additional + * information. + *

+ * Because the dialog is short-lived, most of the configuration of the dialog is + * done in the constructor. Set methods are only provided for those values that + * are expected to be dynamically computed based on a particular instance's + * internal state. + *

+ * Clients are expected to override the creation of the main dialog area, and + * may optionally override the creation of the title area and info area in order + * to add content. In general, however, the creation of stylistic features, such + * as the dialog menu, separator styles, and fonts, is kept private so that all + * popup dialogs will have a similar appearance. + * + * @since 3.2 + */ +public class PopupDialog : Window { + + /** + * + */ + private static const GridDataFactory LAYOUTDATA_GRAB_BOTH; + + /** + * The dialog settings key name for stored dialog x location. + */ + private static const String DIALOG_ORIGIN_X = "DIALOG_X_ORIGIN"; //$NON-NLS-1$ + + /** + * The dialog settings key name for stored dialog y location. + */ + private static const String DIALOG_ORIGIN_Y = "DIALOG_Y_ORIGIN"; //$NON-NLS-1$ + + /** + * The dialog settings key name for stored dialog width. + */ + private static const String DIALOG_WIDTH = "DIALOG_WIDTH"; //$NON-NLS-1$ + + /** + * The dialog settings key name for stored dialog height. + */ + private static const String DIALOG_HEIGHT = "DIALOG_HEIGHT"; //$NON-NLS-1$ + + /** + * The dialog settings key name for remembering if the persisted bounds + * should be accessed. + */ + private static const String DIALOG_USE_PERSISTED_BOUNDS = "DIALOG_USE_PERSISTED_BOUNDS"; //$NON-NLS-1$ + + /** + * Move action for the dialog. + */ + private class MoveAction : Action { + + this() { + super(JFaceResources.getString("PopupDialog.move"), //$NON-NLS-1$ + IAction.AS_PUSH_BUTTON); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.action.IAction#run() + */ + public void run() { + performTrackerAction(DWT.NONE); + } + + } + + /** + * Resize action for the dialog. + */ + private class ResizeAction : Action { + + this() { + super(JFaceResources.getString("PopupDialog.resize"), //$NON-NLS-1$ + IAction.AS_PUSH_BUTTON); + } + + /* + * @see dwtx.jface.action.Action#run() + */ + public void run() { + performTrackerAction(DWT.RESIZE); + } + } + + /** + * + * Remember bounds action for the dialog. + */ + private class PersistBoundsAction : Action { + + this() { + super(JFaceResources.getString("PopupDialog.persistBounds"), //$NON-NLS-1$ + IAction.AS_CHECK_BOX); + setChecked(persistBounds); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.action.IAction#run() + */ + public void run() { + persistBounds = isChecked(); + } + } + + /** + * Shell style appropriate for a simple hover popup that cannot get focus. + */ + public const static int HOVER_SHELLSTYLE = DWT.NO_FOCUS | DWT.ON_TOP + | DWT.NO_TRIM; + + /** + * Shell style appropriate for an info popup that can get focus. + */ + public const static int INFOPOPUP_SHELLSTYLE = DWT.NO_TRIM; + + /** + * Shell style appropriate for a resizable info popup that can get focus. + */ + public const static int INFOPOPUPRESIZE_SHELLSTYLE = DWT.RESIZE; + + /** + * Margin width (in pixels) to be used in layouts inside popup dialogs + * (value is 0). + */ + public const static int POPUP_MARGINWIDTH = 0; + + /** + * Margin height (in pixels) to be used in layouts inside popup dialogs + * (value is 0). + */ + public const static int POPUP_MARGINHEIGHT = 0; + + /** + * Vertical spacing (in pixels) between cells in the layouts inside popup + * dialogs (value is 1). + */ + public const static int POPUP_VERTICALSPACING = 1; + + /** + * Vertical spacing (in pixels) between cells in the layouts inside popup + * dialogs (value is 1). + */ + public const static int POPUP_HORIZONTALSPACING = 1; + + /** + * + */ + private static const GridLayoutFactory POPUP_LAYOUT_FACTORY; + + static this(){ + LAYOUTDATA_GRAB_BOTH = GridDataFactory.fillDefaults().grab(true,true); + POPUP_LAYOUT_FACTORY = GridLayoutFactory + .fillDefaults().margins(POPUP_MARGINWIDTH, POPUP_MARGINHEIGHT) + .spacing(POPUP_HORIZONTALSPACING, POPUP_VERTICALSPACING); + } + /** + * Border thickness in pixels. + */ + private static const int BORDER_THICKNESS = 1; + + /** + * The dialog's toolbar for the move and resize capabilities. + */ + private ToolBar toolBar = null; + + /** + * The dialog's menu manager. + */ + private MenuManager menuManager = null; + + /** + * The control representing the main dialog area. + */ + private Control dialogArea; + + /** + * Labels that contain title and info text. Cached so they can be updated + * dynamically if possible. + */ + private Label titleLabel, infoLabel; + + /** + * Separator controls. Cached so they can be excluded from color changes. + */ + private Control titleSeparator, infoSeparator; + + /** + * The images for the dialog menu. + */ + private Image menuImage, disabledMenuImage = null; + + /** + * Font to be used for the info area text. Computed based on the dialog's + * font. + */ + private Font infoFont; + + /** + * Font to be used for the title area text. Computed based on the dialog's + * font. + */ + private Font titleFont; + + /** + * Flags indicating whether we are listening for shell deactivate events, + * either those or our parent's. Used to prevent closure when a menu command + * is chosen or a secondary popup is launched. + */ + private bool listenToDeactivate; + + private bool listenToParentDeactivate; + + private Listener parentDeactivateListener; + + /** + * Flag indicating whether focus should be taken when the dialog is opened. + */ + private bool takeFocusOnOpen = false; + + /** + * Flag specifying whether a menu should be shown that allows the user to + * move and resize. + */ + private bool showDialogMenu_ = false; + + /** + * Flag specifying whether a menu action allowing the user to choose whether + * the dialog bounds should be persisted is to be shown. + */ + private bool showPersistAction = false; + + /** + * Flag specifying whether the bounds of the popup should be persisted. This + * flag is updated by a menu if the menu is shown. + */ + private bool persistBounds = false; + + /** + * Text to be shown in an optional title area (on top). + */ + private String titleText; + + /** + * Text to be shown in an optional info area (at the bottom). + */ + private String infoText; + + /** + * Constructs a new instance of PopupDialog. + * + * @param parent + * The parent shell. + * @param shellStyle + * The shell style. + * @param takeFocusOnOpen + * A bool indicating whether focus should be taken by this + * popup when it opens. + * @param persistBounds + * A bool indicating whether the bounds should be persisted + * upon close of the dialog. The bounds can only be persisted if + * the dialog settings for persisting the bounds are also + * specified. If a menu action will be provided that allows the + * user to control this feature, then the last known value of the + * user's setting will be used instead of this flag. + * @param showDialogMenu + * A bool indicating whether a menu for moving and resizing + * the popup should be provided. + * @param showPersistAction + * A bool indicating whether an action allowing the user to + * control the persisting of the dialog bounds should be shown in + * the dialog menu. This parameter has no effect if + * showDialogMenu is false. + * @param titleText + * Text to be shown in an upper title area, or null + * if there is no title. + * @param infoText + * Text to be shown in a lower info area, or null + * if there is no info area. + * + * @see PopupDialog#getDialogSettings() + */ + public this(Shell parent, int shellStyle, bool takeFocusOnOpen, + bool persistBounds, bool showDialogMenu_, + bool showPersistAction, String titleText, String infoText) { + super(parent); + setShellStyle(shellStyle); + this.takeFocusOnOpen = takeFocusOnOpen; + this.showDialogMenu_ = showDialogMenu_; + this.showPersistAction = showPersistAction; + this.titleText = titleText; + this.infoText = infoText; + + setBlockOnOpen(false); + + this.persistBounds = persistBounds; + initializeWidgetState(); + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.window.Window#configureShell(Shell) + */ + protected void configureShell(Shell shell) { + Display display = shell.getDisplay(); + shell.setBackground(display.getSystemColor(DWT.COLOR_BLACK)); + + int border = ((getShellStyle() & DWT.NO_TRIM) is 0) ? 0 + : BORDER_THICKNESS; + GridLayoutFactory.fillDefaults().margins(border, border).spacing(5,5).applyTo(shell); + + shell.addListener(DWT.Deactivate, new class Listener { + public void handleEvent(Event event) { + /* + * Close if we are deactivating and have no child shells. If we + * have child shells, we are deactivating due to their opening. + * On X, we receive this when a menu child (such as the system + * menu) of the shell opens, but I have not found a way to + * distinguish that case here. Hence bug #113577 still exists. + */ + if (listenToDeactivate && event.widget is getShell() + && getShell().getShells().length is 0) { + close(); + } else { + /* We typically ignore deactivates to work around platform-specific + * event ordering. Now that we've ignored whatever we were supposed to, + * start listening to deactivates. Example issues can be found in + * https://bugs.eclipse.org/bugs/show_bug.cgi?id=123392 + */ + listenToDeactivate = true; + } + } + }); + // Set this true whenever we activate. It may have been turned + // off by a menu or secondary popup showing. + shell.addListener(DWT.Activate, new class Listener { + public void handleEvent(Event event) { + // ignore this event if we have launched a child + if (event.widget is getShell() + && getShell().getShells().length is 0) { + listenToDeactivate = true; + // Typically we start listening for parent deactivate after + // we are activated, except on the Mac, where the deactivate + // is received after activate. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=100668 + listenToParentDeactivate = !"carbon".equals(DWT.getPlatform()); //$NON-NLS-1$ + } + } + }); + + if ((getShellStyle() & DWT.ON_TOP) !is 0 && shell.getParent() !is null) { + parentDeactivateListener= new class Listener { + public void handleEvent(Event event) { + if (listenToParentDeactivate) { + close(); + } else { + // Our first deactivate, now start listening on the Mac. + listenToParentDeactivate = listenToDeactivate; + } + } + }; + shell.getParent().addListener(DWT.Deactivate, parentDeactivateListener); + } + + shell.addDisposeListener(new class DisposeListener { + public void widgetDisposed(DisposeEvent event) { + handleDispose(); + } + }); + } + + /** + * The PopupDialog implementation of this Window + * method creates and lays out the top level composite for the dialog. It + * then calls the createTitleMenuArea, + * createDialogArea, and createInfoTextArea + * methods to create an optional title and menu area on the top, a dialog + * area in the center, and an optional info text area at the bottom. + * Overriding createDialogArea and (optionally) + * createTitleMenuArea and createTitleMenuArea + * are recommended rather than overriding this method. + * + * @param parent + * the composite used to parent the contents. + * + * @return the control representing the contents. + */ + protected Control createContents(Composite parent) { + Composite composite = new Composite(parent, DWT.NONE); + POPUP_LAYOUT_FACTORY.applyTo(composite); + LAYOUTDATA_GRAB_BOTH.applyTo(composite); + + // Title area + if (hasTitleArea()) { + createTitleMenuArea(composite); + titleSeparator = createHorizontalSeparator(composite); + } + // Content + dialogArea = createDialogArea(composite); + // Create a grid data layout data if one was not provided. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=118025 + if (dialogArea.getLayoutData() is null) { + LAYOUTDATA_GRAB_BOTH.applyTo(dialogArea); + } + + // Info field + if (hasInfoArea()) { + infoSeparator = createHorizontalSeparator(composite); + createInfoTextArea(composite); + } + + applyColors(composite); + applyFonts(composite); + return composite; + } + + /** + * Creates and returns the contents of the dialog (the area below the title + * area and above the info text area. + *

+ * The PopupDialog implementation of this framework method + * creates and returns a new Composite with standard margins + * and spacing. + *

+ * The returned control's layout data must be an instance of + * GridData. This method must not modify the parent's + * layout. + *

+ * Subclasses must override this method but may call super as + * in the following example: + * + *

+     * Composite composite = (Composite) super.createDialogArea(parent);
+     * //add controls to composite as necessary
+     * return composite;
+     * 
+ * + * @param parent + * the parent composite to contain the dialog area + * @return the dialog area control + */ + protected Control createDialogArea(Composite parent) { + Composite composite = new Composite(parent, DWT.NONE); + POPUP_LAYOUT_FACTORY.applyTo(composite); + LAYOUTDATA_GRAB_BOTH.applyTo(composite); + return composite; + } + + /** + * Returns the control that should get initial focus. Subclasses may + * override this method. + * + * @return the Control that should receive focus when the popup opens. + */ + protected Control getFocusControl() { + return dialogArea; + } + + /** + * Sets the tab order for the popup. Clients should override to introduce + * specific tab ordering. + * + * @param composite + * the composite in which all content, including the title area + * and info area, was created. This composite's parent is the + * shell. + */ + protected void setTabOrder(Composite composite) { + // default is to do nothing + } + + /** + * Returns a bool indicating whether the popup should have a title area + * at the top of the dialog. Subclasses may override. Default behavior is to + * have a title area if there is to be a menu or title text. + * + * @return true if a title area should be created, + * false if it should not. + */ + protected bool hasTitleArea() { + return titleText !is null || showDialogMenu_; + } + + /** + * Returns a bool indicating whether the popup should have an info area + * at the bottom of the dialog. Subclasses may override. Default behavior is + * to have an info area if info text was provided at the time of creation. + * + * @return true if a title area should be created, + * false if it should not. + */ + protected bool hasInfoArea() { + return infoText !is null; + } + + /** + * Creates the title and menu area. Subclasses typically need not override + * this method, but instead should use the constructor parameters + * showDialogMenu and showPersistAction to + * indicate whether a menu should be shown, and + * createTitleControl to to customize the presentation of the + * title. + * + *

+ * If this method is overridden, the returned control's layout data must be + * an instance of GridData. This method must not modify the + * parent's layout. + * + * @param parent + * The parent composite. + * @return The Control representing the title and menu area. + */ + protected Control createTitleMenuArea(Composite parent) { + + Composite titleAreaComposite = new Composite(parent, DWT.NONE); + POPUP_LAYOUT_FACTORY.copy().numColumns(2).applyTo(titleAreaComposite); + GridDataFactory.fillDefaults() + .align_(DWT.FILL, DWT.CENTER).grab(true, false) + .applyTo(titleAreaComposite); + + createTitleControl(titleAreaComposite); + + if (showDialogMenu_) { + createDialogMenu(titleAreaComposite); + } + return titleAreaComposite; + } + + /** + * Creates the control to be used to represent the dialog's title text. + * Subclasses may override if a different control is desired for + * representing the title text, or if something different than the title + * should be displayed in location where the title text typically is shown. + * + *

+ * If this method is overridden, the returned control's layout data must be + * an instance of GridData. This method must not modify the + * parent's layout. + * + * @param parent + * The parent composite. + * @return The Control representing the title area. + */ + protected Control createTitleControl(Composite parent) { + titleLabel = new Label(parent, DWT.NONE); + + GridDataFactory.fillDefaults() + .align_(DWT.FILL, DWT.CENTER) + .grab(true, false) + .span(showDialogMenu_ ? 1 : 2, 1) + .applyTo(titleLabel); + + Font font = titleLabel.getFont(); + FontData[] fontDatas = font.getFontData(); + for (int i = 0; i < fontDatas.length; i++) { + fontDatas[i].setStyle(DWT.BOLD); + } + titleFont = new Font(titleLabel.getDisplay(), fontDatas); + titleLabel.setFont(titleFont); + + if (titleText !is null) { + titleLabel.setText(titleText); + } + return titleLabel; + } + + /** + * Creates the optional info text area. This method is only called if the + * hasInfoArea() method returns true. Subclasses typically + * need not override this method, but may do so. + * + *

+ * If this method is overridden, the returned control's layout data must be + * an instance of GridData. This method must not modify the + * parent's layout. + * + * + * @param parent + * The parent composite. + * @return The control representing the info text area. + * + * @see PopupDialog#hasInfoArea() + * @see PopupDialog#createTitleControl(Composite) + */ + protected Control createInfoTextArea(Composite parent) { + // Status label + infoLabel = new Label(parent, DWT.RIGHT); + infoLabel.setText(infoText); + Font font = infoLabel.getFont(); + FontData[] fontDatas = font.getFontData(); + for (int i = 0; i < fontDatas.length; i++) { + fontDatas[i].setHeight(fontDatas[i].getHeight() * 9 / 10); + } + infoFont = new Font(infoLabel.getDisplay(), fontDatas); + infoLabel.setFont(infoFont); + GridDataFactory.fillDefaults().grab(true, false).align_(DWT.FILL, DWT.BEGINNING) + .applyTo(infoLabel); + infoLabel.setForeground(parent.getDisplay().getSystemColor( + DWT.COLOR_WIDGET_DARK_SHADOW)); + return infoLabel; + } + + /** + * Create a horizontal separator for the given parent. + * + * @param parent + * The parent composite. + * @return The Control representing the horizontal separator. + */ + private Control createHorizontalSeparator(Composite parent) { + Label separator = new Label(parent, DWT.SEPARATOR | DWT.HORIZONTAL + | DWT.LINE_DOT); + GridDataFactory.fillDefaults().align_(DWT.FILL, DWT.CENTER).grab(true, false).applyTo(separator); + return separator; + } + + /** + * Create the dialog's menu for the move and resize actions. + * + * @param parent + * The parent composite. + */ + private void createDialogMenu(Composite parent) { + + toolBar = new ToolBar(parent, DWT.FLAT); + ToolItem viewMenuButton = new ToolItem(toolBar, DWT.PUSH, 0); + + GridDataFactory.fillDefaults().align_(DWT.END, DWT.CENTER).applyTo(toolBar); + + menuImage = ImageDescriptor.createFromFile(PopupDialog.classinfo, + "images/popup_menu.gif").createImage();//$NON-NLS-1$ + disabledMenuImage = ImageDescriptor.createFromFile(PopupDialog.classinfo, + "images/popup_menu_disabled.gif").createImage();//$NON-NLS-1$ + viewMenuButton.setImage(menuImage); + viewMenuButton.setDisabledImage(disabledMenuImage); + viewMenuButton.setToolTipText(JFaceResources + .getString("PopupDialog.menuTooltip")); //$NON-NLS-1$ + viewMenuButton.addSelectionListener(new class SelectionAdapter { + public void widgetSelected(SelectionEvent e) { + showDialogMenu(); + } + }); + viewMenuButton.addDisposeListener(new class DisposeListener { + public void widgetDisposed(DisposeEvent e) { + menuImage.dispose(); + menuImage = null; + disabledMenuImage.dispose(); + disabledMenuImage = null; + } + }); + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=177183 + toolBar.addMouseListener(new class MouseAdapter { + public void mouseDown(MouseEvent e) { + showDialogMenu(); + } + }); + } + + /** + * Fill the dialog's menu. Subclasses may extend or override. + * + * @param dialogMenu + * The dialog's menu. + */ + protected void fillDialogMenu(IMenuManager dialogMenu) { + dialogMenu.add(new GroupMarker("SystemMenuStart")); //$NON-NLS-1$ + dialogMenu.add(new MoveAction()); + dialogMenu.add(new ResizeAction()); + if (showPersistAction) { + dialogMenu.add(new PersistBoundsAction()); + } + dialogMenu.add(new Separator("SystemMenuEnd")); //$NON-NLS-1$ + } + + /** + * Perform the requested tracker action (resize or move). + * + * @param style + * The track style (resize or move). + */ + private void performTrackerAction(int style) { + Shell shell = getShell(); + if (shell is null || shell.isDisposed()) { + return; + } + + Tracker tracker = new Tracker(shell.getDisplay(), style); + tracker.setStippled(true); + Rectangle[] r = [ shell.getBounds() ]; + tracker.setRectangles(r); + + // Ignore any deactivate events caused by opening the tracker. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=120656 + bool oldListenToDeactivate = listenToDeactivate; + listenToDeactivate = false; + if (tracker.open()) { + if (shell !is null && !shell.isDisposed()) { + shell.setBounds(tracker.getRectangles()[0]); + } + } + listenToDeactivate = oldListenToDeactivate; + } + + /** + * Show the dialog's menu. This message has no effect if the receiver was + * not configured to show a menu. Clients may call this method in order to + * trigger the menu via keystrokes or other gestures. Subclasses typically + * do not override method. + */ + protected void showDialogMenu() { + if (!showDialogMenu_) { + return; + } + + if (menuManager is null) { + menuManager = new MenuManager(); + fillDialogMenu(menuManager); + } + // Setting this flag works around a problem that remains on X only, + // whereby activating the menu deactivates our shell. + listenToDeactivate = !"gtk".equals(DWT.getPlatform()); //$NON-NLS-1$ + + Menu menu = menuManager.createContextMenu(getShell()); + Rectangle bounds = toolBar.getBounds(); + Point topLeft = new Point(bounds.x, bounds.y + bounds.height); + topLeft = getShell().toDisplay(topLeft); + menu.setLocation(topLeft.x, topLeft.y); + menu.setVisible(true); + } + + /** + * Set the text to be shown in the popup's info area. This message has no + * effect if there was no info text supplied when the dialog first opened. + * Subclasses may override this method. + * + * @param text + * the text to be shown when the info area is displayed. + * + */ + protected void setInfoText(String text) { + infoText = text; + if (infoLabel !is null) { + infoLabel.setText(text); + } + } + + /** + * Set the text to be shown in the popup's title area. This message has no + * effect if there was no title label specified when the dialog was + * originally opened. Subclasses may override this method. + * + * @param text + * the text to be shown when the title area is displayed. + * + */ + protected void setTitleText(String text) { + titleText = text; + if (titleLabel !is null) { + titleLabel.setText(text); + } + } + + /** + * Return a bool indicating whether this dialog will persist its bounds. + * This value is initially set in the dialog's constructor, but can be + * modified if the persist bounds action is shown on the menu and the user + * has changed its value. Subclasses may override this method. + * + * @return if the dialogs bounds will be persisted, false if it will + * not. + */ + protected bool getPersistBounds() { + return persistBounds; + } + + /** + * Opens this window, creating it first if it has not yet been created. + *

+ * This method is reimplemented for special configuration of PopupDialogs. + * It never blocks on open, immediately returning OK if the + * open is successful, or CANCEL if it is not. It provides + * framework hooks that allow subclasses to set the focus and tab order, and + * avoids the use of shell.open() in cases where the focus + * should not be given to the shell initially. + * + * @return the return code + * + * @see dwtx.jface.window.Window#open() + */ + public int open() { + + Shell shell = getShell(); + if (shell is null || shell.isDisposed()) { + shell = null; + // create the window + create(); + shell = getShell(); + } + + // provide a hook for adjusting the bounds. This is only + // necessary when there is content driven sizing that must be + // adjusted each time the dialog is opened. + adjustBounds(); + + // limit the shell size to the display size + constrainShellSize(); + + // set up the tab order for the dialog + setTabOrder(cast(Composite) getContents()); + + // initialize flags for listening to deactivate + listenToDeactivate = false; + listenToParentDeactivate = false; + + // open the window + if (takeFocusOnOpen) { + shell.open(); + getFocusControl().setFocus(); + } else { + shell.setVisible(true); + } + + return OK; + + } + + /** + * Closes this window, disposes its shell, and removes this window from its + * window manager (if it has one). + *

+ * This method is extended to save the dialog bounds and initialize widget + * state so that the widgets can be recreated if the dialog is reopened. + * This method may be extended (super.close must be called). + *

+ * + * @return true if the window is (or was already) closed, and + * false if it is still open + */ + public bool close() { + // If already closed, there is nothing to do. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=127505 + if (getShell() is null || getShell().isDisposed()) { + return true; + } + + saveDialogBounds(getShell()); + // Widgets are about to be disposed, so null out any state + // related to them that was not handled in dispose listeners. + // We do this before disposal so that any received activate or + // deactivate events are duly ignored. + initializeWidgetState(); + + if (parentDeactivateListener !is null) { + getShell().getParent().removeListener(DWT.Deactivate, parentDeactivateListener); + parentDeactivateListener = null; + } + + return super.close(); + } + + /** + * Gets the dialog settings that should be used for remembering the bounds + * of the dialog. Subclasses should override this method when they wish to + * persist the bounds of the dialog. + * + * @return settings the dialog settings used to store the dialog's location + * and/or size, or null if the dialog's bounds should + * never be stored. + */ + protected IDialogSettings getDialogSettings() { + return null; + } + + /** + * Saves the bounds of the shell in the appropriate dialog settings. The + * bounds are recorded relative to the parent shell, if there is one, or + * display coordinates if there is no parent shell. Subclasses typically + * need not override this method, but may extend it (calling + * super.saveDialogBounds if additional bounds information + * should be stored. Clients may also call this method to persist the bounds + * at times other than closing the dialog. + * + * @param shell + * The shell whose bounds are to be stored + */ + protected void saveDialogBounds(Shell shell) { + IDialogSettings settings = getDialogSettings(); + if (settings !is null) { + Point shellLocation = shell.getLocation(); + Point shellSize = shell.getSize(); + Shell parent = getParentShell(); + if (parent !is null) { + Point parentLocation = parent.getLocation(); + shellLocation.x -= parentLocation.x; + shellLocation.y -= parentLocation.y; + } + if (persistBounds) { + String prefix = this.classinfo.name; + settings.put(prefix ~ DIALOG_ORIGIN_X, shellLocation.x); + settings.put(prefix ~ DIALOG_ORIGIN_Y, shellLocation.y); + settings.put(prefix ~ DIALOG_WIDTH, shellSize.x); + settings.put(prefix ~ DIALOG_HEIGHT, shellSize.y); + } + if (showPersistAction && showDialogMenu_) { + settings.put( + this.classinfo.name ~ DIALOG_USE_PERSISTED_BOUNDS, + persistBounds); + } + } + } + + /* + * (non-Javadoc) + * + * @see dwtx.jface.window.Window#getInitialSize() + */ + protected Point getInitialSize() { + Point result = super.getInitialSize(); + if (persistBounds) { + IDialogSettings settings = getDialogSettings(); + if (settings !is null) { + try { + int width = settings.getInt(this.classinfo.name + ~ DIALOG_WIDTH); + int height = settings.getInt(this.classinfo.name + ~ DIALOG_HEIGHT); + result = new Point(width, height); + + } catch (NumberFormatException e) { + } + } + } + // No attempt is made to constrain the bounds. The default + // constraining behavior in Window will be used. + return result; + } + + /** + * Adjust the bounds of the popup as necessary prior to opening the dialog. + * Default is to do nothing, which honors any bounds set directly by clients + * or those that have been saved in the dialog settings. Subclasses should + * override this method when there are bounds computations that must be + * checked each time the dialog is opened. + */ + protected void adjustBounds() { + } + + /** + * (non-Javadoc) + * + * @see dwtx.jface.window.Window#getInitialLocation(dwt.graphics.Point) + */ + protected Point getInitialLocation(Point initialSize) { + Point result = super.getInitialLocation(initialSize); + if (persistBounds) { + IDialogSettings settings = getDialogSettings(); + if (settings !is null) { + try { + int x = settings.getInt(this.classinfo.name + ~ DIALOG_ORIGIN_X); + int y = settings.getInt(this.classinfo.name + ~ DIALOG_ORIGIN_Y); + result = new Point(x, y); + // The coordinates were stored relative to the parent shell. + // Convert to display coordinates. + Shell parent = getParentShell(); + if (parent !is null) { + Point parentLocation = parent.getLocation(); + result.x += parentLocation.x; + result.y += parentLocation.y; + } + } catch (NumberFormatException e) { + } + } + } + // No attempt is made to constrain the bounds. The default + // constraining behavior in Window will be used. + return result; + } + + /** + * Apply any desired color to the specified composite and its children. + * + * @param composite + * the contents composite + */ + private void applyColors(Composite composite) { + applyForegroundColor(getShell().getDisplay().getSystemColor( + DWT.COLOR_INFO_FOREGROUND), composite, + getForegroundColorExclusions()); + applyBackgroundColor(getShell().getDisplay().getSystemColor( + DWT.COLOR_INFO_BACKGROUND), composite, + getBackgroundColorExclusions()); + } + + /** + * Apply any desired fonts to the specified composite and its children. + * + * @param composite + * the contents composite + */ + private void applyFonts(Composite composite) { + Dialog.applyDialogFont(composite); + + } + + /** + * Set the specified foreground color for the specified control and all of + * its children, except for those specified in the list of exclusions. + * + * @param color + * the color to use as the foreground color + * @param control + * the control whose color is to be changed + * @param exclusions + * a list of controls who are to be excluded from getting their + * color assigned + */ + private void applyForegroundColor(Color color, Control control, + SeqView!(Control) exclusions) { + if (!exclusions.contains(control)) { + control.setForeground(color); + } + if ( auto comp = cast(Composite)control ) { + Control[] children = comp.getChildren(); + for (int i = 0; i < children.length; i++) { + applyForegroundColor(color, children[i], exclusions); + } + } + } + + /** + * Set the specified background color for the specified control and all of + * its children. + * + * @param color + * the color to use as the background color + * @param control + * the control whose color is to be changed + * @param exclusions + * a list of controls who are to be excluded from getting their + * color assigned + */ + private void applyBackgroundColor(Color color, Control control, + SeqView!(Control) exclusions) { + if (!exclusions.contains(control)) { + control.setBackground(color); + } + if (auto comp = cast(Composite)control ) { + Control[] children = comp.getChildren(); + for (int i = 0; i < children.length; i++) { + applyBackgroundColor(color, children[i], exclusions); + } + } + } + + /** + * Set the specified foreground color for the specified control and all of + * its children. Subclasses may override this method, but typically do not. + * If a subclass wishes to exclude a particular control in its contents from + * getting the specified foreground color, it may instead override + * PopupDialog.getForegroundColorExclusions. + * + * @param color + * the color to use as the background color + * @param control + * the control whose color is to be changed + * @see PopupDialog#getBackgroundColorExclusions() + */ + protected void applyForegroundColor(Color color, Control control) { + applyForegroundColor(color, control, getForegroundColorExclusions()); + } + + /** + * Set the specified background color for the specified control and all of + * its children. Subclasses may override this method, but typically do not. + * If a subclass wishes to exclude a particular control in its contents from + * getting the specified background color, it may instead override + * PopupDialog.getBackgroundColorExclusions. + * + * @param color + * the color to use as the background color + * @param control + * the control whose color is to be changed + * @see PopupDialog#getBackgroundColorExclusions() + */ + protected void applyBackgroundColor(Color color, Control control) { + applyBackgroundColor(color, control, getBackgroundColorExclusions()); + } + + /** + * Return a list of controls which should never have their foreground color + * reset. Subclasses may extend this method (should always call + * super.getForegroundColorExclusions to aggregate the list. + * + * + * @return the List of controls + */ + protected SeqView!(Control) getForegroundColorExclusions() { + auto list = new ArraySeq!(Control); + list.capacity(3); + if (infoLabel !is null) { + list.append(infoLabel); + } + if (titleSeparator !is null) { + list.append(titleSeparator); + } + if (infoSeparator !is null) { + list.append(infoSeparator); + } + return list; + } + + /** + * Return a list of controls which should never have their background color + * reset. Subclasses may extend this method (should always call + * super.getBackgroundColorExclusions to aggregate the list. + * + * @return the List of controls + */ + protected SeqView!(Control) getBackgroundColorExclusions() { + auto list = new ArraySeq!(Control); + list.capacity(2); + if (titleSeparator !is null) { + list.append(titleSeparator); + } + if (infoSeparator !is null) { + list.append(infoSeparator); + } + return list; + } + + /** + * Initialize any state related to the widgetry that should be set up each + * time widgets are created. + */ + private void initializeWidgetState() { + menuManager = null; + dialogArea = null; + titleLabel = null; + titleSeparator = null; + infoSeparator = null; + infoLabel = null; + toolBar = null; + + // If the menu item for persisting bounds is displayed, use the stored + // value to determine whether any persisted bounds should be honored at + // all. + if (showDialogMenu_ && showPersistAction) { + IDialogSettings settings = getDialogSettings(); + if (settings !is null) { + persistBounds = settings.getBoolean(this.classinfo.name + ~ DIALOG_USE_PERSISTED_BOUNDS); + } + } + + } + + /** + * The dialog is being disposed. Dispose of any resources allocated. + * + */ + private void handleDispose() { + if (infoFont !is null && !infoFont.isDisposed()) { + infoFont.dispose(); + } + infoFont = null; + if (titleFont !is null && !titleFont.isDisposed()) { + titleFont.dispose(); + } + titleFont = null; + } +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/dialogs/StatusDialog.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/dialogs/StatusDialog.d Tue Apr 01 08:00:31 2008 +0200 @@ -0,0 +1,306 @@ +/******************************************************************************* + * 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 + *******************************************************************************/ +module dwtx.jface.dialogs.StatusDialog; + +import dwtx.jface.dialogs.Dialog; +import dwtx.jface.dialogs.TrayDialog; +import dwtx.jface.dialogs.IDialogConstants; + +import dwt.DWT; +import dwt.custom.CLabel; +import dwt.graphics.Color; +import dwt.graphics.Image; +import dwt.layout.GridData; +import dwt.layout.GridLayout; +import dwt.widgets.Button; +import dwt.widgets.Composite; +import dwt.widgets.Control; +import dwt.widgets.Shell; +import dwtx.core.runtime.IStatus; +import dwtx.core.runtime.Status; +import dwtx.jface.resource.JFaceColors; +import dwtx.jface.resource.JFaceResources; +import dwtx.jface.util.Policy; +import dwtx.jface.util.Util; + +import dwt.dwthelper.utils; + +/** + * An abstract base class for dialogs with a status bar and OK/CANCEL buttons. + * The status message is specified in an IStatus which can be of severity ERROR, + * WARNING, INFO or OK. The OK button is enabled or disabled depending on the + * status. + * + * @since 3.1 + */ +public abstract class StatusDialog : TrayDialog { + + private Button fOkButton; + + private MessageLine fStatusLine; + + private IStatus fLastStatus; + + private String fTitle; + + private Image fImage; + + private bool fStatusLineAboveButtons = true; + + /** + * A message line displaying a status. + */ + private class MessageLine : CLabel { + + private Color fNormalMsgAreaBackground; + + /** + * Creates a new message line as a child of the given parent. + * + * @param parent + */ + public this(Composite parent) { + this(parent, DWT.LEFT); + } + + /** + * Creates a new message line as a child of the parent and with the + * given DWT stylebits. + * + * @param parent + * @param style + */ + public this(Composite parent, int style) { + super(parent, style); + fNormalMsgAreaBackground = getBackground(); + } + + /** + * Find an image assocated with the status. + * + * @param status + * @return Image + */ + private Image findImage(IStatus status) { + if (status.isOK()) { + return null; + } else if (status.matches(IStatus.ERROR)) { + return JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_ERROR); + } else if (status.matches(IStatus.WARNING)) { + return JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_WARNING); + } else if (status.matches(IStatus.INFO)) { + return JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_INFO); + } + return null; + } + + /** + * Sets the message and image to the given status. + * + * @param status + * IStatus or null. null will + * set the empty text and no image. + */ + public void setErrorStatus(IStatus status) { + if (status !is null && !status.isOK()) { + String message = status.getMessage(); + if (message !is null && message.length > 0) { + setText(message); + // unqualified call of setImage is too ambiguous for + // Foundation 1.0 compiler + // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=140576 + this.outer.setImage(findImage(status)); + setBackground(JFaceColors.getErrorBackground(getDisplay())); + return; + } + } + setText(""); //$NON-NLS-1$ + // unqualified call of setImage is too ambiguous for Foundation 1.0 + // compiler + // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=140576 + this.outer.setImage(null); + setBackground(fNormalMsgAreaBackground); + } + } + + /** + * Creates an instance of a status dialog. + * + * @param parent + * the parent Shell of the dialog + */ + public this(Shell parent) { + super(parent); + fLastStatus = new Status(IStatus.OK, Policy.JFACE, IStatus.OK, + Util.ZERO_LENGTH_STRING, null); + } + + /** + * Specifies whether status line appears to the left of the buttons + * (default) or above them. + * + * @param aboveButtons + * if true status line is placed above buttons; if + * false to the right + */ + public void setStatusLineAboveButtons(bool aboveButtons) { + fStatusLineAboveButtons = aboveButtons; + } + + /** + * Update the dialog's status line to reflect the given status. It is safe + * to call this method before the dialog has been opened. + * + * @param status + * the status to set + */ + protected void updateStatus(IStatus status) { + fLastStatus = status; + if (fStatusLine !is null && !fStatusLine.isDisposed()) { + updateButtonsEnableState(status); + fStatusLine.setErrorStatus(status); + } + } + + /** + * Returns the last status. + * + * @return IStatus + */ + public IStatus getStatus() { + return fLastStatus; + } + + /** + * Updates the status of the ok button to reflect the given status. + * Subclasses may override this method to update additional buttons. + * + * @param status + * the status. + */ + protected void updateButtonsEnableState(IStatus status) { + if (fOkButton !is null && !fOkButton.isDisposed()) { + fOkButton.setEnabled(!status.matches(IStatus.ERROR)); + } + } + + /* + * @see Window#create(Shell) + */ + protected void configureShell(Shell shell) { + super.configureShell(shell); + if (fTitle !is null) { + shell.setText(fTitle); + } + } + + /* + * @see Window#create() + */ + public void create() { + super.create(); + if (fLastStatus !is null) { + // policy: dialogs are not allowed to come up with an error message + if (fLastStatus.matches(IStatus.ERROR)) { + // remove the message + fLastStatus = new Status(IStatus.ERROR, + fLastStatus.getPlugin(), fLastStatus.getCode(), + "", fLastStatus.getException()); //$NON-NLS-1$ + } + updateStatus(fLastStatus); + } + } + + /* + * @see Dialog#createButtonsForButtonBar(Composite) + */ + protected void createButtonsForButtonBar(Composite parent) { + fOkButton = createButton(parent, IDialogConstants.OK_ID, + IDialogConstants.OK_LABEL, true); + createButton(parent, IDialogConstants.CANCEL_ID, + IDialogConstants.CANCEL_LABEL, false); + } + + /* + * @see Dialog#createButtonBar(Composite) + */ + protected Control createButtonBar(Composite parent) { + Composite composite = new Composite(parent, DWT.NULL); + GridLayout layout = new GridLayout(); + + if (fStatusLineAboveButtons) { + layout.numColumns = 1; + } else { + layout.numColumns = 2; + } + + layout.marginHeight = 0; + layout.marginLeft = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN); + layout.marginWidth = 0; + composite.setLayout(layout); + composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + if (!fStatusLineAboveButtons && isHelpAvailable()) { + createHelpControl(composite); + } + fStatusLine = new MessageLine(composite); + fStatusLine.setAlignment(DWT.LEFT); + GridData statusData = new GridData(GridData.FILL_HORIZONTAL); + fStatusLine.setErrorStatus(null); + if (fStatusLineAboveButtons && isHelpAvailable()) { + statusData.horizontalSpan = 2; + createHelpControl(composite); + } + fStatusLine.setLayoutData(statusData); + applyDialogFont(composite); + + /* + * Create the rest of the button bar, but tell it not to create a help + * button (we've already created it). + */ + bool helpAvailable = isHelpAvailable(); + setHelpAvailable(false); + super.createButtonBar(composite); + setHelpAvailable(helpAvailable); + return composite; + } + + /** + * Sets the title for this dialog. + * + * @param title + * the title. + */ + public void setTitle(String title) { + fTitle = title !is null ? title : ""; //$NON-NLS-1$ + Shell shell = getShell(); + if ((shell !is null) && !shell.isDisposed()) { + shell.setText(fTitle); + } + } + + /** + * Sets the image for this dialog. + * + * @param image + * the image. + */ + public void setImage(Image image) { + fImage = image; + Shell shell = getShell(); + if ((shell !is null) && !shell.isDisposed()) { + shell.setImage(fImage); + } + } + +} diff -r db8940420ed8 -r e0f0aaf75edd dwtx/jface/util/Util.d --- a/dwtx/jface/util/Util.d Mon Mar 31 02:00:41 2008 +0200 +++ b/dwtx/jface/util/Util.d Tue Apr 01 08:00:31 2008 +0200 @@ -179,6 +179,9 @@ return 0; } } + public static final int compare(String left, String right) { + return left < right; + } // /** // * Compares two lists -- account for null. The lists must @@ -272,6 +275,9 @@ return left is null ? right is null : ((right !is null) && left .opEquals(right)); } + public static final bool opEquals(String left, String right) { + return left == right; + } /** * Tests whether two arrays of objects are equal to each other. The arrays @@ -316,6 +322,22 @@ return true; } + public static final bool opEquals(String[] leftArray, String[] rightArray) { + if (leftArray.length !is rightArray.length) { + return false; + } + + for (int i = 0; i < leftArray.length; i++) { + String left = leftArray[i]; + String right = rightArray[i]; + if (left != right) { + return false; + } + } + + return true; + } + /** * Provides a hash code based on the given integer value. * @@ -365,6 +387,18 @@ return hashCode; } + public static final hash_t toHash(String str) { + return dwt.dwthelper.utils.toHash(str); + } + public static final hash_t toHash(String[] objects) { + int hashCode = 89; + for (int i = 0; i < objects.length; i++) { + auto object = objects[i]; + hashCode = hashCode * 31 + toHash(object); + } + + return hashCode; + } /** * Checks whether the second array is a subsequence of the first array, and