Package org.eclipse.ui.internal.keys

Source Code of org.eclipse.ui.internal.keys.NewKeysPreferencePage$BindingComparator

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

package org.eclipse.ui.internal.keys;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
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.NamedHandleObject;
import org.eclipse.core.commands.common.NamedHandleObjectComparator;
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.commands.util.Tracing;
import org.eclipse.core.databinding.observable.Diffs;
import org.eclipse.core.databinding.observable.IStaleListener;
import org.eclipse.core.databinding.observable.StaleEvent;
import org.eclipse.core.databinding.observable.set.IObservableSet;
import org.eclipse.core.databinding.observable.set.ISetChangeListener;
import org.eclipse.core.databinding.observable.set.ObservableSet;
import org.eclipse.core.databinding.observable.set.SetChangeEvent;
import org.eclipse.core.databinding.observable.set.UnionSet;
import org.eclipse.core.databinding.observable.set.WritableSet;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.IValueChangeListener;
import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
import org.eclipse.core.runtime.IStatus;
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.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.databinding.swt.SWTObservables;
import org.eclipse.jface.databinding.viewers.ViewersObservables;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.internal.databinding.provisional.swt.ControlUpdater;
import org.eclipse.jface.preference.PreferencePage;
import org.eclipse.jface.resource.DeviceResourceException;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.AbstractListViewer;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.NamedHandleObjectLabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
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.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.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.dialogs.FilteredTree;
import org.eclipse.ui.internal.IWorkbenchHelpContextIds;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.internal.commands.ICommandImageService;
import org.eclipse.ui.internal.misc.Policy;
import org.eclipse.ui.internal.misc.StatusUtil;
import org.eclipse.ui.internal.util.BundleUtility;
import org.eclipse.ui.internal.util.Util;
import org.eclipse.ui.keys.IBindingService;
import org.eclipse.ui.statushandlers.StatusManager;

