Package org.eclipse.ui.internal.keys

Source Code of org.eclipse.ui.internal.keys.KeysPreferencePage$SortOrderSelectionListener

/*******************************************************************************
* 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
*******************************************************************************/

package org.eclipse.ui.internal.keys;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;

import org.eclipse.core.commands.Category;
import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.CommandManager;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.core.commands.common.NotDefinedException;
import org.eclipse.core.commands.contexts.Context;
import org.eclipse.core.commands.contexts.ContextManager;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.bindings.Binding;
import org.eclipse.jface.bindings.BindingManager;
import org.eclipse.jface.bindings.Scheme;
import org.eclipse.jface.bindings.TriggerSequence;
import org.eclipse.jface.bindings.keys.KeyBinding;
import org.eclipse.jface.bindings.keys.KeySequence;
import org.eclipse.jface.bindings.keys.KeySequenceText;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.contexts.IContextIds;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.activities.IActivityManager;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.internal.IPreferenceConstants;
import org.eclipse.ui.internal.IWorkbenchHelpContextIds;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.misc.StatusUtil;
import org.eclipse.ui.internal.util.PrefUtil;
import org.eclipse.ui.internal.util.Util;
import org.eclipse.ui.keys.IBindingService;
import org.eclipse.ui.statushandlers.StatusManager;

import com.ibm.icu.text.Collator;
import com.ibm.icu.text.MessageFormat;