/**
* <p>
* A preference page that is capable of displaying and editing the bindings
* between commands and user input events. These are typically things like
* keyboard shortcuts.
* </p>
* <p>
* This preference page has four general types of methods. Create methods are
* called when the page is first made visible. They are responsible for creating
* all of the widgets, and laying them out within the preference page. Fill
* methods populate the contents of the widgets that contain collections of data
* from which items can be selected. The select methods respond to selection
* events from the user, such as a button press or a table selection. The update
* methods update the contents of various widgets based on the current state of
* the user interface. For example, the command name label will always try to
* match the current select in the binding table.
* </p>
*
* @since 3.2
*/
public final class NewKeysPreferencePage extends PreferencePage implements
    IWorkbenchPreferencePage {

  private static boolean DEBUG = Policy.DEBUG_KEY_BINDINGS;

  private static final String TRACING_COMPONENT = "NewKeysPref"; //$NON-NLS-1$

  /**
   * @since 3.3
   *
   */
  private final class ResortColumn extends SelectionAdapter {
    private final BindingComparator comparator;
    private final TreeColumn treeColumn;
    private final Tree tree;
    private final int column;

    /**
     * @param comparator
     * @param commandNameColumn
     * @param tree
     */
    private ResortColumn(BindingComparator comparator,
        TreeColumn treeColumn, Tree tree, int column) {
      this.comparator = comparator;
      this.treeColumn = treeColumn;
      this.tree = tree;
      this.column = column;
    }

    public void widgetSelected(SelectionEvent e) {
      if (comparator.getSortColumn() == column) {
        comparator.setAscending(!comparator.isAscending());
      }
      tree.setSortColumn(treeColumn);
      comparator.setSortColumn(column);
      tree.setSortDirection(comparator.isAscending() ? SWT.UP : SWT.DOWN);
      try {
        filteredTree.getViewer().getTree().setRedraw(false);
        filteredTree.getViewer().refresh();
      } finally {
        filteredTree.getViewer().getTree().setRedraw(true);
      }
    }
  }

  private class ObservableSetContentProvider implements ITreeContentProvider {

    private class KnownElementsSet extends ObservableSet {

      KnownElementsSet(Set wrappedSet) {
        super(SWTObservables.getRealm(Display.getDefault()),
            wrappedSet, Object.class);
      }

      void doFireDiff(Set added, Set removed) {
        fireSetChange(Diffs.createSetDiff(added, removed));
      }

      void doFireStale(boolean isStale) {
        if (isStale) {
          fireStale();
        } else {
          fireChange();
        }
      }
    }

    private IObservableSet readableSet;

    private Viewer viewer;

    /**
     * This readableSet returns the same elements as the input readableSet.
     * However, it only fires events AFTER the elements have been added or
     * removed from the viewer.
     */
    private KnownElementsSet knownElements;

    private ISetChangeListener listener = new ISetChangeListener() {

      public void handleSetChange(SetChangeEvent event) {
        boolean wasStale = knownElements.isStale();
        if (isDisposed()) {
          return;
        }
        doDiff(event.diff.getAdditions(), event.diff.getRemovals(),
            true);
        if (!wasStale && event.getObservableSet().isStale()) {
          knownElements.doFireStale(true);
        }
      }
    };

    private IStaleListener staleListener = new IStaleListener() {
      public void handleStale(StaleEvent event) {
        knownElements.doFireStale(event.getObservable().isStale());
      }
    };

    /**
     *
     */
    public ObservableSetContentProvider() {
      readableSet = new ObservableSet(SWTObservables.getRealm(Display
          .getDefault()), Collections.EMPTY_SET, Object.class) {
      };
      knownElements = new KnownElementsSet(readableSet);
    }

    public void dispose() {
      setInput(null);
    }

    private void doDiff(Set added, Set removed, boolean updateViewer) {
      knownElements.doFireDiff(added, Collections.EMPTY_SET);

      if (updateViewer) {
        if (added.size() > 20 || removed.size() > 20) {
          viewer.refresh();
        } else {
          Object[] toAdd = added.toArray();
          if (viewer instanceof TreeViewer) {
            TreeViewer tv = (TreeViewer) viewer;
            tv.add(model, toAdd);
          } else if (viewer instanceof AbstractListViewer) {
            AbstractListViewer lv = (AbstractListViewer) viewer;
            lv.add(toAdd);
          }
          Object[] toRemove = removed.toArray();
          if (viewer instanceof TreeViewer) {
            TreeViewer tv = (TreeViewer) viewer;
            tv.remove(toRemove);
          } else if (viewer instanceof AbstractListViewer) {
            AbstractListViewer lv = (AbstractListViewer) viewer;
            lv.remove(toRemove);
          }
        }
      }
      knownElements.doFireDiff(Collections.EMPTY_SET, removed);
    }

    public Object[] getElements(Object inputElement) {
      return readableSet.toArray();
    }

    /**
     * Returns the readableSet of elements known to this content provider.
     * Items are added to this readableSet before being added to the viewer,
     * and they are removed after being removed from the viewer. The
     * readableSet is always updated after the viewer. This is intended for
     * use by label providers, as it will always return the items that need
     * labels.
     *
     * @return readableSet of items that will need labels
     */
    public IObservableSet getKnownElements() {
      return knownElements;
    }

    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
      this.viewer = viewer;

      if (newInput != null && !(newInput instanceof IObservableSet)) {
        throw new IllegalArgumentException(
            "This content provider only works with input of type IReadableSet"); //$NON-NLS-1$
      }

      setInput((IObservableSet) newInput);
    }

    private boolean isDisposed() {
      return viewer.getControl() == null
          || viewer.getControl().isDisposed();
    }

    private void setInput(IObservableSet newSet) {
      boolean updateViewer = true;
      if (newSet == null) {
        newSet = new ObservableSet(SWTObservables.getRealm(Display
            .getDefault()), Collections.EMPTY_SET, Object.class) {
        };
        // don't update the viewer - its input is null
        updateViewer = false;
      }

      boolean wasStale = false;
      if (readableSet != null) {
        wasStale = readableSet.isStale();
        readableSet.removeSetChangeListener(listener);
        readableSet.removeStaleListener(staleListener);
      }

      HashSet additions = new HashSet();
      HashSet removals = new HashSet();

      additions.addAll(newSet);
      additions.removeAll(readableSet);

      removals.addAll(readableSet);
      removals.removeAll(newSet);

      readableSet = newSet;

      doDiff(additions, removals, updateViewer);

      if (readableSet != null) {
        readableSet.addSetChangeListener(listener);
        readableSet.addStaleListener(staleListener);
      }

      boolean isStale = (readableSet != null && readableSet.isStale());
      if (isStale != wasStale) {
        knownElements.doFireStale(isStale);
      }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
     */
    public Object[] getChildren(Object parentElement) {
      // TODO Auto-generated method stub
      return null;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.viewers.ITreeContentProvider#getParent(java.lang.Object)
     */
    public Object getParent(Object element) {
      // TODO Auto-generated method stub
      return null;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object)
     */
    public boolean hasChildren(Object element) {
      // TODO Auto-generated method stub
      return false;
    }

  }

  /**
   * A FilteredTree that provides a combo which is used to organize and
   * display elements in the tree according to the selected criteria.
   *
   */
  protected class CategoryFilterTree extends FilteredTree {

    private CategoryPatternFilter filter;

    /**
     * Constructor for PatternFilteredTree.
     *
     * @param parent
     * @param treeStyle
     * @param filter
     */
    protected CategoryFilterTree(Composite parent, int treeStyle,
        CategoryPatternFilter filter) {
      super(parent, treeStyle, filter);
      this.filter = filter;
    }

    public void filterCategories(boolean b) {
      filter.filterCategories(b);
      textChanged();
    }

    public boolean isFilteringCategories() {
      return filter.isFilteringCategories();
    }
  }

  /**
   * A label provider that simply extracts the command name and the formatted
   * trigger sequence from a given binding, and matches them to the correct
   * column.
   */
  private final class BindingLabelProvider extends LabelProvider implements
      ITableLabelProvider {

    /**
     * The index of the column containing the command name.
     */
    private static final int COLUMN_COMMAND = 0;

    /**
     * The index of the column containing the trigger sequence.
     */
    private static final int COLUMN_TRIGGER_SEQUENCE = 1;

    /**
     * The index of the column containing the trigger sequence.
     */
    private static final int COLUMN_WHEN = 2;

    /**
     * The index of the column containing the Category.
     */
    private static final int COLUMN_CATEGORY = 3;

    /**
     * The index of the column with the image for User binding
     */
    private static final int COLUMN_USER = 4;

    /**
     * A resource manager for this preference page.
     */
    private final LocalResourceManager localResourceManager = new LocalResourceManager(
        JFaceResources.getResources());

    public final void dispose() {
      super.dispose();
      localResourceManager.dispose();
    }

    public final Image getColumnImage(final Object element,
        final int columnIndex) {
      final Object value = element;
      if (value instanceof Binding) {
        switch (columnIndex) {
        case COLUMN_COMMAND:
          final ParameterizedCommand parameterizedCommand = ((Binding) value)
              .getParameterizedCommand();
          if (parameterizedCommand != null) {
            final String commandId = parameterizedCommand.getId();
            final ImageDescriptor imageDescriptor = commandImageService
                .getImageDescriptor(commandId);
            if (imageDescriptor == null) {
              return null;
            }
            try {
              return localResourceManager
                  .createImage(imageDescriptor);
            } catch (final DeviceResourceException e) {
              final String message = "Problem retrieving image for a command '" //$NON-NLS-1$
                  + commandId + '\'';
              final IStatus status = new Status(IStatus.ERROR,
                  WorkbenchPlugin.PI_WORKBENCH, 0, message, e);
              WorkbenchPlugin.log(message, status);
            }
          }
          return null;

        case COLUMN_USER:
          if (((Binding) value).getType() == Binding.USER)
            return ImageFactory.getImage("change"); //$NON-NLS-1$
          return ImageFactory.getImage("blank"); //$NON-NLS-1$
        }

      } else if (value instanceof ParameterizedCommand) {
        switch (columnIndex) {
        case COLUMN_COMMAND:
          final ParameterizedCommand parameterizedCommand = (ParameterizedCommand) value;
          final String commandId = parameterizedCommand.getId();
          final ImageDescriptor imageDescriptor = commandImageService
              .getImageDescriptor(commandId);
          if (imageDescriptor == null) {
            return null;
          }
          try {
            return localResourceManager
                .createImage(imageDescriptor);
          } catch (final DeviceResourceException e) {
            final String message = "Problem retrieving image for a command '" //$NON-NLS-1$
                + commandId + '\'';
            final IStatus status = new Status(IStatus.ERROR,
                WorkbenchPlugin.PI_WORKBENCH, 0, message, e);
            WorkbenchPlugin.log(message, status);
          }
          return null;
        case COLUMN_USER:
          return ImageFactory.getImage("blank"); //$NON-NLS-1$
        }

      } else if ((value instanceof Category) || (value instanceof String)) {
        switch (columnIndex) {
        case COLUMN_COMMAND:
          final URL url = BundleUtility.find(PlatformUI.PLUGIN_ID,
              ICON_GROUP_OF_BINDINGS);
          final ImageDescriptor imageDescriptor = ImageDescriptor
              .createFromURL(url);
          try {
            return localResourceManager
                .createImage(imageDescriptor);
          } catch (final DeviceResourceException e) {
            final String message = "Problem retrieving image for groups of bindings: '" //$NON-NLS-1$
                + ICON_GROUP_OF_BINDINGS + '\'';
            final IStatus status = new Status(IStatus.ERROR,
                WorkbenchPlugin.PI_WORKBENCH, 0, message, e);
            WorkbenchPlugin.log(message, status);
          }
        }

      }

      return null;
    }

    private boolean checkConflict(Binding binding) {
      Collection matches = (Collection) localChangeManager
          .getActiveBindingsDisregardingContext().get(
              binding.getTriggerSequence());
      if (matches != null) {
        Iterator i = matches.iterator();
        while (i.hasNext()) {
          Binding b = (Binding) i.next();
          if (binding != b
              && b.getContextId().equals(binding.getContextId())
              && b.getSchemeId().equals(binding.getSchemeId())) {
            return true;
          }
        }
      }
      return false;
    }

    public final String getColumnText(final Object element,
        final int columnIndex) {
      final Object value = element;
      if (value instanceof Binding) {
        final Binding binding = (Binding) value;
        switch (columnIndex) {
        case COLUMN_COMMAND:
          try {
            return binding.getParameterizedCommand().getName();

          } catch (final NotDefinedException e) {
            return NewKeysPreferenceMessages.Undefined_Command;
          }
        case COLUMN_TRIGGER_SEQUENCE:
          if (checkConflict(binding)) {
            return "*" + binding.getTriggerSequence().format(); //$NON-NLS-1$
          }
          return binding.getTriggerSequence().format();

        case COLUMN_WHEN:
          try {
            return contextService
                .getContext(binding.getContextId()).getName();
          } catch (NotDefinedException e1) {
            return NewKeysPreferenceMessages.Undefined_Context;
          }
        case COLUMN_CATEGORY:
          try {
            return binding.getParameterizedCommand().getCommand()
                .getCategory().getName();
          } catch (NotDefinedException e) {
            return NewKeysPreferenceMessages.Unavailable_Category;
          }
        default:
          return null;
        }
      } else if (value instanceof Category) {
        if (columnIndex == COLUMN_COMMAND) {
          try {
            return ((Category) value).getName();
          } catch (final NotDefinedException e) {
            return NewKeysPreferenceMessages.Unavailable_Category;
          }
        }

        return null;

      } else if (value instanceof String) {
        // This is a context.
        if (columnIndex == COLUMN_COMMAND) {
          try {
            return contextService.getContext((String) value)
                .getName();
          } catch (final NotDefinedException e) {
            return NewKeysPreferenceMessages.Undefined_Context;
          }
        }

        return null;
      } else if (value instanceof ParameterizedCommand) {
        if (columnIndex == COLUMN_COMMAND) {
          try {
            return ((ParameterizedCommand) value).getName();
          } catch (final NotDefinedException e) {
            return NewKeysPreferenceMessages.Undefined_Command;
          }
        }
        if (columnIndex == COLUMN_TRIGGER_SEQUENCE)
          return ""; //$NON-NLS-1$

        if (columnIndex == COLUMN_WHEN)
          return ""; //$NON-NLS-1$

        if (columnIndex == COLUMN_CATEGORY) {
          try {
            return ((ParameterizedCommand) value).getCommand()
                .getCategory().getName();
          } catch (NotDefinedException e) {
            return NewKeysPreferenceMessages.Unavailable_Category;
          }
        }
        return null;
      }

      return null;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.eclipse.jface.viewers.LabelProvider#getText(java.lang.Object)
     */
    public String getText(Object element) {
      String rc = getColumnText(element, 0);
      if (rc == null) {
        super.getText(element);
      }
      StringBuffer buf = new StringBuffer(rc);
      for (int i = 1; i < COLUMN_USER; i++) {
        String text = getColumnText(element, i);
        if (text != null) {
          buf.append(' ');
          buf.append(text);
        }
      }
      return buf.toString();
    }
  }

  /**
   * Sorts the bindings in the filtered tree based on the current grouping.
   */
  private final class BindingComparator extends ViewerComparator {

    private int sortColumn = 0;

    private int lastSortColumn = 0;

    private boolean ascending = true;

    public final int category(final Object element) {
      switch (grouping) {
      case GROUPING_CATEGORY:
        // TODO This has to be done with something other than the hash.
        try {
          final ParameterizedCommand command = (element instanceof ParameterizedCommand) ? (ParameterizedCommand) element
              : ((Binding) element).getParameterizedCommand();
          return command.getCommand().getCategory().hashCode();
        } catch (final NotDefinedException e) {
          return 0;
        }
      case GROUPING_CONTEXT:
        // TODO This has to be done with something other than the hash.
        if (element instanceof Binding) {
          return ((Binding) element).getContextId().hashCode();
        }
      case GROUPING_NONE:
      default:
        return 0;
      }
    }

    public final int compare(final Viewer viewer, final Object a,
        final Object b) {

      int result = compareColumn(viewer, a, b, sortColumn);
      if (result == 0 && sortColumn != lastSortColumn) {
        result = compareColumn(viewer, a, b, lastSortColumn);
      }
      return ascending ? result : (-1) * result;
    }
   
    private int compareColumn(final Viewer viewer, final Object a, final Object b,
        final int columnNumber) {
      if (columnNumber == BindingLabelProvider.COLUMN_USER) {
        return sortUser(viewer, a, b);
      }
      IBaseLabelProvider baseLabel = ((TreeViewer)viewer).getLabelProvider();
      if (baseLabel instanceof ITableLabelProvider) {
        ITableLabelProvider tableProvider = (ITableLabelProvider) baseLabel;
        String e1p = tableProvider.getColumnText(a, columnNumber);
        String e2p = tableProvider.getColumnText(b, columnNumber);
        if (e1p != null && e2p != null) {
          return getComparator().compare(e1p, e2p);
        }
      }
      return 0;
    }
   
    private int sortUser(final Viewer viewer, final Object a, final Object b) {
      int typeA = (a instanceof Binding?((Binding)a).getType():Binding.SYSTEM);
      int typeB = (b instanceof Binding?((Binding)b).getType():Binding.SYSTEM);
      int result = typeA - typeB;
      return result;
    }

    /**
     * @return Returns the sortColumn.
     */
    public int getSortColumn() {
      return sortColumn;
    }

    /**
     * @param sortColumn
     *            The sortColumn to set.
     */
    public void setSortColumn(int sortColumn) {
      lastSortColumn = this.sortColumn;
      if (lastSortColumn != sortColumn) {
        ascending = true;
      }
      this.sortColumn = sortColumn;
    }

    /**
     * @return Returns the ascending.
     */
    public boolean isAscending() {
      return ascending;
    }

    /**
     * @param ascending
     *            The ascending to set.
     */
    public void setAscending(boolean ascending) {
      this.ascending = ascending;
    }
  }

  /**
   * The constant value for <code>grouping</code> when the bindings should
   * be grouped by category.
   */
  private static final int GROUPING_CATEGORY = 0;

  /**
   * The constant value for <code>grouping</code> when the bindings should
   * be grouped by context.
   */
  private static final int GROUPING_CONTEXT = 1;

  /**
   * The constant value for <code>grouping</code> when the bindings should
   * not be grouped (i.e., they should be displayed in a flat list).
   */
  private static final int GROUPING_NONE = 2;

  /**
   * The path at which the icon for "groups of bindings" is located.
   */
  private static final String ICON_GROUP_OF_BINDINGS = "$nl$/icons/full/obj16/keygroups_obj.gif"; //$NON-NLS-1$

  private static final String CONTEXT_ID_ACTION_SETS = "org.eclipse.ui.contexts.actionSet"; //$NON-NLS-1$

  private static final String CONTEXT_ID_INTERNAL = ".internal."; //$NON-NLS-1$

  /**
   * The number of items to show in the bindings table tree.
   */
  private static final int ITEMS_TO_SHOW = 7;

  /**
   * A comparator that can be used for display of
   * <code>NamedHandleObject</code> instances to the end user.
   */
  private static final NamedHandleObjectComparator NAMED_HANDLE_OBJECT_COMPARATOR = new NamedHandleObjectComparator();

  public final static String TAG_DIALOG_SECTION = "org.eclipse.ui.preferences.keysPreferencePage"; //$NON-NLS-1$

  private final String TAG_FIELD = "showAllField"; //$NON-NLS-1$
  private final String TAG_CONVERT_SHOW_ALL = "showAllConvert"; //$NON-NLS-1$

  private static final String TAG_FILTER_ACTION_SETS = "actionSetFilter"; //$NON-NLS-1$

  private static final String TAG_FILTER_INTERNAL = "internalFilter"; //$NON-NLS-1$

  private static final String TAG_FILTER_UNCAT = "uncategorizedFilter"; //$NON-NLS-1$

  /**
   * Sorts the given array of <code>NamedHandleObject</code> instances based
   * on their name. This is generally useful if they will be displayed to an
   * end users.
   *
   * @param objects
   *            The objects to be sorted; must not be <code>null</code>.
   * @return The same array, but sorted in place; never <code>null</code>.
   */
  private static final NamedHandleObject[] sortByName(
      final NamedHandleObject[] objects) {
    Arrays.sort(objects, NAMED_HANDLE_OBJECT_COMPARATOR);
    return objects;
  }

  /**
   * 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 text widget containing the key sequence. This value is
   * <code>null</code> until the controls are created.
   */
  private Text bindingText;

  /**
   * The workbench's command image service. This command image service is used
   * to provide an icon beside each command.
   */
  private ICommandImageService commandImageService;

  /**
   * The label containing the name of the currently selected binding's
   * command. This value is <code>null</code> until the controls are
   * created.
   */
  private Label commandNameValueLabel;

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

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

  /**
   * The label containing the description of the currently selected binding's
   * command. This value is <code>null</code> until the controls are
   * created.
   */
  private Text descriptionValueText;

  /**
   * The filtered tree containing the list of commands and bindings to edit.
   */
  private CategoryFilterTree filteredTree;

  private CategoryPatternFilter patternFilter;

  /**
   * The grouping for the bindings tree. Either there should be no group
   * (i.e., flat list), or the bindings should be grouped by either category
   * or context.
   */
  private int grouping = GROUPING_NONE;

  /**
   * The key sequence entry widget containing the trigger sequence for the
   * currently selected binding. This value is <code>null</code> until the
   * controls are created.
   */
  private KeySequenceText keySequenceText;

  /**
   * 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 BindingManager localChangeManager;

  /**
   * The context id of the binding which the user is trying to add. This value
   * is derived from the binding that is selected at the time the user tried
   * to add a binding. If this value is <code>null</code>, then the user is
   * not currently trying to add a binding to a command that already has a
   * binding.
   */
  private String markedContextId = null;

  /**
   * The parameterized command to which the user is currently trying to add a
   * binding. If this value is <code>null</code>, then the user is not
   * currently trying to add a binding to a command that already has a
   * binding.
   */
  private ParameterizedCommand markedParameterizedCommand = null;

  /**
   * The combo box containing the list of possible schemes to choose from.
   * This value is <code>null</code> until the contents are created.
   */
  private ComboViewer schemeCombo = null;

  /**
   * The check box controlling whether all commands should be shown in the
   * filtered tree. This value is <code>null</code> until the contents are
   * created.
   */
  private Button showAllCheckBox = null;

  private boolean filterActionSetContexts = true;

  private boolean filterInternalContexts = true;

  private IObservableSet commandModel;

  private IObservableSet bindingModel;

  private UnionSet model;

  /**
   * The combo box containing the list of possible contexts to choose from.
   * This value is <code>null</code> until the contents are create.
   */
  private ComboViewer whenCombo = null;

  /**
   * Adds a new binding based on an existing binding. The command and the
   * context are copied from the existing binding. The scheme id is set to be
   * the user's personal derivative scheme. The preference page is updated,
   * and focus is placed in the key sequence field.
   *
   * @param binding
   *            The binding to be added; must not be <code>null</code>.
   */
  private final void bindingAdd(final Binding binding) {
    if (!(binding.getParameterizedCommand().getCommand().isDefined()))
      return;

    // Remember the parameterized command and context.
    markedParameterizedCommand = binding.getParameterizedCommand();
    markedContextId = binding.getContextId();

    // Update the preference page.
    update();

    // Select the new binding.
    filteredTree.getViewer().setSelection(
        new StructuredSelection(binding.getParameterizedCommand()),
        true);
    bindingText.setFocus();
    bindingText.selectAll();
  }

  /**
   * Removes an existing binding. The preference page is then updated.
   *
   * @param binding
   *            The binding to be removed; must not be <code>null</code>.
   */
  private final void bindingRemove(final KeyBinding binding) {
    ArrayList extraSystemDeletes = new ArrayList();
    final String contextId = binding.getContextId();
    final String schemeId = binding.getSchemeId();
    final KeySequence triggerSequence = binding.getKeySequence();
    if (binding.getType() == Binding.USER) {
      localChangeManager.removeBinding(binding);
    } else {
      // TODO This should be the user's personal scheme.
      Collection previousConflictMatches = (Collection) localChangeManager
          .getActiveBindingsDisregardingContext().get(
              binding.getTriggerSequence());
      KeyBinding deleteBinding = new KeyBinding(triggerSequence, null,
          schemeId, contextId, null, null, null, Binding.USER);
      localChangeManager.addBinding(deleteBinding);
      if (previousConflictMatches != null) {
        Iterator i = previousConflictMatches.iterator();
        while (i.hasNext()) {
          Binding b = (Binding) i.next();
          if (b != binding && deletes(deleteBinding, b)) {
            extraSystemDeletes.add(b);
          }
        }
      }
    }

    // update the model
    bindingModel.remove(binding);
    bindingAdd(binding);
    if (!extraSystemDeletes.isEmpty()) {
      Iterator i = extraSystemDeletes.iterator();
      while (i.hasNext()) {
        KeyBinding b = (KeyBinding) i.next();
        KeyBinding newBinding = new KeyBinding(b.getKeySequence(), b
            .getParameterizedCommand(), b.getSchemeId(), b
            .getContextId(), null, null, null, Binding.USER);
        localChangeManager.addBinding(newBinding);

        bindingModel.remove(b);
        bindingModel.add(newBinding);
      }
    }
    updateConflicts(binding);
  }

  private final void updateConflicts(final Collection bindings) {
    Iterator i = bindings.iterator();
    while (i.hasNext()) {
      final Binding b = (Binding) i.next();
      if (b.getParameterizedCommand()!=null) {
        updateConflicts(b);
      }
    }
  }

  private final void updateConflicts(final Binding binding) {
    Collection matches = (Collection) localChangeManager
        .getActiveBindingsDisregardingContext().get(
            binding.getTriggerSequence());
    if (matches != null) {
      Iterator i = matches.iterator();
      while (i.hasNext()) {
        Binding b = (Binding) i.next();
        if (binding != b
            && b.getContextId().equals(binding.getContextId())) {
          filteredTree.getViewer().update(b, null);
        }
      }
    }
  }

  private final void bindingRestore(final KeyBinding binding) {
    final ParameterizedCommand cmd = binding.getParameterizedCommand();
    bindingRestore(cmd, false);
  }

  private String locale = Locale.getDefault().toString();

  private boolean localMatches(String l) {
    if (l == null) {
      return true;
    }
    return Util.equals(locale, l);
  }

  private String platform = SWT.getPlatform();

  private boolean platformMatches(String p) {
    if (p == null) {
      return true;
    }
    return Util.equals(platform, p);
  }
 
  private final String[] getSchemeIds(String schemeId) {
    final List strings = new ArrayList();
    while (schemeId != null) {
      strings.add(schemeId);
      try {
        schemeId = bindingService.getScheme(schemeId).getParentId();
      } catch (final NotDefinedException e) {
        return new String[0];
      }
    }

    return (String[]) strings.toArray(new String[strings.size()]);
  }

  private final void bindingRestore(final ParameterizedCommand cmd,
      boolean removeCmd) {
    Set addSystemAll = new HashSet();
    ArrayList removeUser = new ArrayList();
    ArrayList removeBinding = new ArrayList();
    Binding[] bindings = localChangeManager.getBindings();
    for (int i = 0; i < bindings.length; i++) {
      final Binding b = bindings[i];
      if (b.getParameterizedCommand() == null
          && localMatches(b.getLocale())
          && platformMatches(b.getPlatform())) {
        // flat out, a delete marker
        removeBinding.add(b);
      } else if (cmd.equals(b.getParameterizedCommand())) {
        if (b.getType() == Binding.SYSTEM
            && localMatches(b.getLocale())
            && platformMatches(b.getPlatform())) {
          // a system binding for this command
          addSystemAll.add(b);
        } else if (b.getType() == Binding.USER) {
          // a user binding for this command
          removeUser.add(b);
          localChangeManager.removeBinding(b);
        }
      }
    }

    if (!addSystemAll.isEmpty()) {
      String[] activeSchemeIds = getSchemeIds(getSchemeId());
      Binding[] sysArray = (Binding[]) addSystemAll
          .toArray(new Binding[addSystemAll.size()]);
      for (int k = 0; k < sysArray.length; k++) {
        Binding sys = sysArray[k];
        boolean deleted = false;
        for (Iterator i = removeBinding.iterator(); i.hasNext();) {
          Binding del = (Binding) i.next();
          if (deletes(del, sys)) {
            if (del.getType() == Binding.USER) {
              removeUser.add(del);
              localChangeManager.removeBinding(del);
            } else {
              deleted = true;
              addSystemAll.remove(sys);
            }
          }
        }
        // Check the scheme ids.
        final String schemeId = sys.getSchemeId();
        boolean found = false;
        if (activeSchemeIds != null && !deleted) {
          for (int j = 0; j < activeSchemeIds.length; j++) {
            if (Util.equals(schemeId, activeSchemeIds[j])) {
              found = true;
              break;
            }
          }
        }
        if (!found && sys.getType() == Binding.SYSTEM) {
          addSystemAll.remove(sys);
        }
      }
    }
   

    bindingModel.addAll(addSystemAll);
    bindingModel.removeAll(removeUser);
    updateConflicts(addSystemAll);
    updateConflicts(removeUser);
    if (addSystemAll.isEmpty()) {
      commandModel.add(cmd);
      filteredTree.getViewer().setSelection(new StructuredSelection(cmd),
          true);
    } else if (removeCmd) {
      commandModel.remove(cmd);
    }
    if (!addSystemAll.isEmpty()) {
      // Select the new binding.
      filteredTree.getViewer().setSelection(
          new StructuredSelection(addSystemAll.iterator().next()),
          true);
    }

    update();
  }

  final static boolean deletes(final Binding del, final Binding binding) {
    boolean deletes = true;
    deletes &= Util.equals(del.getContextId(), binding.getContextId());
    deletes &= Util.equals(del.getTriggerSequence(), binding
        .getTriggerSequence());
    if (del.getLocale() != null) {
      deletes &= Util.equals(del.getLocale(), binding.getLocale());
    }
    if (del.getPlatform() != null) {
      deletes &= Util.equals(del.getPlatform(), binding.getPlatform());
    }
    deletes &= (binding.getType() == Binding.SYSTEM);
    deletes &= Util.equals(del.getParameterizedCommand(), null);

    return deletes;
  }

  /**
   * Creates the button bar across the bottom of the preference page. This
   * button bar contains the "Advanced..." button.
   *
   * @param parent
   *            The composite in which the button bar should be placed; never
   *            <code>null</code>.
   * @return The button bar composite; never <code>null</code>.
   */
  private final Control createButtonBar(final Composite parent) {
    GridLayout layout;
    GridData gridData;
    int widthHint;

    // Create the composite to house the button bar.
    final Composite buttonBar = new Composite(parent, SWT.NONE);
    layout = new GridLayout(1, false);
    layout.marginWidth = 0;
    buttonBar.setLayout(layout);
    gridData = new GridData();
    gridData.horizontalAlignment = SWT.END;
    buttonBar.setLayoutData(gridData);

    // Advanced button.
    final Button advancedButton = new Button(buttonBar, SWT.PUSH);
    gridData = new GridData();
    widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
    advancedButton.setText(NewKeysPreferenceMessages.AdvancedButton_Text);
    gridData.widthHint = Math.max(widthHint, advancedButton.computeSize(
        SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
    advancedButton.setLayoutData(gridData);
    advancedButton.addSelectionListener(new SelectionListener() {
      public void widgetDefaultSelected(SelectionEvent e) {
      }

      public void widgetSelected(SelectionEvent e) {
        KeysPreferenceFiltersDialog dialog = new KeysPreferenceFiltersDialog(
            getShell());
        dialog.setFilterActionSet(filterActionSetContexts);
        dialog.setFilterInternal(filterInternalContexts);
        dialog.setFilterUncategorized(filteredTree.isFilteringCategories());
        if (dialog.open() == Window.OK) {
          filterActionSetContexts = dialog.getFilterActionSet();
          filterInternalContexts = dialog.getFilterInternal();
          filteredTree.filterCategories(dialog
              .getFilterUncategorized());
          whenCombo.setInput(getContexts());
          updateDataControls();
        }
      }
    });
    return buttonBar;
  }

  /*
   * (non-Javadoc)
   *
   * @see org.eclipse.jface.preference.PreferencePage#createContents(org.eclipse.swt.widgets.Composite)
   */
  protected final Control createContents(final Composite parent) {
    PlatformUI.getWorkbench().getHelpSystem().setHelp(parent,
        IWorkbenchHelpContextIds.KEYS_PREFERENCE_PAGE);
   
    GridLayout layout = null;

    long startTime = 0L;
    if (DEBUG) {
      startTime = System.currentTimeMillis();
    }

    IDialogSettings settings = getDialogSettings();
    if (settings.get(TAG_FILTER_ACTION_SETS) != null) {
      filterActionSetContexts = settings
          .getBoolean(TAG_FILTER_ACTION_SETS);
    }
    if (settings.get(TAG_FILTER_INTERNAL) != null) {
      filterInternalContexts = settings.getBoolean(TAG_FILTER_INTERNAL);
    }
    patternFilter = new CategoryPatternFilter(
        true, commandService.getCategory(null));
    if (settings.get(TAG_FILTER_UNCAT) != null) {
      patternFilter.filterCategories(settings
          .getBoolean(TAG_FILTER_UNCAT));
    }

    // Creates a composite to hold all of the page contents.
    final Composite page = new Composite(parent, SWT.NONE);
    layout = new GridLayout(1, false);
    layout.marginWidth = 0;
    page.setLayout(layout);

    createSchemeControls(page);
    createTree(page);
    createTreeControls(page);
    createDataControls(page);
    createButtonBar(page);

    fill();
    update();

    applyDialogFont(page);

    if (DEBUG) {
      final long elapsedTime = System.currentTimeMillis() - startTime;
      Tracing.printTrace(TRACING_COMPONENT, "Created page in " //$NON-NLS-1$
          + elapsedTime + "ms"); //$NON-NLS-1$
    }

    return page;
  }

  private final Control createDataControls(final Composite parent) {
    GridLayout layout;
    GridData gridData;

    // Creates the data area.
    final Composite dataArea = new Composite(parent, SWT.NONE);
    layout = new GridLayout(2, true);
    layout.marginWidth = 0;
    dataArea.setLayout(layout);
    gridData = new GridData();
    gridData.grabExcessHorizontalSpace = true;
    gridData.horizontalAlignment = SWT.FILL;
    dataArea.setLayoutData(gridData);

    // LEFT DATA AREA
    // Creates the left data area.
    final Composite leftDataArea = new Composite(dataArea, SWT.NONE);
    layout = new GridLayout(3, false);
    leftDataArea.setLayout(layout);
    gridData = new GridData();
    gridData.grabExcessHorizontalSpace = true;
    gridData.verticalAlignment = SWT.TOP;
    gridData.horizontalAlignment = SWT.FILL;
    leftDataArea.setLayoutData(gridData);

    // The command name label.
    final Label commandNameLabel = new Label(leftDataArea, SWT.NONE);
    commandNameLabel
        .setText(NewKeysPreferenceMessages.CommandNameLabel_Text);

    // The current command name.
    commandNameValueLabel = new Label(leftDataArea, SWT.NONE);
    gridData = new GridData();
    gridData.grabExcessHorizontalSpace = true;
    gridData.horizontalSpan = 2;
    gridData.horizontalAlignment = SWT.FILL;
    commandNameValueLabel.setLayoutData(gridData);

    // The binding label.
    final Label bindingLabel = new Label(leftDataArea, SWT.NONE);
    bindingLabel.setText(NewKeysPreferenceMessages.BindingLabel_Text);

    // The key sequence entry widget.
    bindingText = new Text(leftDataArea, SWT.BORDER);
    gridData = new GridData();
    gridData.grabExcessHorizontalSpace = true;
    gridData.horizontalAlignment = SWT.FILL;
    gridData.widthHint = 200;
    bindingText.setLayoutData(gridData);

    bindingText.addFocusListener(new FocusListener() {
      public void focusGained(FocusEvent e) {
        bindingService.setKeyFilterEnabled(false);
      }

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

    keySequenceText = new KeySequenceText(bindingText);
    keySequenceText.setKeyStrokeLimit(4);
    keySequenceText
        .addPropertyChangeListener(new IPropertyChangeListener() {
          public final void propertyChange(
              final PropertyChangeEvent event) {
            if (!event.getOldValue().equals(event.getNewValue())) {
              keySequenceChanged();
            }
          }
        });

    // Button for adding trapped key strokes
    final Button addKeyButton = new Button(leftDataArea, SWT.LEFT
        | SWT.ARROW);
    addKeyButton
        .setToolTipText(NewKeysPreferenceMessages.AddKeyButton_ToolTipText);
    gridData = new GridData();
    gridData.heightHint = schemeCombo.getCombo().getTextHeight();
    addKeyButton.setLayoutData(gridData);

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

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

        public void widgetSelected(SelectionEvent e) {
          keySequenceText.insert(trappedKey);
          bindingText.setFocus();
          bindingText.setSelection(bindingText.getTextLimit());
        }
      });
    }
    addKeyButton.addSelectionListener(new SelectionAdapter() {

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

    final IObservableValue selection = ViewersObservables
        .observeSingleSelection(filteredTree.getViewer());

    // The when label.
    final Label whenLabel = new Label(leftDataArea, SWT.NONE);
    whenLabel.setText(NewKeysPreferenceMessages.WhenLabel_Text);

    // The when combo.
    whenCombo = new ComboViewer(leftDataArea);
    gridData = new GridData();
    gridData.grabExcessHorizontalSpace = true;
    gridData.horizontalAlignment = SWT.FILL;
    gridData.horizontalSpan = 2;
    whenCombo.getCombo().setLayoutData(gridData);
    whenCombo.setLabelProvider(new NamedHandleObjectLabelProvider());
    whenCombo.setContentProvider(new ArrayContentProvider());
    whenCombo.setComparator(new ViewerComparator());
    whenCombo
        .addPostSelectionChangedListener(new ISelectionChangedListener() {

          public void selectionChanged(SelectionChangedEvent event) {
            updateWhenCombo();
          }
        });

    whenCombo.getCombo().setVisibleItemCount(20);
    whenCombo.getCombo().setVisible(false);
    whenLabel.setVisible(false);
    selection.addValueChangeListener(new IValueChangeListener() {

      public void handleValueChange(ValueChangeEvent event) {
        boolean visible = false;
        if (selection.getValue() instanceof KeyBinding) {
          visible = true;
        }
        Combo combo = whenCombo.getCombo();
        if (!combo.isDisposed()) {
          combo.setVisible(visible);
        }
        if (!whenLabel.isDisposed()) {
          whenLabel.setVisible(visible);
        }
      }
    });
   
    final Label asterisk = new Label(leftDataArea, SWT.NONE);
    asterisk.setText(NewKeysPreferenceMessages.Asterisk_Text);
    gridData = new GridData();
    gridData.grabExcessHorizontalSpace = true;
    gridData.horizontalSpan = 2;
    gridData.horizontalAlignment = SWT.FILL;
    asterisk.setLayoutData(gridData);

    // RIGHT DATA AREA
    // Creates the right data area.
    final Composite rightDataArea = new Composite(dataArea, SWT.NONE);
    layout = new GridLayout(1, false);
    rightDataArea.setLayout(layout);
    gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
    rightDataArea.setLayoutData(gridData);

    // The description label.
    final Label descriptionLabel = new Label(rightDataArea, SWT.NONE);
    descriptionLabel
        .setText(NewKeysPreferenceMessages.DescriptionLabel_Text);
    gridData = new GridData();
    gridData.grabExcessHorizontalSpace = true;
    gridData.horizontalAlignment = SWT.FILL;
    descriptionLabel.setLayoutData(gridData);

    // The description value.
    descriptionValueText = new Text(rightDataArea, SWT.BORDER | SWT.MULTI
        | SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL | SWT.H_SCROLL);
    gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
    gridData.horizontalIndent = 20;
    descriptionValueText.setLayoutData(gridData);
    return dataArea;
  }

  private final Control createSchemeControls(final Composite parent) {
    GridLayout layout;
    GridData gridData;

    // Create a composite to hold the controls.
    final Composite schemeControls = new Composite(parent, SWT.NONE);
    layout = new GridLayout(3, false);
    layout.marginWidth = 0;
    schemeControls.setLayout(layout);
    gridData = new GridData();
    gridData.grabExcessHorizontalSpace = true;
    gridData.horizontalAlignment = SWT.FILL;
    schemeControls.setLayoutData(gridData);

    // Create the label.
    final Label schemeLabel = new Label(schemeControls, SWT.NONE);
    schemeLabel.setText(NewKeysPreferenceMessages.SchemeLabel_Text);

    // Create the combo.
    schemeCombo = new ComboViewer(schemeControls);
    schemeCombo.setLabelProvider(new NamedHandleObjectLabelProvider());
    schemeCombo.setContentProvider(new ArrayContentProvider());
    gridData = new GridData();
    gridData.widthHint = 150;
    gridData.horizontalAlignment = SWT.FILL;
    schemeCombo.getCombo().setLayoutData(gridData);
    schemeCombo
        .addSelectionChangedListener(new ISelectionChangedListener() {
          public final void selectionChanged(
              final SelectionChangedEvent event) {
            selectSchemeCombo(event);
          }
        });

    return schemeControls;
  }

  private final Control createTree(final Composite parent) {
    GridData gridData;

    filteredTree = new CategoryFilterTree(parent, SWT.SINGLE
        | SWT.FULL_SELECTION | SWT.BORDER, patternFilter);
    final GridLayout layout = new GridLayout(1, false);
    layout.marginWidth = 0;
    filteredTree.setLayout(layout);
    gridData = new GridData();
    gridData.grabExcessHorizontalSpace = true;
    gridData.grabExcessVerticalSpace = true;
    gridData.horizontalAlignment = SWT.FILL;
    gridData.verticalAlignment = SWT.FILL;
    filteredTree.setLayoutData(gridData);

    // Make sure the filtered tree has a height of ITEMS_TO_SHOW
    final Tree tree = filteredTree.getViewer().getTree();
    tree.setHeaderVisible(true);
    final Object layoutData = tree.getLayoutData();
    if (layoutData instanceof GridData) {
      gridData = (GridData) layoutData;
      final int itemHeight = tree.getItemHeight();
      if (itemHeight > 1) {
        gridData.heightHint = ITEMS_TO_SHOW * itemHeight;
      }
    }

    final BindingComparator comparator = new BindingComparator();
    comparator.setSortColumn(0);

    // Create the columns for the tree.

    final TreeColumn commandNameColumn = new TreeColumn(tree, SWT.LEFT,
        BindingLabelProvider.COLUMN_COMMAND);
    commandNameColumn
        .setText(NewKeysPreferenceMessages.CommandNameColumn_Text);
    tree.setSortColumn(commandNameColumn);
    tree.setSortDirection(comparator.isAscending() ? SWT.UP : SWT.DOWN);
    commandNameColumn.addSelectionListener(new ResortColumn(comparator,
        commandNameColumn, tree, BindingLabelProvider.COLUMN_COMMAND));

    final TreeColumn triggerSequenceColumn = new TreeColumn(tree, SWT.LEFT,
        BindingLabelProvider.COLUMN_TRIGGER_SEQUENCE);
    triggerSequenceColumn
        .setText(NewKeysPreferenceMessages.TriggerSequenceColumn_Text);
    triggerSequenceColumn.addSelectionListener(new ResortColumn(comparator,
        triggerSequenceColumn, tree,
        BindingLabelProvider.COLUMN_TRIGGER_SEQUENCE));

    final TreeColumn whenColumn = new TreeColumn(tree, SWT.LEFT,
        BindingLabelProvider.COLUMN_WHEN);
    whenColumn.setText(NewKeysPreferenceMessages.WhenColumn_Text);
    whenColumn.addSelectionListener(new ResortColumn(comparator,
        whenColumn, tree, BindingLabelProvider.COLUMN_WHEN));

    final TreeColumn categoryColumn = new TreeColumn(tree, SWT.LEFT,
        BindingLabelProvider.COLUMN_CATEGORY);
    categoryColumn.setText(NewKeysPreferenceMessages.CategoryColumn_Text);
    categoryColumn.addSelectionListener(new ResortColumn(comparator,
        categoryColumn, tree, BindingLabelProvider.COLUMN_CATEGORY));

    final TreeColumn userMarker = new TreeColumn(tree, SWT.LEFT,
        BindingLabelProvider.COLUMN_USER);
    userMarker.setText(NewKeysPreferenceMessages.UserColumn_Text);
    userMarker.addSelectionListener(new ResortColumn(comparator,
        userMarker, tree, BindingLabelProvider.COLUMN_USER));

    // Set up the providers for the viewer.
    final TreeViewer viewer = filteredTree.getViewer();
    viewer.setLabelProvider(new BindingLabelProvider());

    viewer.setContentProvider(new ObservableSetContentProvider());

    viewer.setComparator(comparator);

    /*
     * Listen for selection changes so that the data controls can be
     * updated.
     */
    viewer.addSelectionChangedListener(new ISelectionChangedListener() {
      public final void selectionChanged(final SelectionChangedEvent event) {
        selectTreeRow(event);
      }
    });

    // Adjust how the filter works.
    filteredTree.getPatternFilter().setIncludeLeadingWildcard(true);
    return filteredTree;
  }

  private final Control createTreeControls(final Composite parent) {
    GridLayout layout;
    GridData gridData;
    int widthHint;

    // Creates controls related to the tree.
    final Composite treeControls = new Composite(parent, SWT.NONE);
    layout = new GridLayout(4, false);
    layout.marginWidth = 0;
    treeControls.setLayout(layout);
    gridData = new GridData();
    gridData.grabExcessHorizontalSpace = true;
    gridData.horizontalAlignment = SWT.FILL;
    treeControls.setLayoutData(gridData);

    // Create the show all check box.
    showAllCheckBox = new Button(treeControls, SWT.CHECK);
    gridData = new GridData();
    gridData.grabExcessHorizontalSpace = true;
    gridData.horizontalAlignment = SWT.FILL;
    gridData.verticalAlignment = SWT.TOP;
    showAllCheckBox.setLayoutData(gridData);
    showAllCheckBox.setText(NewKeysPreferenceMessages.ShowAllCheckBox_Text);
    IDialogSettings settings = getDialogSettings();
    boolean showAllValue = !settings.getBoolean(TAG_CONVERT_SHOW_ALL)
      || settings.getBoolean(TAG_FIELD);
    showAllCheckBox.setSelection(showAllValue);
    showAllCheckBox.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        updateShowAll();
      }
    });

    final IObservableValue selection = ViewersObservables
        .observeSingleSelection(filteredTree.getViewer());

    // Create the delete binding button.
    final Button addBindingButton = new Button(treeControls, SWT.PUSH);
    gridData = new GridData();
    widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
    addBindingButton
        .setText(NewKeysPreferenceMessages.AddBindingButton_Text);
    gridData.widthHint = Math.max(widthHint, addBindingButton.computeSize(
        SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
    addBindingButton.setLayoutData(gridData);
    addBindingButton.addSelectionListener(new SelectionAdapter() {
      public final void widgetSelected(final SelectionEvent event) {
        selectAddBindingButton(event);
      }
    });
    new ControlUpdater(addBindingButton) {
      protected void updateControl() {
        Object selectedObject = selection.getValue();
        addBindingButton
            .setEnabled(selectedObject instanceof KeyBinding);
      }
    };

    // Create the delete binding button.
    final Button removeBindingButton = new Button(treeControls, SWT.PUSH);
    gridData = new GridData();
    widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
    removeBindingButton
        .setText(NewKeysPreferenceMessages.RemoveBindingButton_Text);
    gridData.widthHint = Math.max(widthHint, removeBindingButton
        .computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
    removeBindingButton.setLayoutData(gridData);
    removeBindingButton.addSelectionListener(new SelectionAdapter() {
      public final void widgetSelected(final SelectionEvent event) {
        selectRemoveBindingButton(event);
      }
    });
    new ControlUpdater(removeBindingButton) {
      protected void updateControl() {
        Object selectedObject = selection.getValue();
        removeBindingButton
            .setEnabled(selectedObject instanceof KeyBinding);
      }
    };

    // Create the delete binding button.
    final Button restore = new Button(treeControls, SWT.PUSH);
    gridData = new GridData();
    widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
    restore.setText(NewKeysPreferenceMessages.RestoreBindingButton_Text);
    gridData.widthHint = Math.max(widthHint, restore.computeSize(
        SWT.DEFAULT, SWT.DEFAULT, true).x) + 5;
    restore.setLayoutData(gridData);
    restore.addSelectionListener(new SelectionAdapter() {
      public final void widgetSelected(final SelectionEvent event) {
        selectRestoreBindingButton(event);
      }
    });

    return treeControls;
  }

  private void updateShowAll() {
    BusyIndicator.showWhile(
        filteredTree.getViewer().getTree().getDisplay(),
        new Runnable() {
          public void run() {
            try {
              filteredTree.getViewer().getTree().setRedraw(false);
              fillInCommands();
            } finally {
              filteredTree.getViewer().getTree().setRedraw(true);
            }
          }
        });
  }

  /**
   * Copies all of the information from the workbench into a local change
   * manager, and then the local change manager is used to populate the
   * contents of the various widgets on the page.
   *
   * The widgets affected by this method are: scheme combo, bindings
   * table/tree model, and the when combo.
   */
  private final void fill() {
    // Make an internal binding manager to track changes.
    localChangeManager = new BindingManager(new ContextManager(),
        new CommandManager());
    final Scheme[] definedSchemes = bindingService.getDefinedSchemes();
    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());

    // Update the scheme combo.
    schemeCombo
        .setInput(sortByName(localChangeManager.getDefinedSchemes()));
    setScheme(localChangeManager.getActiveScheme());

    // Update the when combo.
    whenCombo.setInput(getContexts());

    commandModel = new WritableSet();
    bindingModel = new WritableSet();
    model = new UnionSet(
        new IObservableSet[] { bindingModel, commandModel });

    bindingModel.addAll(localChangeManager
        .getActiveBindingsDisregardingContextFlat());
    fillInCommands();

    if (DEBUG) {
      Tracing.printTrace(TRACING_COMPONENT,
          "fill in size: " + model.size()); //$NON-NLS-1$
    }

    filteredTree.getViewer().setInput(model);
  }

  /**
   *
   */
  private void fillInCommands() {
    long startTime = 0L;
    if (DEBUG) {
      startTime = System.currentTimeMillis();
    }
    if (showAllCheckBox.getSelection()) {
      final Collection commandIds = commandService.getDefinedCommandIds();
      final Collection commands = new HashSet();
      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.
        }
      }

      // Remove duplicates.
      Iterator i = bindingModel.iterator();
      while (i.hasNext()) {
        commands.remove(((Binding) i.next()).getParameterizedCommand());
      }

      commandModel.addAll(commands);
    } else {
      commandModel.clear();
    }

    if (DEBUG) {
      final long elapsedTime = System.currentTimeMillis() - startTime;
      Tracing.printTrace(TRACING_COMPONENT, "fillInCommands in " //$NON-NLS-1$
          + elapsedTime + "ms"); //$NON-NLS-1$
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench)
   */
  public final void init(final IWorkbench workbench) {
    bindingService = (IBindingService) workbench
        .getService(IBindingService.class);
    commandImageService = (ICommandImageService) workbench
        .getService(ICommandImageService.class);
    commandService = (ICommandService) workbench
        .getService(ICommandService.class);
    contextService = (IContextService) workbench
        .getService(IContextService.class);
  }

  /**
   * Updates the interface as the key sequence has changed. This finds the
   * selected item. If the selected item is a binding, then it updates the
   * binding -- either by updating a user binding, or doing the deletion
   * marker dance with a system binding. If the selected item is a
   * parameterized command, then a binding is created based on the data
   * controls.
   */
  private final void keySequenceChanged() {
    long startTime = 0L;
    if (DEBUG) {
      startTime = System.currentTimeMillis();
    }

    final KeySequence keySequence = keySequenceText.getKeySequence();
    if (!keySequence.isComplete()) {
      return;
    }

    if ((keySequence == null) || (keySequence.isEmpty())) {
      ISelection selection = filteredTree.getViewer().getSelection();
      if (selection instanceof IStructuredSelection) {
        IStructuredSelection structuredSelection = (IStructuredSelection) selection;
        final Object node = structuredSelection.getFirstElement();
        if (node instanceof KeyBinding) {
          bindingRemove((KeyBinding) node);
        }
      }
      return;
    }

    ISelection selection = filteredTree.getViewer().getSelection();
    if (selection instanceof IStructuredSelection) {
      IStructuredSelection structuredSelection = (IStructuredSelection) selection;
      final Object node = structuredSelection.getFirstElement();
      if (node != null) {
        final Object object = node;
        selection = whenCombo.getSelection();
        final String contextId;
        if (selection instanceof IStructuredSelection) {
          structuredSelection = (IStructuredSelection) selection;
          final Object firstElement = structuredSelection
              .getFirstElement();
          if (firstElement == null) {
            contextId = IContextIds.CONTEXT_ID_WINDOW;
          } else {
            contextId = ((Context) firstElement).getId();
          }
        } else {
          contextId = IContextIds.CONTEXT_ID_WINDOW;
        }
        if (object instanceof KeyBinding) {
          KeyBinding keyBinding = (KeyBinding) object;
          if (!keyBinding.getContextId().equals(contextId)
              || !keyBinding.getKeySequence().equals(keySequence)) {
            final KeyBinding binding = new KeyBinding(
                keySequence,
                keyBinding.getParameterizedCommand(),
                getSchemeId(),
                contextId, null, null, null, Binding.USER);

            ArrayList extraSystemDeletes = new ArrayList();
            if (keyBinding.getType() == Binding.USER) {
              localChangeManager.removeBinding(keyBinding);
            } else {             
              // TODO This should be the user's personal scheme.
              Collection previousConflictMatches = (Collection) localChangeManager
                  .getActiveBindingsDisregardingContext().get(
                      keyBinding.getTriggerSequence());
              KeyBinding deleteBinding = new KeyBinding(
                  keyBinding.getKeySequence(), null,
                  keyBinding.getSchemeId(), keyBinding
                      .getContextId(), null, null, null,
                  Binding.USER);
              localChangeManager.addBinding(deleteBinding);
              if (previousConflictMatches != null) {
                Iterator i = previousConflictMatches.iterator();
                while (i.hasNext()) {
                  Binding b = (Binding) i.next();
                  if (b != keyBinding && deletes(deleteBinding, b)) {
                    extraSystemDeletes.add(b);
                  }
                }
              }
            }
            localChangeManager.addBinding(binding);
            // update the model
            bindingModel.remove(keyBinding);
            bindingModel.add(binding);
            if (!extraSystemDeletes.isEmpty()) {
              Iterator i = extraSystemDeletes.iterator();
              while (i.hasNext()) {
                KeyBinding b = (KeyBinding) i.next();
                KeyBinding newBinding = new KeyBinding(b.getKeySequence(), b
                    .getParameterizedCommand(), b.getSchemeId(), b
                    .getContextId(), null, null, null, Binding.USER);
                localChangeManager.addBinding(newBinding);

                bindingModel.remove(b);
                bindingModel.add(newBinding);
              }
            }
            updateConflicts(keyBinding);
            updateConflicts(binding);
            // end update the model
            update();
            filteredTree.getViewer().setSelection(
                new StructuredSelection(binding), true);
          }
        } else if (object instanceof ParameterizedCommand) {
          // TODO This should use the user's personal scheme.
          final KeyBinding binding = new KeyBinding(keySequence,
              (ParameterizedCommand) object,
              getSchemeId(),
              contextId, null, null, null, Binding.USER);
          localChangeManager.addBinding(binding);
          // update the model
          // end update the model
          bindingModel.add(binding);
          commandModel.remove(object);
          updateConflicts(binding);
          update();

          filteredTree.getViewer().setSelection(
              new StructuredSelection(binding), true);
        }
      }
    }
    if (DEBUG) {
      final long elapsedTime = System.currentTimeMillis() - startTime;
      Tracing.printTrace(TRACING_COMPONENT, "keySequenceChanged in " //$NON-NLS-1$
          + elapsedTime + "ms"); //$NON-NLS-1$
    }
  }

  /**
   * 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 = NewKeysPreferenceMessages.PreferenceStoreError_Message;
    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);
  }

  protected final void performDefaults() {

    // Ask the user to confirm
    final String title = NewKeysPreferenceMessages.RestoreDefaultsMessageBoxText;
    final String message = NewKeysPreferenceMessages.RestoreDefaultsMessageBoxMessage;
    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);
      }
      long startTime = 0L;
      if (DEBUG) {
        startTime = System.currentTimeMillis();
      }
      busyRefillTree();
      if (DEBUG) {
        final long elapsedTime = System.currentTimeMillis() - startTime;
        Tracing.printTrace(TRACING_COMPONENT,
            "performDefaults:model in " //$NON-NLS-1$
                + elapsedTime + "ms"); //$NON-NLS-1$
      }
    }

    setScheme(localChangeManager.getActiveScheme());

    super.performDefaults();
  }

  /**
   * We're re-filling the  entire tree, both bindings and commands.  It's
   * loud.
   */
  private void busyRefillTree() {
    if (bindingModel==null) {
      // we haven't really been created yet.
      return;
    }
    BusyIndicator.showWhile(filteredTree.getViewer().getTree()
        .getDisplay(), new Runnable() {
      public void run() {
        try {
          filteredTree.getViewer().getTree().setRedraw(false);

          bindingModel.clear();
          commandModel.clear();
          Collection comeBack = localChangeManager
              .getActiveBindingsDisregardingContextFlat();
          bindingModel.addAll(comeBack);

          // showAllCheckBox.setSelection(false);
          fillInCommands();
        } finally {
          filteredTree.getViewer().getTree().setRedraw(true);
        }
      }
    });
    updateDataControls();
  }

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

  /**
   * Handles the selection event on the add binding button. This adds a new
   * binding based on the current selection.
   *
   * @param event
   *            Ignored.
   */
  private final void selectAddBindingButton(final SelectionEvent event) {
    long startTime = 0L;
    if (DEBUG) {
      startTime = System.currentTimeMillis();
    }

    // Check to make sure we've got a selection.
    final TreeViewer viewer = filteredTree.getViewer();
    final ISelection selection = viewer.getSelection();
    if (!(selection instanceof IStructuredSelection)) {
      return;
    }

    final IStructuredSelection structuredSelection = (IStructuredSelection) selection;
    final Object firstElement = structuredSelection.getFirstElement();
    final Object value = firstElement;
    if (value instanceof KeyBinding) {
      bindingAdd((KeyBinding) value);
    } else if (value instanceof ParameterizedCommand) {
      bindingText.setFocus();
    }

    if (DEBUG) {
      final long elapsedTime = System.currentTimeMillis() - startTime;
      Tracing.printTrace(TRACING_COMPONENT, "selectAddBindingButton in " //$NON-NLS-1$
          + elapsedTime + "ms"); //$NON-NLS-1$
    }
  }

  /**
   * Handles the selection event on the remove binding button. This removes
   * the selected binding.
   *
   * @param event
   *            Ignored.
   */
  private final void selectRemoveBindingButton(final SelectionEvent event) {
    long startTime = 0L;
    if (DEBUG) {
      startTime = System.currentTimeMillis();
    }
    // Check to make sure we've got a selection.
    final TreeViewer viewer = filteredTree.getViewer();
    final ISelection selection = viewer.getSelection();
    if (!(selection instanceof IStructuredSelection)) {
      return;
    }

    final IStructuredSelection structuredSelection = (IStructuredSelection) selection;
    final Object firstElement = structuredSelection.getFirstElement();
    final Object value = firstElement;
    if (value instanceof KeyBinding) {
      bindingRemove((KeyBinding) value);
    } else if (value == markedParameterizedCommand) {
      commandModel.remove(markedParameterizedCommand);
      markedParameterizedCommand = null;
      markedContextId = null;
      update();
    }
    if (DEBUG) {
      final long elapsedTime = System.currentTimeMillis() - startTime;
      Tracing.printTrace(TRACING_COMPONENT,
          "selectRemoveBindingButton in " //$NON-NLS-1$
              + elapsedTime + "ms"); //$NON-NLS-1$
    }
  }

  private final void selectRestoreBindingButton(final SelectionEvent event) {
    long startTime = 0L;
    if (DEBUG) {
      startTime = System.currentTimeMillis();
    }
    // Check to make sure we've got a selection.
    final TreeViewer viewer = filteredTree.getViewer();
    final ISelection selection = viewer.getSelection();
    if (!(selection instanceof IStructuredSelection)) {
      return;
    }

    final IStructuredSelection structuredSelection = (IStructuredSelection) selection;
    final Object firstElement = structuredSelection.getFirstElement();
    final Object value = firstElement;
    if (value instanceof KeyBinding) {
      bindingRestore((KeyBinding) value);
    } else if (value instanceof ParameterizedCommand) {
      bindingRestore((ParameterizedCommand) value, true);
    }
    if (DEBUG) {
      final long elapsedTime = System.currentTimeMillis() - startTime;
      Tracing.printTrace(TRACING_COMPONENT,
          "selectRestoreBindingButton in " //$NON-NLS-1$
              + elapsedTime + "ms"); //$NON-NLS-1$
    }
  }

  /**
   * Handles a selection event on the scheme combo. If the scheme has changed,
   * then the local change manager is updated, and the page's contents are
   * updated as well.
   *
   * @param event
   *            The selection event; must not be <code>null</code>.
   */
  private final void selectSchemeCombo(final SelectionChangedEvent event) {
    final ISelection selection = event.getSelection();
    if (selection instanceof IStructuredSelection) {
      final Object firstElement = ((IStructuredSelection) selection)
          .getFirstElement();
      if (firstElement instanceof Scheme) {
        final Scheme newScheme = (Scheme) firstElement;
        if (newScheme != localChangeManager.getActiveScheme()) {
          try {
            localChangeManager.setActiveScheme(newScheme);
            busyRefillTree();
          } catch (final NotDefinedException e) {
            // TODO The scheme wasn't valid.
          }
        }
      }
    }
  }

  /**
   * If the row has changed, then update the data controls.
   */
  private final void selectTreeRow(final SelectionChangedEvent event) {
    updateDataControls();
  }

  /**
   * Sets the currently selected scheme. Setting the scheme always triggers an
   * update of the underlying widgets.
   *
   * @param scheme
   *            The scheme to select; may be <code>null</code>.
   */
  private final void setScheme(final Scheme scheme) {
    schemeCombo.setSelection(new StructuredSelection(scheme));
  }

  /**
   * Updates all of the controls on this preference page in response to a user
   * interaction.
   */
  private final void update() {
    updateTree();
    updateDataControls();
  }

  /**
   * Updates the data controls to match the current selection, if any.
   */
  private final void updateDataControls() {
    final ISelection selection = filteredTree.getViewer().getSelection();
    if (selection instanceof IStructuredSelection) {
      final IStructuredSelection structuredSelection = (IStructuredSelection) selection;
      final Object node = structuredSelection.getFirstElement();
      if (node != null) {
        final Object object = node;
        if (object instanceof KeyBinding) {
          final KeyBinding binding = (KeyBinding) object;
          try {
            commandNameValueLabel.setText(binding
                .getParameterizedCommand().getName());
            String description = binding.getParameterizedCommand()
                .getCommand().getDescription();
            if (description == null) {
              description = Util.ZERO_LENGTH_STRING;
            }
            descriptionValueText.setText(description);
          } catch (final NotDefinedException e) {
            // It's probably okay to just let this one slide.
          }
          whenCombo.setSelection(new StructuredSelection(
              contextService.getContext(binding.getContextId())));
          keySequenceText.setKeySequence(binding.getKeySequence());

        } else if (object instanceof ParameterizedCommand) {
          final ParameterizedCommand command = (ParameterizedCommand) object;
          try {
            commandNameValueLabel.setText(command.getName());
            String description = command.getCommand()
                .getDescription();
            if (description == null) {
              description = Util.ZERO_LENGTH_STRING;
            }
            descriptionValueText.setText(description);
          } catch (final NotDefinedException e) {
            // It's probably okay to just let this one slide.
          }
          keySequenceText.clear();
          if (command == markedParameterizedCommand) {
            whenCombo.setSelection(new StructuredSelection(
                contextService.getContext(markedContextId)));
          } else {
            whenCombo
                .setSelection(new StructuredSelection(
                    contextService
                        .getContext(IContextIds.CONTEXT_ID_WINDOW)));
          }
        }
      } else {
        commandNameValueLabel.setText(""); //$NON-NLS-1$
        descriptionValueText.setText(""); //$NON-NLS-1$
        keySequenceText.clear();
        whenCombo.setSelection(null);
      }
    }
  }

  private final void updateTree() {
    long startTime = 0L;
    if (DEBUG) {
      startTime = System.currentTimeMillis();
    }

    final TreeViewer viewer = filteredTree.getViewer();

    // Add the marked parameterized command, if any.
    if (markedParameterizedCommand != null) {
      commandModel.add(markedParameterizedCommand);
      markedParameterizedCommand = null;
    }

    // Repack all of the columns.
    final Tree tree = viewer.getTree();
    final TreeColumn[] columns = tree.getColumns();

    columns[BindingLabelProvider.COLUMN_COMMAND].setWidth(240);
    columns[BindingLabelProvider.COLUMN_TRIGGER_SEQUENCE].setWidth(130);
    columns[BindingLabelProvider.COLUMN_WHEN].setWidth(130);
    columns[BindingLabelProvider.COLUMN_CATEGORY].setWidth(130);
    columns[BindingLabelProvider.COLUMN_USER].setWidth(50);

    if (DEBUG) {
      final long elapsedTime = System.currentTimeMillis() - startTime;
      Tracing.printTrace(TRACING_COMPONENT, "Refreshed page in " //$NON-NLS-1$
          + elapsedTime + "ms"); //$NON-NLS-1$
    }
  }

  /**
   * Save the state of the receiver.
   *
   * @param dialogSettings
   */
  public void saveState(IDialogSettings dialogSettings) {
    if (dialogSettings == null) {
      return;
    }
    dialogSettings.put(TAG_FIELD, showAllCheckBox.getSelection());
    dialogSettings.put(TAG_CONVERT_SHOW_ALL, true);
    dialogSettings.put(TAG_FILTER_ACTION_SETS, filterActionSetContexts);
    dialogSettings.put(TAG_FILTER_INTERNAL, filterInternalContexts);
    dialogSettings.put(TAG_FILTER_UNCAT, filteredTree.isFilteringCategories());
  }

  protected IDialogSettings getDialogSettings() {
    IDialogSettings workbenchSettings = WorkbenchPlugin.getDefault()
        .getDialogSettings();

    IDialogSettings settings = workbenchSettings
        .getSection(TAG_DIALOG_SECTION);

    if (settings == null) {
      settings = workbenchSettings.addNewSection(TAG_DIALOG_SECTION);
    }
    return settings;
  }

  protected Object[] getContexts() {

    Context[] contexts = contextService.getDefinedContexts();
    List filteredContexts = new ArrayList();
    try {
      if (filterActionSetContexts) {
        for (int i = 0; i < contexts.length; i++) {
          String parentId = contexts[i].getParentId();
          boolean check = false;
          if (contexts[i].getId().equalsIgnoreCase(
              CONTEXT_ID_ACTION_SETS)) {
            check = true;
          }
          while (parentId != null) {
            if (parentId.equalsIgnoreCase(CONTEXT_ID_ACTION_SETS)) {
              check = true;
            }
            parentId = contextService.getContext(parentId)
                .getParentId();
          }
          if (!check) {
            filteredContexts.add(contexts[i]);
          }
        }
      } else {
        filteredContexts.addAll(Arrays.asList(contexts));
      }

      if (filterInternalContexts) {
        for (int i = 0; i < filteredContexts.size(); i++) {
          if (((Context) filteredContexts.get(i)).getId().indexOf(
              CONTEXT_ID_INTERNAL) != -1) {
            filteredContexts.remove(i);
          }
        }
      }

    } catch (NotDefinedException e) {
      return contexts;
    }

    return filteredContexts.toArray();
  }

  private void updateWhenCombo() {
    ISelection selection = filteredTree.getViewer().getSelection();
    if (selection instanceof IStructuredSelection) {
      IStructuredSelection structuredSelection = (IStructuredSelection) selection;
      Object node = structuredSelection.getFirstElement();
      if (node != null) {
        final Object object = node;
        selection = whenCombo.getSelection();
        final String contextId;
        if (selection instanceof IStructuredSelection) {
          structuredSelection = (IStructuredSelection) selection;
          final Object firstElement = structuredSelection
              .getFirstElement();
          if (firstElement == null) {
            contextId = IContextIds.CONTEXT_ID_WINDOW;
          } else {
            contextId = ((Context) firstElement).getId();
          }
        } else {
          contextId = IContextIds.CONTEXT_ID_WINDOW;
        }
        if (object instanceof KeyBinding) {
          KeyBinding keyBinding = (KeyBinding) object;
          if (!keyBinding.getContextId().equals(contextId)) {
            final KeyBinding binding = new KeyBinding(
                keyBinding.getKeySequence(),
                keyBinding.getParameterizedCommand(),
                getSchemeId(),
                contextId, null, null, null, Binding.USER);

            if (keyBinding.getType() == Binding.USER) {
              localChangeManager.removeBinding(keyBinding);
            } else {
              localChangeManager.addBinding(new KeyBinding(
                  keyBinding.getKeySequence(), null,
                  keyBinding.getSchemeId(), keyBinding
                      .getContextId(), null, null, null,
                  Binding.USER));
            }
            localChangeManager.addBinding(binding);
            // update the model
            bindingModel.remove(keyBinding);
            bindingModel.add(binding);
            updateConflicts(keyBinding);
            updateConflicts(binding);
            // end update the model
            update();
            filteredTree.getViewer().setSelection(
                new StructuredSelection(binding), true);
          }
        }
      }
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see org.eclipse.jface.preference.PreferencePage#applyData(java.lang.Object)
   */
  public void applyData(Object data) {
    if (data instanceof Binding && filteredTree != null) {
      filteredTree.getViewer().setSelection(
          new StructuredSelection(data), true);
    }
  }
 
  public String getSchemeId() {
    ISelection sel = schemeCombo.getSelection();
    if (sel instanceof IStructuredSelection) {
      Object o = ((IStructuredSelection)sel).getFirstElement();
      if (o instanceof Scheme) {
        return ((Scheme)o).getId();
      }
    }
    return IBindingService.DEFAULT_DEFAULT_ACTIVE_SCHEME_ID;
  }
}
TOP

Related Classes of org.eclipse.ui.internal.keys.NewKeysPreferencePage$BindingComparator

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.