/**
* The preference page for defining keyboard shortcuts. While some of its
* underpinning have been made generic to "bindings" rather than "key bindings",
* it will still take some work to remove the link entirely.
*
* @since 3.0
*/
public final class KeysPreferencePage extends PreferencePage implements
    IWorkbenchPreferencePage {

  /**
   * A selection listener to be used on the columns in the table on the view
   * tab. This selection listener modifies the sort order so that the
   * appropriate column is in the first position.
   *
   * @since 3.1
   */
  private class SortOrderSelectionListener extends SelectionAdapter {

    /**
     * The column to be put in the first position. This value should be one
     * of the constants defined by <code>SORT_COLUMN_</code>.
     */
    private final int columnSelected;

    /**
     * Constructs a new instance of <code>SortOrderSelectionListener</code>.
     *
     * @param columnSelected
     *            The column to be given first priority in the sort order;
     *            this value should be one of the constants defined as
     *            <code>SORT_COLUMN_</code>.
     */
    private SortOrderSelectionListener(final int columnSelected) {
      this.columnSelected = columnSelected;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
     */
    public void widgetSelected(SelectionEvent e) {
      // Change the column titles.
      final int oldSortIndex = sortOrder[0];
      final TableColumn oldSortColumn = tableBindings
          .getColumn(oldSortIndex);
      oldSortColumn.setText(UNSORTED_COLUMN_NAMES[oldSortIndex]);
      final TableColumn newSortColumn = tableBindings
          .getColumn(columnSelected);
      newSortColumn.setText(SORTED_COLUMN_NAMES[columnSelected]);

      // Change the sort order.
      boolean columnPlaced = false;
      boolean enoughRoom = false;
      int bumpedColumn = -1;
      for (int i = 0; i < sortOrder.length; i++) {
        if (sortOrder[i] == columnSelected) {
          /*
           * We've found the place where the column existing in the
           * old sort order. No matter what at this point, we have
           * completed the reshuffling.
           */
          enoughRoom = true;
          if (bumpedColumn != -1) {
            // We have already started bumping things around, so
            // drop the last bumped column here.
            sortOrder[i] = bumpedColumn;
          } else {
            // The order has not changed.
            columnPlaced = true;
          }
          break;

        } else if (columnPlaced) {
          // We are currently bumping, so just bump another.
          int temp = sortOrder[i];
          sortOrder[i] = bumpedColumn;
          bumpedColumn = temp;

        } else {
          /*
           * We are not currently bumping, so drop the column and
           * start bumping.
           */
          bumpedColumn = sortOrder[i];
          sortOrder[i] = columnSelected;
          columnPlaced = true;
        }
      }

      // Grow the sort order.
      if (!enoughRoom) {
        final int[] newSortOrder = new int[sortOrder.length + 1];
        System.arraycopy(sortOrder, 0, newSortOrder, 0,
            sortOrder.length);
        newSortOrder[sortOrder.length] = bumpedColumn;
        sortOrder = newSortOrder;
      }

      // Update the view tab.
      updateViewTab();
    }
  }

  /**
   * The data key for the binding stored on an SWT widget. The key is a
   * fully-qualified name, but in reverse order. This is so that the equals
   * method will detect misses faster.
   */
  private static final String BINDING_KEY = "Binding.bindings.jface.eclipse.org"; //$NON-NLS-1$

  /**
   * The image associate with a binding that exists as part of the system
   * definition.
   */
  private static final Image IMAGE_BLANK = ImageFactory.getImage("blank"); //$NON-NLS-1$

  /**
   * The image associated with a binding changed by the user.
   */
  private static final Image IMAGE_CHANGE = ImageFactory.getImage("change"); //$NON-NLS-1$

  /**
   * The data key at which the <code>Binding</code> instance for a table
   * item is stored.
   */
  private static final String ITEM_DATA_KEY = "org.eclipse.jface.bindings"; //$NON-NLS-1$

  /**
   * The number of items to show in the combo boxes.
   */
  private static final int ITEMS_TO_SHOW = 9;

  /**
   * The resource bundle from which translations can be retrieved.
   */
  private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle
      .getBundle(KeysPreferencePage.class.getName());

  /**
   * The total number of columns on the view tab.
   */
  private static final int VIEW_TOTAL_COLUMNS = 4;

  /**
   * The translated names for the columns when they are the primary sort key
   * (e.g., ">Category<").
   */
  private static final String[] SORTED_COLUMN_NAMES = new String[VIEW_TOTAL_COLUMNS];

  /**
   * The index of the modify tab.
   *
   * @since 3.1
   */
  private static final int TAB_INDEX_MODIFY = 1;

  /**
   * The translated names for the columns when they are not the primary sort
   * key (e.g., "Category").
   */
  private static final String[] UNSORTED_COLUMN_NAMES = new String[VIEW_TOTAL_COLUMNS];

  /**
   * The index of the column on the view tab containing the category name.
   */
  private static final int VIEW_CATEGORY_COLUMN_INDEX = 0;

  /**
   * The index of the column on the view tab containing the command name.
   */
  private static final int VIEW_COMMAND_COLUMN_INDEX = 1;

  /**
   * The index of the column on the view tab containing the context name.
   */
  private static final int VIEW_CONTEXT_COLUMN_INDEX = 3;

  /**
   * The index of the column on the view tab containing the key sequence.
   */
  private static final int VIEW_KEY_SEQUENCE_COLUMN_INDEX = 2;

  static {
    UNSORTED_COLUMN_NAMES[VIEW_CATEGORY_COLUMN_INDEX] = Util
        .translateString(RESOURCE_BUNDLE, "tableColumnCategory"); //$NON-NLS-1$
    UNSORTED_COLUMN_NAMES[VIEW_COMMAND_COLUMN_INDEX] = Util
        .translateString(RESOURCE_BUNDLE, "tableColumnCommand"); //$NON-NLS-1$
    UNSORTED_COLUMN_NAMES[VIEW_KEY_SEQUENCE_COLUMN_INDEX] = Util
        .translateString(RESOURCE_BUNDLE, "tableColumnKeySequence"); //$NON-NLS-1$
    UNSORTED_COLUMN_NAMES[VIEW_CONTEXT_COLUMN_INDEX] = Util
        .translateString(RESOURCE_BUNDLE, "tableColumnContext"); //$NON-NLS-1$

    SORTED_COLUMN_NAMES[VIEW_CATEGORY_COLUMN_INDEX] = Util.translateString(
        RESOURCE_BUNDLE, "tableColumnCategorySorted"); //$NON-NLS-1$
    SORTED_COLUMN_NAMES[VIEW_COMMAND_COLUMN_INDEX] = Util.translateString(
        RESOURCE_BUNDLE, "tableColumnCommandSorted"); //$NON-NLS-1$
    SORTED_COLUMN_NAMES[VIEW_KEY_SEQUENCE_COLUMN_INDEX] = Util
        .translateString(RESOURCE_BUNDLE,
            "tableColumnKeySequenceSorted"); //$NON-NLS-1$
    SORTED_COLUMN_NAMES[VIEW_CONTEXT_COLUMN_INDEX] = Util.translateString(
        RESOURCE_BUNDLE, "tableColumnContextSorted"); //$NON-NLS-1$
  }

  /**
   * The workbench's activity manager. This activity manager is used to see if
   * certain commands should be filtered from the user interface.
   */
  private IActivityManager activityManager;

  /**
   * The workbench's binding service. This binding service is used to access
   * the current set of bindings, and to persist changes.
   */
  private IBindingService bindingService;

  /**
   * The add button located on the bottom left of the preference page. This
   * button adds the current trigger sequence to the currently selected
   * command.
   */
  private Button buttonAdd;

  /**
   * The remove button located on the bottom left of the preference page. This
   * button removes the current trigger sequence from the current command.
   */
  private Button buttonRemove;

  /**
   * The restore button located on the bottom left of the preference page.
   * This button attempts to restore the currently trigger sequence to its
   * initial (i.e., Binding.SYSTEM) state -- undoing all user modifications.
   */
  private Button buttonRestore;

  /**
   * A map of all the category identifiers indexed by the names that appear in
   * the user interface. This look-up table is built during initialization.
   */
  private Map categoryIdsByUniqueName;

  /**
   * A map of all the category names in the user interface indexed by their
   * identifiers. This look-up table is built during initialization.
   */
  private Map categoryUniqueNamesById;

  /**
   * The combo box containing the list of all categories for commands.
   */
  private Combo comboCategory;

  /**
   * The combo box containing the list of commands relevent for the currently
   * selected category.
   */
  private Combo comboCommand;

  /**
   * The combo box containing the list of contexts in the system.
   */
  private Combo comboContext;

  /**
   * The combo box containing the list of schemes in the system.
   */
  private Combo comboScheme;

  /**
   * A map of all the command identifiers indexed by the categories to which
   * they belong. This look-up table is built during initialization.
   */
  private Map commandIdsByCategoryId;

  /**
   * The parameterized commands corresponding to the current contents of
   * <code>comboCommand</code>. The commands in this array are in the same
   * order as in the combo. This value can be <code>null</code> if nothing
   * is selected in the combo.
   */
  private ParameterizedCommand[] commands = null;

  /**
   * The workbench's command service. This command service is used to access
   * the list of commands.
   */
  private ICommandService commandService;

  /**
   * A map of all the context identifiers indexed by the names that appear in
   * the user interface. This look-up table is built during initialization.
   */
  private Map contextIdsByUniqueName;

  /**
   * The workbench's context service. This context service is used to access
   * the list of contexts.
   */
  private IContextService contextService;

  /**
   * A map of all the category names in the user interface indexed by their
   * identifiers. This look-up table is built during initialization.
   */
  private Map contextUniqueNamesById;

  /**
   * The workbench's help system. This is used to register the page with the
   * help system.
   *
   * TODO Add a help context
   */
  // private IWorkbenchHelpSystem helpSystem;
  /**
   * This is the label next to the table showing the bindings matching a
   * particular command. The label is disabled if there isn't a selected
   * command identifier.
   */
  private Label labelBindingsForCommand;

  /**
   * This is the label next to the table showing the bindings matching a
   * particular trigger sequence. The label is disabled if there isn't a
   * current key sequence.
   */
  private Label labelBindingsForTriggerSequence;

  /**
   * The label next to the context combo box. This label indicates whether the
   * context is a child of another context. If the current context is not a
   * child, then this label is blank.
   */
  private Label labelContextExtends;

  /**
   * The label next to the scheme combo box. This label indicates whether the
   * scheme is a child of another scheme. If the current scheme is not a
   * child, then this label is blank.
   */
  private Label labelSchemeExtends;

  /**
   * A binding manager local to this preference page. When the page is
   * initialized, the current bindings are read out from the binding service
   * and placed in this manager. This manager is then updated as the user
   * makes changes. When the user has finished, the contents of this manager
   * are compared with the contents of the binding service. The changes are
   * then persisted.
   */
  private final BindingManager localChangeManager = new BindingManager(
      new ContextManager(), new CommandManager());

  /**
   * A map of all the scheme identifiers indexed by the names that appear in
   * the user interface. This look-up table is built during initialization.
   */
  private Map schemeIdsByUniqueName;

  /**
   * A map of all the scheme names in the user interface indexed by their
   * identifiers. This look-up table is built during initialization.
   */
  private Map schemeUniqueNamesById;

  /**
   * The sort order to be used on the view tab to display all of the key
   * bindings. This sort order can be changed by the user. This array is never
   * <code>null</code>, but may be empty.
   */
  private int[] sortOrder = { VIEW_CATEGORY_COLUMN_INDEX,
      VIEW_COMMAND_COLUMN_INDEX, VIEW_KEY_SEQUENCE_COLUMN_INDEX,
      VIEW_CONTEXT_COLUMN_INDEX };

  /**
   * The top-most tab folder for the preference page -- containing a view and
   * a modify tab.
   */
  private TabFolder tabFolder;

  /**
   * A table of the key bindings currently defined. This table appears on the
   * view tab; it is intended to be an easy way for users to learn the key
   * bindings in Eclipse. This value is only <code>null</code> until the
   * controls are first created.
   */
  private Table tableBindings;

  /**
   * The table containing all of the bindings matching the selected command.
   */
  private Table tableBindingsForCommand;

  /**
   * The table containing all of the bindings matching the current trigger
   * sequence.
   */
  private Table tableBindingsForTriggerSequence;

  /**
   * The text widget where keys are entered. This widget is managed by
   * <code>textTriggerSequenceManager</code>, which provides its special
   * behaviour.
   */
  private Text textTriggerSequence;

  /**
   * The manager for the text widget that traps incoming key events. This
   * manager should be used to access the widget, rather than accessing the
   * widget directly.
   */
  private KeySequenceText textTriggerSequenceManager;

 
  /* (non-Javadoc)
   * @see org.eclipse.jface.preference.PreferencePage#applyData(java.lang.Object)
   */
  public void applyData(Object data) {
    if(data instanceof Binding) {
      editBinding((Binding) data);
    }
  }
  protected final Control createContents(final Composite parent) {
   
    PlatformUI.getWorkbench().getHelpSystem()
      .setHelp(parent, IWorkbenchHelpContextIds.KEYS_PREFERENCE_PAGE);
   
    tabFolder = new TabFolder(parent, SWT.NULL);

    // View tab
    final TabItem viewTab = new TabItem(tabFolder, SWT.NULL);
    viewTab.setText(Util.translateString(RESOURCE_BUNDLE, "viewTab.Text")); //$NON-NLS-1$
    viewTab.setControl(createViewTab(tabFolder));

    // Modify tab
    final TabItem modifyTab = new TabItem(tabFolder, SWT.NULL);
    modifyTab.setText(Util.translateString(RESOURCE_BUNDLE,
        "modifyTab.Text")); //$NON-NLS-1$
    modifyTab.setControl(createModifyTab(tabFolder));

    // Do some fancy stuff.
    applyDialogFont(tabFolder);
    final IPreferenceStore store = getPreferenceStore();
    final int selectedTab = store
        .getInt(IPreferenceConstants.KEYS_PREFERENCE_SELECTED_TAB);
    if ((tabFolder.getItemCount() > selectedTab) && (selectedTab > 0)) {
      tabFolder.setSelection(selectedTab);
    }
   
    return tabFolder;
  }

  /**
   * Creates the tab that allows the user to change the keyboard shortcuts.
   *
   * @param parent
   *            The tab folder in which the tab should be created; must not be
   *            <code>null</code>.
   * @return The composite which represents the contents of the tab; never
   *         <code>null</code>.
   */
  private final Composite createModifyTab(final TabFolder parent) {
    final Composite composite = new Composite(parent, SWT.NULL);
    composite.setLayout(new GridLayout());
    GridData gridData = new GridData(GridData.FILL_BOTH);
    composite.setLayoutData(gridData);
    final Composite compositeKeyConfiguration = new Composite(composite,
        SWT.NULL);
    GridLayout gridLayout = new GridLayout();
    gridLayout.numColumns = 3;
    compositeKeyConfiguration.setLayout(gridLayout);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    compositeKeyConfiguration.setLayoutData(gridData);
    final Label labelKeyConfiguration = new Label(
        compositeKeyConfiguration, SWT.LEFT);
    labelKeyConfiguration.setText(Util.translateString(RESOURCE_BUNDLE,
        "labelScheme")); //$NON-NLS-1$
    comboScheme = new Combo(compositeKeyConfiguration, SWT.READ_ONLY);
    gridData = new GridData();
    gridData.widthHint = 200;
    comboScheme.setLayoutData(gridData);
    comboScheme.setVisibleItemCount(ITEMS_TO_SHOW);

    comboScheme.addSelectionListener(new SelectionAdapter() {
      public final void widgetSelected(final SelectionEvent e) {
        selectedComboScheme();
      }
    });

    labelSchemeExtends = new Label(compositeKeyConfiguration, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    labelSchemeExtends.setLayoutData(gridData);
    final Control spacer = new Composite(composite, SWT.NULL);
    gridData = new GridData();
    gridData.heightHint = 10;
    gridData.widthHint = 10;
    spacer.setLayoutData(gridData);
    final Group groupCommand = new Group(composite, SWT.SHADOW_NONE);
    gridLayout = new GridLayout();
    gridLayout.numColumns = 3;
    groupCommand.setLayout(gridLayout);
    gridData = new GridData(GridData.FILL_BOTH);
    groupCommand.setLayoutData(gridData);
    groupCommand.setText(Util.translateString(RESOURCE_BUNDLE,
        "groupCommand")); //$NON-NLS-1$ 
    final Label labelCategory = new Label(groupCommand, SWT.LEFT);
    gridData = new GridData();
    labelCategory.setLayoutData(gridData);
    labelCategory.setText(Util.translateString(RESOURCE_BUNDLE,
        "labelCategory")); //$NON-NLS-1$
    comboCategory = new Combo(groupCommand, SWT.READ_ONLY);
    gridData = new GridData();
    gridData.horizontalSpan = 2;
    gridData.widthHint = 200;
    comboCategory.setLayoutData(gridData);
    comboCategory.setVisibleItemCount(ITEMS_TO_SHOW);

    comboCategory.addSelectionListener(new SelectionAdapter() {
      public final void widgetSelected(final SelectionEvent e) {
        update();
      }
    });

    final Label labelCommand = new Label(groupCommand, SWT.LEFT);
    gridData = new GridData();
    labelCommand.setLayoutData(gridData);
    labelCommand.setText(Util.translateString(RESOURCE_BUNDLE,
        "labelCommand")); //$NON-NLS-1$
    comboCommand = new Combo(groupCommand, SWT.READ_ONLY);
    gridData = new GridData();
    gridData.horizontalSpan = 2;
    gridData.widthHint = 300;
    comboCommand.setLayoutData(gridData);
    comboCommand.setVisibleItemCount(9);

    comboCommand.addSelectionListener(new SelectionAdapter() {
      public final void widgetSelected(final SelectionEvent e) {
        update();
      }
    });

    labelBindingsForCommand = new Label(groupCommand, SWT.LEFT);
    gridData = new GridData(GridData.VERTICAL_ALIGN_BEGINNING);
    gridData.verticalAlignment = GridData.FILL_VERTICAL;
    labelBindingsForCommand.setLayoutData(gridData);
    labelBindingsForCommand.setText(Util.translateString(RESOURCE_BUNDLE,
        "labelAssignmentsForCommand")); //$NON-NLS-1$
    tableBindingsForCommand = new Table(groupCommand, SWT.BORDER
        | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL);
    tableBindingsForCommand.setHeaderVisible(true);
    gridData = new GridData(GridData.FILL_BOTH);
    gridData.heightHint = 60;
    gridData.horizontalSpan = 2;
    gridData.widthHint = "carbon".equals(SWT.getPlatform()) ? 620 : 520; //$NON-NLS-1$
    tableBindingsForCommand.setLayoutData(gridData);
    TableColumn tableColumnDelta = new TableColumn(tableBindingsForCommand,
        SWT.NULL, 0);
    tableColumnDelta.setResizable(false);
    tableColumnDelta.setText(Util.ZERO_LENGTH_STRING);
    tableColumnDelta.setWidth(20);
    TableColumn tableColumnContext = new TableColumn(
        tableBindingsForCommand, SWT.NULL, 1);
    tableColumnContext.setResizable(true);
    tableColumnContext.setText(Util.translateString(RESOURCE_BUNDLE,
        "tableColumnContext")); //$NON-NLS-1$
    tableColumnContext.pack();
    tableColumnContext.setWidth(200);
    final TableColumn tableColumnKeySequence = new TableColumn(
        tableBindingsForCommand, SWT.NULL, 2);
    tableColumnKeySequence.setResizable(true);
    tableColumnKeySequence.setText(Util.translateString(RESOURCE_BUNDLE,
        "tableColumnKeySequence")); //$NON-NLS-1$
    tableColumnKeySequence.pack();
    tableColumnKeySequence.setWidth(300);

    tableBindingsForCommand.addMouseListener(new MouseAdapter() {

      public void mouseDoubleClick(MouseEvent mouseEvent) {
        update();
      }
    });

    tableBindingsForCommand.addSelectionListener(new SelectionAdapter() {

      public void widgetSelected(SelectionEvent selectionEvent) {
        selectedTableBindingsForCommand();
      }
    });

    final Group groupKeySequence = new Group(composite, SWT.SHADOW_NONE);
    gridLayout = new GridLayout();
    gridLayout.numColumns = 4;
    groupKeySequence.setLayout(gridLayout);
    gridData = new GridData(GridData.FILL_BOTH);
    groupKeySequence.setLayoutData(gridData);
    groupKeySequence.setText(Util.translateString(RESOURCE_BUNDLE,
        "groupKeySequence")); //$NON-NLS-1$ 
    final Label labelKeySequence = new Label(groupKeySequence, SWT.LEFT);
    gridData = new GridData();
    labelKeySequence.setLayoutData(gridData);
    labelKeySequence.setText(Util.translateString(RESOURCE_BUNDLE,
        "labelKeySequence")); //$NON-NLS-1$

    // The text widget into which the key strokes will be entered.
    textTriggerSequence = new Text(groupKeySequence, SWT.BORDER);
    // On MacOS X, this font will be changed by KeySequenceText
    textTriggerSequence.setFont(groupKeySequence.getFont());
    gridData = new GridData();
    gridData.horizontalSpan = 2;
    gridData.widthHint = 300;
    textTriggerSequence.setLayoutData(gridData);
    textTriggerSequence.addModifyListener(new ModifyListener() {
      public void modifyText(ModifyEvent e) {
        update();
      }
    });
    textTriggerSequence.addFocusListener(new FocusListener() {
      public void focusGained(FocusEvent e) {
        bindingService.setKeyFilterEnabled(false);
      }

      public void focusLost(FocusEvent e) {
        bindingService.setKeyFilterEnabled(true);
      }
    });
    textTriggerSequence.addDisposeListener(new DisposeListener() {
      public void widgetDisposed(DisposeEvent e) {
        if (!bindingService.isKeyFilterEnabled()) {
          bindingService.setKeyFilterEnabled(true);
        }
      }
    });

    // The manager for the key sequence text widget.
    textTriggerSequenceManager = new KeySequenceText(textTriggerSequence);
    textTriggerSequenceManager.setKeyStrokeLimit(4);

    // Button for adding trapped key strokes
    final Button buttonAddKey = new Button(groupKeySequence, SWT.LEFT
        | SWT.ARROW);
    buttonAddKey.setToolTipText(Util.translateString(RESOURCE_BUNDLE,
        "buttonAddKey.ToolTipText")); //$NON-NLS-1$
    gridData = new GridData();
    gridData.heightHint = comboCategory.getTextHeight();
    buttonAddKey.setLayoutData(gridData);

    // Arrow buttons aren't normally added to the tab list. Let's fix that.
    final Control[] tabStops = groupKeySequence.getTabList();
    final ArrayList newTabStops = new ArrayList();
    for (int i = 0; i < tabStops.length; i++) {
      Control tabStop = tabStops[i];
      newTabStops.add(tabStop);
      if (textTriggerSequence.equals(tabStop)) {
        newTabStops.add(buttonAddKey);
      }
    }
    final Control[] newTabStopArray = (Control[]) newTabStops
        .toArray(new Control[newTabStops.size()]);
    groupKeySequence.setTabList(newTabStopArray);

    // Construct the menu to attach to the above button.
    final Menu menuButtonAddKey = new Menu(buttonAddKey);
    final Iterator trappedKeyItr = KeySequenceText.TRAPPED_KEYS.iterator();
    while (trappedKeyItr.hasNext()) {
      final KeyStroke trappedKey = (KeyStroke) trappedKeyItr.next();
      final MenuItem menuItem = new MenuItem(menuButtonAddKey, SWT.PUSH);
      menuItem.setText(trappedKey.format());
      menuItem.addSelectionListener(new SelectionAdapter() {

        public void widgetSelected(SelectionEvent e) {
          textTriggerSequenceManager.insert(trappedKey);
          textTriggerSequence.setFocus();
          textTriggerSequence.setSelection(textTriggerSequence
              .getTextLimit());
        }
      });
    }
    buttonAddKey.addSelectionListener(new SelectionAdapter() {

      public void widgetSelected(SelectionEvent selectionEvent) {
        Point buttonLocation = buttonAddKey.getLocation();
        buttonLocation = groupKeySequence.toDisplay(buttonLocation.x,
            buttonLocation.y);
        Point buttonSize = buttonAddKey.getSize();
        menuButtonAddKey.setLocation(buttonLocation.x, buttonLocation.y
            + buttonSize.y);
        menuButtonAddKey.setVisible(true);
      }
    });

    labelBindingsForTriggerSequence = new Label(groupKeySequence, SWT.LEFT);
    gridData = new GridData(GridData.VERTICAL_ALIGN_BEGINNING);
    gridData.verticalAlignment = GridData.FILL_VERTICAL;
    labelBindingsForTriggerSequence.setLayoutData(gridData);
    labelBindingsForTriggerSequence.setText(Util.translateString(
        RESOURCE_BUNDLE, "labelAssignmentsForKeySequence")); //$NON-NLS-1$
    tableBindingsForTriggerSequence = new Table(groupKeySequence,
        SWT.BORDER | SWT.FULL_SELECTION | SWT.H_SCROLL | SWT.V_SCROLL);
    tableBindingsForTriggerSequence.setHeaderVisible(true);
    gridData = new GridData(GridData.FILL_BOTH);
    gridData.heightHint = 60;
    gridData.horizontalSpan = 3;
    gridData.widthHint = "carbon".equals(SWT.getPlatform()) ? 620 : 520; //$NON-NLS-1$
    tableBindingsForTriggerSequence.setLayoutData(gridData);
    tableColumnDelta = new TableColumn(tableBindingsForTriggerSequence,
        SWT.NULL, 0);
    tableColumnDelta.setResizable(false);
    tableColumnDelta.setText(Util.ZERO_LENGTH_STRING);
    tableColumnDelta.setWidth(20);
    tableColumnContext = new TableColumn(tableBindingsForTriggerSequence,
        SWT.NULL, 1);
    tableColumnContext.setResizable(true);
    tableColumnContext.setText(Util.translateString(RESOURCE_BUNDLE,
        "tableColumnContext")); //$NON-NLS-1$
    tableColumnContext.pack();
    tableColumnContext.setWidth(200);
    final TableColumn tableColumnCommand = new TableColumn(
        tableBindingsForTriggerSequence, SWT.NULL, 2);
    tableColumnCommand.setResizable(true);
    tableColumnCommand.setText(Util.translateString(RESOURCE_BUNDLE,
        "tableColumnCommand")); //$NON-NLS-1$
    tableColumnCommand.pack();
    tableColumnCommand.setWidth(300);

    tableBindingsForTriggerSequence.addMouseListener(new MouseAdapter() {

      public void mouseDoubleClick(MouseEvent mouseEvent) {
        update();
      }
    });

    tableBindingsForTriggerSequence
        .addSelectionListener(new SelectionAdapter() {

          public void widgetSelected(SelectionEvent selectionEvent) {
            selectedTableBindingsForTriggerSequence();
          }
        });

    final Composite compositeContext = new Composite(composite, SWT.NULL);
    gridLayout = new GridLayout();
    gridLayout.numColumns = 3;
    compositeContext.setLayout(gridLayout);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    compositeContext.setLayoutData(gridData);
    final Label labelContext = new Label(compositeContext, SWT.LEFT);
    labelContext.setText(Util.translateString(RESOURCE_BUNDLE,
        "labelContext")); //$NON-NLS-1$
    comboContext = new Combo(compositeContext, SWT.READ_ONLY);
    gridData = new GridData();
    gridData.widthHint = 250;
    comboContext.setLayoutData(gridData);
    comboContext.setVisibleItemCount(ITEMS_TO_SHOW);

    comboContext.addSelectionListener(new SelectionAdapter() {
      public final void widgetSelected(final SelectionEvent e) {
        update();
      }
    });

    labelContextExtends = new Label(compositeContext, SWT.LEFT);
    gridData = new GridData(GridData.FILL_HORIZONTAL);
    labelContextExtends.setLayoutData(gridData);
    final Composite compositeButton = new Composite(composite, SWT.NULL);
    gridLayout = new GridLayout();
    gridLayout.marginHeight = 20;
    gridLayout.marginWidth = 0;
    gridLayout.numColumns = 3;
    compositeButton.setLayout(gridLayout);
    gridData = new GridData();
    compositeButton.setLayoutData(gridData);
    buttonAdd = new Button(compositeButton, SWT.CENTER | SWT.PUSH);
    gridData = new GridData();
    int widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
    buttonAdd.setText(Util.translateString(RESOURCE_BUNDLE, "buttonAdd")); //$NON-NLS-1$
    gridData.widthHint = Math.max(widthHint, buttonAdd.computeSize(
        SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
    buttonAdd.setLayoutData(gridData);

    buttonAdd.addSelectionListener(new SelectionAdapter() {

      public void widgetSelected(SelectionEvent selectionEvent) {
        selectedButtonAdd();
      }
    });

    buttonRemove = new Button(compositeButton, SWT.CENTER | SWT.PUSH);
    gridData = new GridData();
    widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
    buttonRemove.setText(Util.translateString(RESOURCE_BUNDLE,
        "buttonRemove")); //$NON-NLS-1$
    gridData.widthHint = Math.max(widthHint, buttonRemove.computeSize(
        SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
    buttonRemove.setLayoutData(gridData);

    buttonRemove.addSelectionListener(new SelectionAdapter() {

      public void widgetSelected(SelectionEvent selectionEvent) {
        selectedButtonRemove();
      }
    });

    buttonRestore = new Button(compositeButton, SWT.CENTER | SWT.PUSH);
    gridData = new GridData();
    widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
    buttonRestore.setText(Util.translateString(RESOURCE_BUNDLE,
        "buttonRestore")); //$NON-NLS-1$
    gridData.widthHint = Math.max(widthHint, buttonRestore.computeSize(
        SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
    buttonRestore.setLayoutData(gridData);

    buttonRestore.addSelectionListener(new SelectionAdapter() {

      public void widgetSelected(SelectionEvent selectionEvent) {
        selectedButtonRestore();
      }
    });

    return composite;
  }

  /**
   * Creates a tab on the main page for displaying an uneditable list of the
   * current key bindings. This is intended as a discovery tool for new users.
   * It shows all of the key bindings for the current key configuration,
   * platform and locale.
   *
   * @param parent
   *            The tab folder in which the tab should be created; must not be
   *            <code>null</code>.
   * @return The newly created composite containing all of the controls; never
   *         <code>null</code>.
   * @since 3.1
   */
  private final Composite createViewTab(final TabFolder parent) {
    GridData gridData = null;
    int widthHint;

    // Create the composite for the tab.
    final Composite composite = new Composite(parent, SWT.NONE);
    composite.setLayoutData(new GridData(GridData.FILL_BOTH));
    composite.setLayout(new GridLayout());

    // Place a table inside the tab.
    tableBindings = new Table(composite, SWT.BORDER | SWT.FULL_SELECTION
        | SWT.H_SCROLL | SWT.V_SCROLL);
    tableBindings.setHeaderVisible(true);
    gridData = new GridData(GridData.FILL_BOTH);
    gridData.heightHint = 400;
    gridData.horizontalSpan = 2;
    tableBindings.setLayoutData(gridData);
    final TableColumn tableColumnCategory = new TableColumn(tableBindings,
        SWT.NONE, VIEW_CATEGORY_COLUMN_INDEX);
    tableColumnCategory
        .setText(SORTED_COLUMN_NAMES[VIEW_CATEGORY_COLUMN_INDEX]);
    tableColumnCategory
        .addSelectionListener(new SortOrderSelectionListener(
            VIEW_CATEGORY_COLUMN_INDEX));
    final TableColumn tableColumnCommand = new TableColumn(tableBindings,
        SWT.NONE, VIEW_COMMAND_COLUMN_INDEX);
    tableColumnCommand
        .setText(UNSORTED_COLUMN_NAMES[VIEW_COMMAND_COLUMN_INDEX]);
    tableColumnCommand.addSelectionListener(new SortOrderSelectionListener(
        VIEW_COMMAND_COLUMN_INDEX));
    final TableColumn tableColumnKeySequence = new TableColumn(
        tableBindings, SWT.NONE, VIEW_KEY_SEQUENCE_COLUMN_INDEX);
    tableColumnKeySequence
        .setText(UNSORTED_COLUMN_NAMES[VIEW_KEY_SEQUENCE_COLUMN_INDEX]);
    tableColumnKeySequence
        .addSelectionListener(new SortOrderSelectionListener(
            VIEW_KEY_SEQUENCE_COLUMN_INDEX));
    final TableColumn tableColumnContext = new TableColumn(tableBindings,
        SWT.NONE, VIEW_CONTEXT_COLUMN_INDEX);
    tableColumnContext
        .setText(UNSORTED_COLUMN_NAMES[VIEW_CONTEXT_COLUMN_INDEX]);
    tableColumnContext.addSelectionListener(new SortOrderSelectionListener(
        VIEW_CONTEXT_COLUMN_INDEX));
    tableBindings.addSelectionListener(new SelectionAdapter() {
      public final void widgetDefaultSelected(final SelectionEvent e) {
        selectedTableKeyBindings();
      }
    });

    // A composite for the buttons.
    final Composite buttonBar = new Composite(composite, SWT.NONE);
    buttonBar.setLayout(new GridLayout(2, false));
    gridData = new GridData();
    gridData.horizontalAlignment = GridData.END;
    buttonBar.setLayoutData(gridData);

    // A button for editing the current selection.
    final Button editButton = new Button(buttonBar, SWT.PUSH);
    gridData = new GridData();
    widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
    editButton.setText(Util.translateString(RESOURCE_BUNDLE, "buttonEdit")); //$NON-NLS-1$
    gridData.widthHint = Math.max(widthHint, editButton.computeSize(
        SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
    editButton.setLayoutData(gridData);
    editButton.addSelectionListener(new SelectionListener() {

      /*
       * (non-Javadoc)
       *
       * @see org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org.eclipse.swt.events.SelectionEvent)
       */
      public final void widgetDefaultSelected(final SelectionEvent event) {
        selectedTableKeyBindings();
      }

      /*
       * (non-Javadoc)
       *
       * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
       */
      public void widgetSelected(SelectionEvent e) {
        widgetDefaultSelected(e);
      }
    });

    // A button for exporting the contents to a file.
    final Button buttonExport = new Button(buttonBar, SWT.PUSH);
    gridData = new GridData();
    widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
    buttonExport.setText(Util.translateString(RESOURCE_BUNDLE,
        "buttonExport")); //$NON-NLS-1$
    gridData.widthHint = Math.max(widthHint, buttonExport.computeSize(
        SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
    buttonExport.setLayoutData(gridData);
    buttonExport.addSelectionListener(new SelectionListener() {

      /*
       * (non-Javadoc)
       *
       * @see org.eclipse.swt.events.SelectionListener#widgetDefaultSelected(org.eclipse.swt.events.SelectionEvent)
       */
      public final void widgetDefaultSelected(final SelectionEvent event) {
        selectedButtonExport();
      }

      /*
       * (non-Javadoc)
       *
       * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
       */
      public void widgetSelected(SelectionEvent e) {
        widgetDefaultSelected(e);
      }
    });

    return composite;
  }

  protected IPreferenceStore doGetPreferenceStore() {
    return PrefUtil.getInternalPreferenceStore();
  }

  /**
   * Allows the user to change the key bindings for a particular command.
   * Switches the tab to the modify tab, and then selects the category and
   * command that corresponds with the given command name. It then selects the
   * given key sequence and gives focus to the key sequence text widget.
   *
   * @param binding
   *            The binding to be edited; if <code>null</code>, then just
   *            switch to the modify tab. If the <code>binding</code> does
   *            not correspond to anything in the keys preference page, then
   *            this also just switches to the modify tab.
   * @since 3.1
   */
  public final void editBinding(final Binding binding) {
    // Switch to the modify tab.
    tabFolder.setSelection(TAB_INDEX_MODIFY);

    // If there is no command name, stop here.
    if (binding == null) {
      return;
    }

    /*
     * Get the corresponding category and command names. If either is
     * undefined, then we can just stop now. We won't be able to find their
     * name.
     */
    final ParameterizedCommand command = binding.getParameterizedCommand();
    String categoryName = null;
    String commandName = null;
    try {
      categoryName = command.getCommand().getCategory().getName();
      commandName = command.getName();
    } catch (final NotDefinedException e) {
      return; // no name
    }

    // Update the category combo box.
    final String[] categoryNames = comboCategory.getItems();
    int i = 0;
    for (; i < categoryNames.length; i++) {
      if (categoryName.equals(categoryNames[i])) {
        break;
      }
    }
    if (i >= comboCategory.getItemCount()) {
      // Couldn't find the category, so abort.
      return;
    }
    comboCategory.select(i);

    // Update the commands combo box.
    updateComboCommand();

    // Update the command combo box.
    final String[] commandNames = comboCommand.getItems();
    int j = 0;
    for (; j < commandNames.length; j++) {
      if (commandName.equals(commandNames[j])) {
        if (comboCommand.getSelectionIndex() != j) {
          comboCommand.select(j);
        }
        break;
      }
    }
    if (j >= comboCommand.getItemCount()) {
      // Couldn't find the command, so just select the first and then stop
      if (comboCommand.getSelectionIndex() != 0) {
        comboCommand.select(0);
      }
      update();
      return;
    }

    /*
     * Update and validate the state of the modify tab in response to these
     * selection changes.
     */
    update();

    // Select the right key binding, if possible.
    final TableItem[] items = tableBindingsForCommand.getItems();
    int k = 0;
    for (; k < items.length; k++) {
      final String currentKeySequence = items[k].getText(2);
      if (binding.getTriggerSequence().format()
          .equals(currentKeySequence)) {
        break;
      }
    }
    if (k < tableBindingsForCommand.getItemCount()) {
      tableBindingsForCommand.select(k);
      tableBindingsForCommand.notifyListeners(SWT.Selection, null);
      textTriggerSequence.setFocus();
    }
  }

  /**
   * Returns the identifier for the currently selected category.
   *
   * @return The selected category; <code>null</code> if none.
   */
  private final String getCategoryId() {
    return !commandIdsByCategoryId.containsKey(null)
        || comboCategory.getSelectionIndex() > 0 ? (String) categoryIdsByUniqueName
        .get(comboCategory.getText())
        : null;
  }

  /**
   * Returns the identifier for the currently selected context.
   *
   * @return The selected context; <code>null</code> if none.
   */
  private final String getContextId() {
    return comboContext.getSelectionIndex() >= 0 ? (String) contextIdsByUniqueName
        .get(comboContext.getText())
        : null;
  }

  /**
   * Returns the current trigger sequence.
   *
   * @return The trigger sequence; may be empty, but never <code>null</code>.
   */
  private final KeySequence getKeySequence() {
    return textTriggerSequenceManager.getKeySequence();
  }

  /**
   * Returns the currently-selected fully-parameterized command.
   *
   * @return The selected fully-parameterized command; <code>null</code> if
   *         none.
   */
  private final ParameterizedCommand getParameterizedCommand() {
    final int selectionIndex = comboCommand.getSelectionIndex();
    if ((selectionIndex >= 0) && (commands != null)
        && (selectionIndex < commands.length)) {
      return commands[selectionIndex];
    }

    return null;
  }

  /**
   * Returns the identifier for the currently selected scheme.
   *
   * @return The selected scheme; <code>null</code> if none.
   */
  private final String getSchemeId() {
    return comboScheme.getSelectionIndex() >= 0 ? (String) schemeIdsByUniqueName
        .get(comboScheme.getText())
        : null;
  }

  public final void init(final IWorkbench workbench) {
    activityManager = workbench.getActivitySupport().getActivityManager();
    bindingService = (IBindingService) workbench.getService(IBindingService.class);
    commandService = (ICommandService) workbench.getService(ICommandService.class);
    contextService = (IContextService) workbench.getService(IContextService.class);
  }

  /**
   * Checks whether the activity manager knows anything about this command
   * identifier. If the activity manager is currently filtering this command,
   * then it does not appear in the user interface.
   *
   * @param command
   *            The command which should be checked against the activities;
   *            must not be <code>null</code>.
   * @return <code>true</code> if the command identifier is not filtered;
   *         <code>false</code> if it is
   */
  private final boolean isActive(final Command command) {
    return activityManager.getIdentifier(command.getId()).isEnabled();
  }

  /**
   * Logs the given exception, and opens an error dialog saying that something
   * went wrong. The exception is assumed to have something to do with the
   * preference store.
   *
   * @param exception
   *            The exception to be logged; must not be <code>null</code>.
   */
  private final void logPreferenceStoreException(final Throwable exception) {
    final String message = Util.translateString(RESOURCE_BUNDLE,
        "PreferenceStoreError.Message"); //$NON-NLS-1$
    String exceptionMessage = exception.getMessage();
    if (exceptionMessage == null) {
      exceptionMessage = message;
    }
    final IStatus status = new Status(IStatus.ERROR,
        WorkbenchPlugin.PI_WORKBENCH, 0, exceptionMessage, exception);
    WorkbenchPlugin.log(message, status);
    StatusUtil.handleStatus(message, exception, StatusManager.SHOW);
  }

  public final boolean performCancel() {
    // Save the selected tab for future reference.
    persistSelectedTab();

    return super.performCancel();
  }

  protected final void performDefaults() {
    // Ask the user to confirm
    final String title = Util.translateString(RESOURCE_BUNDLE,
        "restoreDefaultsMessageBoxText"); //$NON-NLS-1$
    final String message = Util.translateString(RESOURCE_BUNDLE,
        "restoreDefaultsMessageBoxMessage"); //$NON-NLS-1$
    final boolean confirmed = MessageDialog.openConfirm(getShell(), title,
        message);

    if (confirmed) {
      // Fix the scheme in the local changes.
      final String defaultSchemeId = bindingService.getDefaultSchemeId();
      final Scheme defaultScheme = localChangeManager
          .getScheme(defaultSchemeId);
      try {
        localChangeManager.setActiveScheme(defaultScheme);
      } catch (final NotDefinedException e) {
        // At least we tried....
      }

      // Fix the bindings in the local changes.
      final Binding[] currentBindings = localChangeManager.getBindings();
      final int currentBindingsLength = currentBindings.length;
      final Set trimmedBindings = new HashSet();
      for (int i = 0; i < currentBindingsLength; i++) {
        final Binding binding = currentBindings[i];
        if (binding.getType() != Binding.USER) {
          trimmedBindings.add(binding);
        }
      }
      final Binding[] trimmedBindingArray = (Binding[]) trimmedBindings
          .toArray(new Binding[trimmedBindings.size()]);
      localChangeManager.setBindings(trimmedBindingArray);

      // Apply the changes.
      try {
        bindingService.savePreferences(defaultScheme,
            trimmedBindingArray);
      } catch (final IOException e) {
        logPreferenceStoreException(e);
      }
    }

    setScheme(localChangeManager.getActiveScheme()); // update the scheme
    update(true);
    super.performDefaults();
  }

  public final boolean performOk() {
    // Save the preferences.
    try {
      bindingService.savePreferences(
          localChangeManager.getActiveScheme(), localChangeManager
              .getBindings());
    } catch (final IOException e) {
      logPreferenceStoreException(e);
    }

    // Save the selected tab for future reference.
    persistSelectedTab();

    return super.performOk();
  }

  /**
   * Remembers the currently selected tab for when the preference page next
   * opens.
   */
  private final void persistSelectedTab() {
    final IPreferenceStore store = getPreferenceStore();
    store.setValue(IPreferenceConstants.KEYS_PREFERENCE_SELECTED_TAB,
        tabFolder.getSelectionIndex());
  }

  /**
   * Handles the selection event on the add button. This removes all
   * user-defined bindings matching the given key sequence, scheme and
   * context. It then adds a new binding with the current selections.
   */
  private final void selectedButtonAdd() {
    final ParameterizedCommand command = getParameterizedCommand();
    final String contextId = getContextId();
    final String schemeId = getSchemeId();
    final KeySequence keySequence = getKeySequence();
    localChangeManager.removeBindings(keySequence, schemeId, contextId,
        null, null, null, Binding.USER);
    localChangeManager.addBinding(new KeyBinding(keySequence, command,
        schemeId, contextId, null, null, null, Binding.USER));
    update(true);
  }

  /**
   * Provides a facility for exporting the viewable list of key bindings to a
   * file. Currently, this only supports exporting to a list of
   * comma-separated values. The user is prompted for which file should
   * receive our bounty.
   *
   * @since 3.1
   */
  private final void selectedButtonExport() {
    final FileDialog fileDialog = new FileDialog(getShell(), SWT.SAVE);
    fileDialog.setFilterExtensions(new String[] { "*.csv" }); //$NON-NLS-1$
    fileDialog.setFilterNames(new String[] { Util.translateString(
        RESOURCE_BUNDLE, "csvFilterName") }); //$NON-NLS-1$
    final String filePath = fileDialog.open();
    if (filePath == null) {
      return;
    }

    final SafeRunnable runnable = new SafeRunnable() {
      public final void run() throws IOException {
        Writer fileWriter = null;
        try {
          fileWriter = new BufferedWriter(new FileWriter(filePath));
          final TableItem[] items = tableBindings.getItems();
          final int numColumns = tableBindings.getColumnCount();
          for (int i = 0; i < items.length; i++) {
            final TableItem item = items[i];
            for (int j = 0; j < numColumns; j++) {
              String buf = Util.replaceAll(item.getText(j), "\"", //$NON-NLS-1$
                  "\"\""); //$NON-NLS-1$
              fileWriter.write("\"" + buf + "\"")//$NON-NLS-1$//$NON-NLS-2$
              if (j < numColumns - 1) {
                fileWriter.write(',');
              }
            }
            fileWriter.write(System.getProperty("line.separator")); //$NON-NLS-1$
          }

        } finally {
          if (fileWriter != null) {
            try {
              fileWriter.close();
            } catch (final IOException e) {
              // At least I tried.
            }
          }

        }
      }
    };
    SafeRunner.run(runnable);
  }
 
  /**
   * Handles the selection event on the remove button. This removes all
   * user-defined bindings matching the given key sequence, scheme and
   * context. It then adds a new deletion binding for the selected trigger
   * sequence.
   */
  private final void selectedButtonRemove() {
    final String contextId = getContextId();
    final String schemeId = getSchemeId();
    final KeySequence keySequence = getKeySequence();
    localChangeManager.removeBindings(keySequence, schemeId, contextId,
        null, null, null, Binding.USER);
    localChangeManager.addBinding(new KeyBinding(keySequence, null,
        schemeId, contextId, null, null, null, Binding.USER));
    update(true);
  }

  /**
   * Handles the selection event on the restore button. This removes all
   * user-defined bindings matching the given key sequence, scheme and
   * context.
   */
  private final void selectedButtonRestore() {
    String contextId = getContextId();
    String schemeId = getSchemeId();
    KeySequence keySequence = getKeySequence();
    localChangeManager.removeBindings(keySequence, schemeId, contextId,
        null, null, null, Binding.USER);
    update(true);
  }

  /**
   * Updates the local managers active scheme, and then updates the interface.
   */
  private final void selectedComboScheme() {
    final String activeSchemeId = getSchemeId();
    final Scheme activeScheme = localChangeManager
        .getScheme(activeSchemeId);
    try {
      localChangeManager.setActiveScheme(activeScheme);
    } catch (final NotDefinedException e) {
      // Oh, well.
    }
    update(true);
  }

  /**
   * Handles the selection event on the table containing the bindings for a
   * particular command. This updates the context and trigger sequence based
   * on the selected binding.
   */
  private final void selectedTableBindingsForCommand() {
    final int selection = tableBindingsForCommand.getSelectionIndex();
    if ((selection >= 0)
        && (selection < tableBindingsForCommand.getItemCount())) {
      final TableItem item = tableBindingsForCommand.getItem(selection);
      final KeyBinding binding = (KeyBinding) item.getData(ITEM_DATA_KEY);
      setContextId(binding.getContextId());
      setKeySequence(binding.getKeySequence());
    }

    update();
  }

  /**
   * Handles the selection event on the table containing the bindings for a
   * particular trigger sequence. This updates the context based on the
   * selected binding.
   */
  private final void selectedTableBindingsForTriggerSequence() {
    final int selection = tableBindingsForTriggerSequence
        .getSelectionIndex();
    if ((selection >= 0)
        && (selection < tableBindingsForTriggerSequence.getItemCount())) {
      final TableItem item = tableBindingsForTriggerSequence
          .getItem(selection);
      final Binding binding = (Binding) item.getData(ITEM_DATA_KEY);
      setContextId(binding.getContextId());
    }

    update();
  }

  /**
   * Responds to some kind of trigger on the View tab by taking the current
   * selection on the key bindings table and selecting the appropriate items
   * in the Modify tab.
   *
   * @since 3.1
   */
  private final void selectedTableKeyBindings() {
    final int selectionIndex = tableBindings.getSelectionIndex();
    if (selectionIndex != -1) {
      final TableItem item = tableBindings.getItem(selectionIndex);
      final Binding binding = (Binding) item.getData(BINDING_KEY);
      editBinding(binding);

    } else {
      editBinding(null);
    }
  }

  /**
   * Changes the selected context name in the context combo box. The context
   * selected is either the one matching the identifier provided (if
   * possible), or the default context identifier. If no matching name can be
   * found in the combo, then the first item is selected.
   *
   * @param contextId
   *            The context identifier for the context to be selected in the
   *            combo box; may be <code>null</code>.
   */
  private final void setContextId(final String contextId) {
    // Clear the current selection.
    comboContext.clearSelection();
    comboContext.deselectAll();

    // Figure out which name to look for.
    String contextName = (String) contextUniqueNamesById.get(contextId);
    if (contextName == null) {
      contextName = (String) contextUniqueNamesById
          .get(IContextIds.CONTEXT_ID_WINDOW);
    }
    if (contextName == null) {
      contextName = Util.ZERO_LENGTH_STRING;
    }

    // Scan the list for the selection we're looking for.
    final String[] items = comboContext.getItems();
    boolean found = false;
    for (int i = 0; i < items.length; i++) {
      if (contextName.equals(items[i])) {
        comboContext.select(i);
        found = true;
        break;
      }
    }

    // If we didn't find an item, then set the first item as selected.
    if ((!found) && (items.length > 0)) {
      comboContext.select(0);
    }
  }

  /**
   * Sets the current trigger sequence.
   *
   * @param keySequence
   *            The trigger sequence; may be <code>null</code>.
   */
  private final void setKeySequence(final KeySequence keySequence) {
    textTriggerSequenceManager.setKeySequence(keySequence);
  }

  /**
   * Changes the selection in the command combo box.
   *
   * @param command
   *            The fully-parameterized command to select; may be
   *            <code>null</code>.
   */
  private final void setParameterizedCommand(
      final ParameterizedCommand command) {
    int i = 0;
    if (commands != null) {
      final int commandCount = commands.length;
      for (; i < commandCount; i++) {
        if (commands[i].equals(command)) {
          if ((comboCommand.getSelectionIndex() != i)
              && (i < comboCommand.getItemCount())) {
            comboCommand.select(i);
          }
          break;
        }
      }
      if ((i >= comboCommand.getItemCount())
          && (comboCommand.getSelectionIndex() != 0)) {
        comboCommand.select(0);
      }
    }
  }

  /**
   * Sets the currently selected scheme
   *
   * @param scheme
   *            The scheme to select; may be <code>null</code>.
   */
  private final void setScheme(final Scheme scheme) {
    comboScheme.clearSelection();
    comboScheme.deselectAll();
    final String schemeUniqueName = (String) schemeUniqueNamesById
        .get(scheme.getId());

    if (schemeUniqueName != null) {
      final String items[] = comboScheme.getItems();

      for (int i = 0; i < items.length; i++) {
        if (schemeUniqueName.equals(items[i])) {
          comboScheme.select(i);
          break;
        }
      }
    }
  }

  /**
   * Builds the internal look-up tables before allowing the page to become
   * visible.
   */
  public final void setVisible(final boolean visible) {
    if (visible == true) {
      Map contextsByName = new HashMap();

      for (Iterator iterator = contextService.getDefinedContextIds()
          .iterator(); iterator.hasNext();) {
        Context context = contextService.getContext((String) iterator
            .next());
        try {
          String name = context.getName();
          Collection contexts = (Collection) contextsByName.get(name);

          if (contexts == null) {
            contexts = new HashSet();
            contextsByName.put(name, contexts);
          }

          contexts.add(context);
        } catch (final NotDefinedException e) {
          // Do nothing.
        }
      }
     
      Map commandsByName = new HashMap();

      for (Iterator iterator = commandService.getDefinedCommandIds()
          .iterator(); iterator.hasNext();) {
        Command command = commandService.getCommand((String) iterator
            .next());
        if (!isActive(command)) {
          continue;
        }

        try {
          String name = command.getName();
          Collection commands = (Collection) commandsByName.get(name);

          if (commands == null) {
            commands = new HashSet();
            commandsByName.put(name, commands);
          }

          commands.add(command);
        } catch (NotDefinedException eNotDefined) {
          // Do nothing
        }
      }
     
      // moved here to allow us to remove any empty categories
      commandIdsByCategoryId = new HashMap();

      for (Iterator iterator = commandService.getDefinedCommandIds()
          .iterator(); iterator.hasNext();) {
        final Command command = commandService
            .getCommand((String) iterator.next());
        if (!isActive(command)) {
          continue;
        }

        try {
          String categoryId = command.getCategory().getId();
          Collection commandIds = (Collection) commandIdsByCategoryId
              .get(categoryId);

          if (commandIds == null) {
            commandIds = new HashSet();
            commandIdsByCategoryId.put(categoryId, commandIds);
          }

          commandIds.add(command.getId());
        } catch (NotDefinedException eNotDefined) {
          // Do nothing
        }
      }

      Map categoriesByName = new HashMap();

      for (Iterator iterator = commandService.getDefinedCategoryIds()
          .iterator(); iterator.hasNext();) {
        Category category = commandService
            .getCategory((String) iterator.next());

        try {
          if (commandIdsByCategoryId.containsKey(category.getId())) {
            String name = category.getName();
            Collection categories = (Collection) categoriesByName
                .get(name);

            if (categories == null) {
              categories = new HashSet();
              categoriesByName.put(name, categories);
            }

            categories.add(category);
          }
        } catch (NotDefinedException eNotDefined) {
          // Do nothing
        }
      }

      Map schemesByName = new HashMap();

      final Scheme[] definedSchemes = bindingService.getDefinedSchemes();
      for (int i = 0; i < definedSchemes.length; i++) {
        final Scheme scheme = definedSchemes[i];
        try {
          String name = scheme.getName();
          Collection schemes = (Collection) schemesByName.get(name);

          if (schemes == null) {
            schemes = new HashSet();
            schemesByName.put(name, schemes);
          }

          schemes.add(scheme);
        } catch (final NotDefinedException e) {
          // Do nothing.
        }
      }

      contextIdsByUniqueName = new HashMap();
      contextUniqueNamesById = new HashMap();

      for (Iterator iterator = contextsByName.entrySet().iterator(); iterator
          .hasNext();) {
        Map.Entry entry = (Map.Entry) iterator.next();
        String name = (String) entry.getKey();
        Set contexts = (Set) entry.getValue();
        Iterator iterator2 = contexts.iterator();

        if (contexts.size() == 1) {
          Context context = (Context) iterator2.next();
          contextIdsByUniqueName.put(name, context.getId());
          contextUniqueNamesById.put(context.getId(), name);
        } else {
          while (iterator2.hasNext()) {
            Context context = (Context) iterator2.next();
            String uniqueName = MessageFormat.format(
                Util.translateString(RESOURCE_BUNDLE,
                    "uniqueName"), new Object[] { name, //$NON-NLS-1$
                    context.getId() });
            contextIdsByUniqueName.put(uniqueName, context.getId());
            contextUniqueNamesById.put(context.getId(), uniqueName);
          }
        }
      }

      categoryIdsByUniqueName = new HashMap();
      categoryUniqueNamesById = new HashMap();

      for (Iterator iterator = categoriesByName.entrySet().iterator(); iterator
          .hasNext();) {
        Map.Entry entry = (Map.Entry) iterator.next();
        String name = (String) entry.getKey();
        Set categories = (Set) entry.getValue();
        Iterator iterator2 = categories.iterator();

        if (categories.size() == 1) {
          Category category = (Category) iterator2.next();
          categoryIdsByUniqueName.put(name, category.getId());
          categoryUniqueNamesById.put(category.getId(), name);
        } else {
          while (iterator2.hasNext()) {
            Category category = (Category) iterator2.next();
            String uniqueName = MessageFormat.format(
                Util.translateString(RESOURCE_BUNDLE,
                    "uniqueName"), new Object[] { name, //$NON-NLS-1$
                    category.getId() });
            categoryIdsByUniqueName.put(uniqueName, category
                .getId());
            categoryUniqueNamesById.put(category.getId(),
                uniqueName);
          }
        }
      }

      schemeIdsByUniqueName = new HashMap();
      schemeUniqueNamesById = new HashMap();

      for (Iterator iterator = schemesByName.entrySet().iterator(); iterator
          .hasNext();) {
        Map.Entry entry = (Map.Entry) iterator.next();
        String name = (String) entry.getKey();
        Set keyConfigurations = (Set) entry.getValue();
        Iterator iterator2 = keyConfigurations.iterator();

        if (keyConfigurations.size() == 1) {
          Scheme scheme = (Scheme) iterator2.next();
          schemeIdsByUniqueName.put(name, scheme.getId());
          schemeUniqueNamesById.put(scheme.getId(), name);
        } else {
          while (iterator2.hasNext()) {
            Scheme scheme = (Scheme) iterator2.next();
            String uniqueName = MessageFormat.format(
                Util.translateString(RESOURCE_BUNDLE,
                    "uniqueName"), new Object[] { name, //$NON-NLS-1$
                    scheme.getId() });
            schemeIdsByUniqueName.put(uniqueName, scheme.getId());
            schemeUniqueNamesById.put(scheme.getId(), uniqueName);
          }
        }
      }

      Scheme activeScheme = bindingService.getActiveScheme();

      // Make an internal copy of the binding manager, for local changes.
      try {
        for (int i = 0; i < definedSchemes.length; i++) {
          final Scheme scheme = definedSchemes[i];
          final Scheme copy = localChangeManager.getScheme(scheme
              .getId());
          copy.define(scheme.getName(), scheme.getDescription(),
              scheme.getParentId());
        }
        localChangeManager.setActiveScheme(bindingService
            .getActiveScheme());
      } catch (final NotDefinedException e) {
        throw new Error(
            "There is a programmer error in the keys preference page"); //$NON-NLS-1$
      }
      localChangeManager.setLocale(bindingService.getLocale());
      localChangeManager.setPlatform(bindingService.getPlatform());
      localChangeManager.setBindings(bindingService.getBindings());

      // Populate the category combo box.
      List categoryNames = new ArrayList(categoryIdsByUniqueName.keySet());
      Collections.sort(categoryNames, Collator.getInstance());
      if (commandIdsByCategoryId.containsKey(null)) {
        categoryNames.add(0, Util.translateString(RESOURCE_BUNDLE,
            "other")); //$NON-NLS-1$
      }
      comboCategory.setItems((String[]) categoryNames
          .toArray(new String[categoryNames.size()]));
      comboCategory.clearSelection();
      comboCategory.deselectAll();
      if (commandIdsByCategoryId.containsKey(null)
          || !categoryNames.isEmpty()) {
        comboCategory.select(0);
      }

      // Populate the scheme combo box.
      List schemeNames = new ArrayList(schemeIdsByUniqueName.keySet());
      Collections.sort(schemeNames, Collator.getInstance());
      comboScheme.setItems((String[]) schemeNames
          .toArray(new String[schemeNames.size()]));
      setScheme(activeScheme);

      // Update the entire page.
      update(true);
    }

    super.setVisible(visible);
  }

  /**
   * Updates the entire preference page -- except the view tab -- based on
   * current selection sate. This preference page is written so that
   * everything can be made consistent simply by inspecting the state of its
   * widgets. A change is triggered by the user, and an event is fired. The
   * event triggers an update. It is possible for extra work to be done by
   * this page before calling update.
   */
  private final void update() {
    update(false);
  }

  /**
   * Updates the entire preference page based on current changes. This
   * preference page is written so that everything can be made consistent
   * simply by inspecting the state of its widgets. A change is triggered by
   * the user, and an event is fired. The event triggers an update. It is
   * possible for extra work to be done by this page before calling update.
   *
   * @param updateViewTab
   *            Whether the view tab should be updated as well.
   */
  private final void update(final boolean updateViewTab) {
    if (updateViewTab) {
      updateViewTab();
    }
    updateComboCommand();
    updateComboContext();
    final TriggerSequence triggerSequence = getKeySequence();
    updateTableBindingsForTriggerSequence(triggerSequence);
    final ParameterizedCommand command = getParameterizedCommand();
    updateTableBindingsForCommand(command);
    final String contextId = getContextId();
    updateSelection(tableBindingsForTriggerSequence, contextId,
        triggerSequence);
    updateSelection(tableBindingsForCommand, contextId, triggerSequence);
    updateLabelSchemeExtends();
    updateLabelContextExtends();
    updateEnabled(triggerSequence, command);
  }

  /**
   * Updates the contents of the commands combo box, based on the current
   * selection in the category combo box.
   */
  private final void updateComboCommand() {
    // Remember the current selection, so we can restore it later.
    final ParameterizedCommand command = getParameterizedCommand();

    // Figure out where command identifiers apply to the selected category.
    final String categoryId = getCategoryId();
    Set commandIds = (Set) commandIdsByCategoryId.get(categoryId);
    if (commandIds==null) {
      commandIds = Collections.EMPTY_SET;
    }

    /*
     * Generate an array of parameterized commands based on these
     * identifiers. The parameterized commands will be sorted based on their
     * names.
     */
    List commands = new ArrayList();
    final Iterator commandIdItr = commandIds.iterator();
    while (commandIdItr.hasNext()) {
      final String currentCommandId = (String) commandIdItr.next();
      final Command currentCommand = commandService
          .getCommand(currentCommandId);
      try {
        commands.addAll(ParameterizedCommand
            .generateCombinations(currentCommand));
      } catch (final NotDefinedException e) {
        // It is safe to just ignore undefined commands.
      }
    }
   
    // sort the commands with a collator, so they appear in the
    // combo correctly
    commands = sortParameterizedCommands(commands);
   
    final int commandCount = commands.size();
    this.commands = (ParameterizedCommand[]) commands
        .toArray(new ParameterizedCommand[commandCount]);

    /*
     * Generate an array of command names based on this array of
     * parameterized commands.
     */
    final String[] commandNames = new String[commandCount];
    for (int i = 0; i < commandCount; i++) {
      try {
        commandNames[i] = this.commands[i].getName();
      } catch (final NotDefinedException e) {
        throw new Error(
            "Concurrent modification of the command's defined state"); //$NON-NLS-1$
      }
    }

    /*
     * Copy the command names into the combo box, but only if they've
     * changed. We do this to try to avoid unnecessary calls out to the
     * operating system, as well as to defend against bugs in SWT's event
     * mechanism.
     */
    final String[] currentItems = comboCommand.getItems();
    if (!Arrays.equals(currentItems, commandNames)) {
      comboCommand.setItems(commandNames);
    }

    // Try to restore the selection.
    setParameterizedCommand(command);

    /*
     * Just to be extra careful, make sure that we have a selection at this
     * point. This line could probably be removed, but it makes the code a
     * bit more robust.
     */
    if ((comboCommand.getSelectionIndex() == -1) && (commandCount > 0)) {
      comboCommand.select(0);
    }
  }
 
  /**
   * Sort the commands using the correct language.
   * @param commands the List of ParameterizedCommands
   * @return The sorted List
   */
  private List sortParameterizedCommands(List commands) {
    final Collator collator = Collator.getInstance();
   
    // this comparator is based on the ParameterizedCommands#compareTo(*)
    // method, but uses the collator.
    Comparator comparator = new Comparator() {
      public int compare(Object o1, Object o2) {
        String name1 = null;
        String name2 = null;
        try {
          name1 = ((ParameterizedCommand) o1).getName();
        } catch (NotDefinedException e) {
          return -1;
        }
        try {
          name2 = ((ParameterizedCommand) o2).getName();
        } catch (NotDefinedException e) {
          return 1;
        }
        int rc = collator.compare(name1, name2);
        if (rc != 0) {
          return rc;
        }

        String id1 = ((ParameterizedCommand) o1).getId();
        String id2 = ((ParameterizedCommand) o2).getId();
        return collator.compare(id1, id2);
      }
    };
    Collections.sort(commands, comparator);
    return commands;
  }

  /**
   * Updates the contents of the context combo box, as well as its selection.
   */
  private final void updateComboContext() {
    final String contextId = getContextId();
    final Map contextIdsByName = new HashMap(contextIdsByUniqueName);

    final List contextNames = new ArrayList(contextIdsByName.keySet());
    Collections.sort(contextNames, Collator.getInstance());

    comboContext.setItems((String[]) contextNames
        .toArray(new String[contextNames.size()]));
    setContextId(contextId);

    if (comboContext.getSelectionIndex() == -1 && !contextNames.isEmpty()) {
      comboContext.select(0);
    }
  }

  /**
   * Updates the enabled state of the various widgets on this page. The
   * decision is based on the current trigger sequence and the currently
   * selected command.
   *
   * @param triggerSequence
   *            The current trigger sequence; may be empty, but never
   *            <code>null</code>.
   * @param command
   *            The currently selected command, if any; <code>null</code>
   *            otherwise.
   */
  private final void updateEnabled(final TriggerSequence triggerSequence,
      final ParameterizedCommand command) {
    final boolean commandSelected = command != null;
    labelBindingsForCommand.setEnabled(commandSelected);
    tableBindingsForCommand.setEnabled(commandSelected);

    final boolean triggerSequenceSelected = !triggerSequence.isEmpty();
    labelBindingsForTriggerSequence.setEnabled(triggerSequenceSelected);
    tableBindingsForTriggerSequence.setEnabled(triggerSequenceSelected);

    /*
     * TODO Do some better button enablement.
     */
    final boolean buttonsEnabled = commandSelected
        && triggerSequenceSelected;
    buttonAdd.setEnabled(buttonsEnabled);
    buttonRemove.setEnabled(buttonsEnabled);
    buttonRestore.setEnabled(buttonsEnabled);
  }

  /**
   * Updates the label next to the context that says "extends" if the context
   * is a child of another context. If the context is not a child of another
   * context, then the label is simply blank.
   */
  private final void updateLabelContextExtends() {
    final String contextId = getContextId();

    if (contextId != null) {
      final Context context = contextService.getContext(getContextId());
      if (context.isDefined()) {
        try {
          final String parentId = context.getParentId();
          if (parentId != null) {
            final String name = (String) contextUniqueNamesById
                .get(parentId);
            if (name != null) {
              labelContextExtends.setText(MessageFormat.format(
                  Util.translateString(RESOURCE_BUNDLE,
                      "extends"), //$NON-NLS-1$
                  new Object[] { name }));
              return;
            }
          }
        } catch (final NotDefinedException e) {
          // Do nothing
        }
      }
    }

    labelContextExtends.setText(Util.ZERO_LENGTH_STRING);
  }

  /**
   * Updates the label next to the scheme that says "extends" if the scheme is
   * a child of another scheme. If the scheme is not a child of another
   * scheme, then the label is simply blank.
   */
  private final void updateLabelSchemeExtends() {
    final String schemeId = getSchemeId();

    if (schemeId != null) {
      final Scheme scheme = bindingService.getScheme(schemeId);
      try {
        final String name = (String) schemeUniqueNamesById.get(scheme
            .getParentId());
        if (name != null) {
          labelSchemeExtends.setText(MessageFormat.format(Util
              .translateString(RESOURCE_BUNDLE, "extends"), //$NON-NLS-1$
              new Object[] { name }));
          return;
        }
      } catch (final NotDefinedException e) {
        // Do nothing
      }
    }

    labelSchemeExtends.setText(Util.ZERO_LENGTH_STRING);
  }

  /**
   * Tries to select the correct entry in table based on the currently
   * selected context and trigger sequence. If the table hasn't really
   * changed, then this method is essentially trying to restore the selection.
   * If it has changed, then it is trying to select the most entry based on
   * the context.
   *
   * @param table
   *            The table to be changed; must not be <code>null</code>.
   * @param contextId
   *            The currently selected context; should not be
   *            <code>null</code>.
   * @param triggerSequence
   *            The current trigger sequence; should not be <code>null</code>.
   */
  private final void updateSelection(final Table table,
      final String contextId, final TriggerSequence triggerSequence) {
    if (table.getSelectionCount() > 1) {
      table.deselectAll();
    }

    final TableItem[] items = table.getItems();
    int selection = -1;
    for (int i = 0; i < items.length; i++) {
      final Binding binding = (Binding) items[i].getData(ITEM_DATA_KEY);
      if ((Util.equals(contextId, binding.getContextId()))
          && (Util.equals(triggerSequence, binding
              .getTriggerSequence()))) {
        selection = i;
        break;
      }
    }

    if (selection != -1) {
      table.select(selection);
    }
  }

  /**
   * Updates the contents of the table showing the bindings for the currently
   * selected command. The selection is destroyed by this process.
   *
   * @param parameterizedCommand
   *            The currently selected fully-parameterized command; may be
   *            <code>null</code>.
   */
  private final void updateTableBindingsForCommand(
      final ParameterizedCommand parameterizedCommand) {
    // Clear the table of existing items.
    tableBindingsForCommand.removeAll();

    // Add each of the bindings, if the command identifier matches.
    final Collection bindings = localChangeManager
        .getActiveBindingsDisregardingContextFlat();
    final Iterator bindingItr = bindings.iterator();
    while (bindingItr.hasNext()) {
      final Binding binding = (Binding) bindingItr.next();
      if (!Util.equals(parameterizedCommand, binding
          .getParameterizedCommand())) {
        continue; // binding does not match
      }

      final TableItem tableItem = new TableItem(tableBindingsForCommand,
          SWT.NULL);
      tableItem.setData(ITEM_DATA_KEY, binding);

      /*
       * Set the associated image based on the type of binding. Either it
       * is a user binding or a system binding.
       *
       * TODO Identify more image types.
       */
      if (binding.getType() == Binding.SYSTEM) {
        tableItem.setImage(0, IMAGE_BLANK);
      } else {
        tableItem.setImage(0, IMAGE_CHANGE);
      }

      String contextName = (String) contextUniqueNamesById.get(binding
          .getContextId());
      if (contextName == null) {
        contextName = Util.ZERO_LENGTH_STRING;
      }
      tableItem.setText(1, contextName);
      tableItem.setText(2, binding.getTriggerSequence().format());
    }
  }

  /**
   * Updates the contents of the table showing the bindings for the current
   * trigger sequence. The selection is destroyed by this process.
   *
   * @param triggerSequence
   *            The current trigger sequence; may be <code>null</code> or
   *            empty.
   */
  private final void updateTableBindingsForTriggerSequence(
      final TriggerSequence triggerSequence) {
    // Clear the table of its existing items.
    tableBindingsForTriggerSequence.removeAll();

    // Get the collection of bindings for the current command.
    final Map activeBindings = localChangeManager
        .getActiveBindingsDisregardingContext();
    final Collection bindings = (Collection) activeBindings
        .get(triggerSequence);
    if (bindings == null) {
      return;
    }

    // Add each of the bindings.
    final Iterator bindingItr = bindings.iterator();
    while (bindingItr.hasNext()) {
      final Binding binding = (Binding) bindingItr.next();
      final Context context = contextService.getContext(binding
          .getContextId());
      final ParameterizedCommand parameterizedCommand = binding
          .getParameterizedCommand();
      final Command command = parameterizedCommand.getCommand();
      if ((!context.isDefined()) && (!command.isDefined())) {
        continue;
      }

      final TableItem tableItem = new TableItem(
          tableBindingsForTriggerSequence, SWT.NULL);
      tableItem.setData(ITEM_DATA_KEY, binding);

      /*
       * Set the associated image based on the type of binding. Either it
       * is a user binding or a system binding.
       *
       * TODO Identify more image types.
       */
      if (binding.getType() == Binding.SYSTEM) {
        tableItem.setImage(0, IMAGE_BLANK);
      } else {
        tableItem.setImage(0, IMAGE_CHANGE);
      }

      try {
        tableItem.setText(1, context.getName());
        tableItem.setText(2, parameterizedCommand.getName());
      } catch (final NotDefinedException e) {
        throw new Error(
            "Context or command became undefined on a non-UI thread while the UI thread was processing."); //$NON-NLS-1$
      }
    }
  }

  /**
   * Updates the contents of the view tab. This queries the command manager
   * for a list of key sequence binding definitions, and these definitions are
   * then added to the table.
   *
   * @since 3.1
   */
  private final void updateViewTab() {
    // Clear out the existing table contents.
    tableBindings.removeAll();

    // Get a sorted list of key binding contents.
    final List bindings = new ArrayList(localChangeManager
        .getActiveBindingsDisregardingContextFlat());
    Collections.sort(bindings, new Comparator() {
      /**
       * Compares two instances of <code>Binding</code> based on the
       * current sort order.
       *
       * @param object1
       *            The first object to compare; must be an instance of
       *            <code>Binding</code> (i.e., not <code>null</code>).
       * @param object2
       *            The second object to compare; must be an instance of
       *            <code>Binding</code> (i.e., not <code>null</code>).
       * @return The integer value representing the comparison. The
       *         comparison is based on the current sort order.
       * @since 3.1
       */
      public final int compare(final Object object1, final Object object2) {
        final Binding binding1 = (Binding) object1;
        final Binding binding2 = (Binding) object2;

        /*
         * Get the category name, command name, formatted key sequence
         * and context name for the first binding.
         */
        final Command command1 = binding1.getParameterizedCommand()
            .getCommand();
        String categoryName1 = Util.ZERO_LENGTH_STRING;
        String commandName1 = Util.ZERO_LENGTH_STRING;
        try {
          commandName1 = command1.getName();
          categoryName1 = command1.getCategory().getName();
        } catch (final NotDefinedException e) {
          // Just use the zero-length string.
        }
        final String triggerSequence1 = binding1.getTriggerSequence()
            .format();
        final String contextId1 = binding1.getContextId();
        String contextName1 = Util.ZERO_LENGTH_STRING;
        if (contextId1 != null) {
          final Context context = contextService
              .getContext(contextId1);
          try {
            contextName1 = context.getName();
          } catch (final org.eclipse.core.commands.common.NotDefinedException e) {
            // Just use the zero-length string.
          }
        }

        /*
         * Get the category name, command name, formatted key sequence
         * and context name for the first binding.
         */
        final Command command2 = binding2.getParameterizedCommand()
            .getCommand();
        String categoryName2 = Util.ZERO_LENGTH_STRING;
        String commandName2 = Util.ZERO_LENGTH_STRING;
        try {
          commandName2 = command2.getName();
          categoryName2 = command2.getCategory().getName();
        } catch (final org.eclipse.core.commands.common.NotDefinedException e) {
          // Just use the zero-length string.
        }
        final String keySequence2 = binding2.getTriggerSequence()
            .format();
        final String contextId2 = binding2.getContextId();
        String contextName2 = Util.ZERO_LENGTH_STRING;
        if (contextId2 != null) {
          final Context context = contextService
              .getContext(contextId2);
          try {
            contextName2 = context.getName();
          } catch (final org.eclipse.core.commands.common.NotDefinedException e) {
            // Just use the zero-length string.
          }
        }

        // Compare the items in the current sort order.
        int compare = 0;
        for (int i = 0; i < sortOrder.length; i++) {
          switch (sortOrder[i]) {
          case VIEW_CATEGORY_COLUMN_INDEX:
            compare = Util.compare(categoryName1, categoryName2);
            if (compare != 0) {
              return compare;
            }
            break;
          case VIEW_COMMAND_COLUMN_INDEX:
            compare = Util.compare(commandName1, commandName2);
            if (compare != 0) {
              return compare;
            }
            break;
          case VIEW_KEY_SEQUENCE_COLUMN_INDEX:
            compare = Util.compare(triggerSequence1, keySequence2);
            if (compare != 0) {
              return compare;
            }
            break;
          case VIEW_CONTEXT_COLUMN_INDEX:
            compare = Util.compare(contextName1, contextName2);
            if (compare != 0) {
              return compare;
            }
            break;
          default:
            throw new Error(
                "Programmer error: added another sort column without modifying the comparator."); //$NON-NLS-1$
          }
        }

        return compare;
      }

      /**
       * @see Object#equals(java.lang.Object)
       */
      public final boolean equals(final Object object) {
        return super.equals(object);
      }
    });

    // Add a table item for each item in the list.
    final Iterator keyBindingItr = bindings.iterator();
    while (keyBindingItr.hasNext()) {
      final Binding binding = (Binding) keyBindingItr.next();

      // Get the command and category name.
      final ParameterizedCommand command = binding
          .getParameterizedCommand();
      String commandName = Util.ZERO_LENGTH_STRING;
      String categoryName = Util.ZERO_LENGTH_STRING;
      try {
        commandName = command.getName();
        categoryName = command.getCommand().getCategory().getName();
      } catch (final org.eclipse.core.commands.common.NotDefinedException e) {
        // Just use the zero-length string.
      }

      // Ignore items with a meaningless command name.
      if ((commandName == null) || (commandName.length() == 0)) {
        continue;
      }

      // Get the context name.
      final String contextId = binding.getContextId();
      String contextName = Util.ZERO_LENGTH_STRING;
      if (contextId != null) {
        final Context context = contextService.getContext(contextId);
        try {
          contextName = context.getName();
        } catch (final org.eclipse.core.commands.common.NotDefinedException e) {
          // Just use the zero-length string.
        }
      }

      // Create the table item.
      final TableItem item = new TableItem(tableBindings, SWT.NONE);
      item.setText(VIEW_CATEGORY_COLUMN_INDEX, categoryName);
      item.setText(VIEW_COMMAND_COLUMN_INDEX, commandName);
      item.setText(VIEW_KEY_SEQUENCE_COLUMN_INDEX, binding
          .getTriggerSequence().format());
      item.setText(VIEW_CONTEXT_COLUMN_INDEX, contextName);
      item.setData(BINDING_KEY, binding);
    }

    // Pack the columns.
    for (int i = 0; i < tableBindings.getColumnCount(); i++) {
      tableBindings.getColumn(i).pack();
    }
  }
 
 
}
TOP

Related Classes of org.eclipse.ui.internal.keys.KeysPreferencePage$SortOrderSelectionListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.