/******************************************************************************* * Copyright (c) 2010, 2014 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.e4.ui.bindings.internal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.eclipse.core.commands.ParameterizedCommand; import org.eclipse.core.commands.contexts.Context; import org.eclipse.jface.bindings.Binding; import org.eclipse.jface.bindings.Trigger; import org.eclipse.jface.bindings.TriggerSequence; import org.eclipse.jface.bindings.keys.IKeyLookup; import org.eclipse.jface.bindings.keys.KeyLookupFactory; import org.eclipse.jface.bindings.keys.KeyStroke; /** * manage tables of bindings that can be used to look up commands from keys. */ public class BindingTable { private static int compareSchemes(String[] activeSchemeIds, final String schemeId1, final String schemeId2) { if (activeSchemeIds == null || activeSchemeIds.length == 0) { return 0; } if (!schemeId2.equals(schemeId1)) { for (final String schemePointer : activeSchemeIds) { if (schemeId2.equals(schemePointer)) { return 1; } else if (schemeId1.equals(schemePointer)) { return -1; } } } return 0; } static class BindingComparator implements Comparator<Binding> { private String[] activeSchemeIds; public void setActiveSchemes(String[] activeSchemeIds) { this.activeSchemeIds = activeSchemeIds; } public String[] getActiveSchemes() { return this.activeSchemeIds; } @Override public int compare(Binding o1, Binding o2) { int rc = compareSchemes(activeSchemeIds, o1.getSchemeId(), o2.getSchemeId()); if (rc != 0) { return rc; } /* * Check to see which has the least number of triggers in the trigger sequence. */ final Trigger[] bestTriggers = o1.getTriggerSequence().getTriggers(); final Trigger[] currentTriggers = o2.getTriggerSequence().getTriggers(); int compareTo = bestTriggers.length - currentTriggers.length; if (compareTo != 0) { return compareTo; } /* * Compare the number of keys pressed in each trigger sequence. Some types of keys count * less than others (i.e., some types of modifiers keys are less likely to be chosen). */ compareTo = countStrokes(bestTriggers) - countStrokes(currentTriggers); if (compareTo != 0) { return compareTo; } // If this is still a tie, then just chose the shortest text. return o1.getTriggerSequence().format().length() - o2.getTriggerSequence().format().length(); } private final int countStrokes(final Trigger[] triggers) { int strokeCount = triggers.length; for (final Trigger trigger : triggers) { if (trigger instanceof KeyStroke) { final KeyStroke keyStroke = (KeyStroke) trigger; final int modifierKeys = keyStroke.getModifierKeys(); final IKeyLookup lookup = KeyLookupFactory.getDefault(); if ((modifierKeys & lookup.getAlt()) != 0) { strokeCount += 8; } if ((modifierKeys & lookup.getCtrl()) != 0) { strokeCount += 2; } if ((modifierKeys & lookup.getShift()) != 0) { strokeCount += 4; } if ((modifierKeys & lookup.getCommand()) != 0) { strokeCount += 2; } } else { strokeCount += 99; } } return strokeCount; } } public static final BindingComparator BEST_SEQUENCE = new BindingComparator(); private Context tableId; private ArrayList<Binding> bindings = new ArrayList<Binding>(); private Map<TriggerSequence, Binding> bindingsByTrigger = new HashMap<TriggerSequence, Binding>(); private Map<ParameterizedCommand, ArrayList<Binding>> bindingsByCommand = new HashMap<ParameterizedCommand, ArrayList<Binding>>(); private Map<TriggerSequence, ArrayList<Binding>> bindingsByPrefix = new HashMap<TriggerSequence, ArrayList<Binding>>(); private Map<TriggerSequence, ArrayList<Binding>> conflicts = new HashMap<TriggerSequence, ArrayList<Binding>>(); private Map<TriggerSequence, ArrayList<Binding>> orderedBindingsByTrigger = new HashMap<TriggerSequence, ArrayList<Binding>>(); /** * @param context */ public BindingTable(Context context) { tableId = context; } public Context getTableId() { return tableId; } public String getId() { return tableId.getId(); } public Collection<Binding> getConflicts() { Collection<Binding> conflictsList = new ArrayList<Binding>(); for (ArrayList<Binding> conflictsForTrigger : conflicts.values()) { if (conflictsForTrigger != null) { conflictsList.addAll(conflictsForTrigger); } } return conflictsList; } // checks both the active bindings and conflicts list public Collection<Binding> getConflictsFor(TriggerSequence triggerSequence) { return conflicts.get(triggerSequence); } public void addBinding(Binding binding) { if (!getId().equals(binding.getContextId())) { throw new IllegalArgumentException("Binding context " + binding.getContextId() //$NON-NLS-1$ + " does not match " + getId()); //$NON-NLS-1$ } ArrayList<Binding> bindingList = orderedBindingsByTrigger.get(binding.getTriggerSequence()); Binding possibleConflict = bindingsByTrigger.get(binding.getTriggerSequence()); if (bindingList == null || bindingList.isEmpty()) { if (possibleConflict != null) { if (bindingList == null) { bindingList = new ArrayList<Binding>(); orderedBindingsByTrigger.put(binding.getTriggerSequence(), bindingList); } bindingList.add(binding); bindingList.add(possibleConflict); Collections.sort(bindingList, BEST_SEQUENCE); } } else { bindingList.add(binding); Collections.sort(bindingList, BEST_SEQUENCE); } if (possibleConflict != null && bindingList != null && !bindingList.isEmpty() && bindingList.get(0) != possibleConflict) { removeBindingSimple(possibleConflict); possibleConflict = null; } evaluateOrderedBindings(binding.getTriggerSequence(), binding); } private void addBindingSimple(Binding binding) { bindings.add(binding); bindingsByTrigger.put(binding.getTriggerSequence(), binding); ArrayList<Binding> sequences = bindingsByCommand.get(binding.getParameterizedCommand()); if (sequences == null) { sequences = new ArrayList<Binding>(); bindingsByCommand.put(binding.getParameterizedCommand(), sequences); } sequences.add(binding); Collections.sort(sequences, BEST_SEQUENCE); TriggerSequence[] prefs = binding.getTriggerSequence().getPrefixes(); for (int i = 1; i < prefs.length; i++) { ArrayList<Binding> bindings = bindingsByPrefix.get(prefs[i]); if (bindings == null) { bindings = new ArrayList<Binding>(); bindingsByPrefix.put(prefs[i], bindings); } bindings.add(binding); } } private void removeBindingSimple(Binding binding) { bindings.remove(binding); bindingsByTrigger.remove(binding.getTriggerSequence()); ArrayList<Binding> sequences = bindingsByCommand.get(binding.getParameterizedCommand()); if (sequences != null) { sequences.remove(binding); } TriggerSequence[] prefs = binding.getTriggerSequence().getPrefixes(); for (int i = 1; i < prefs.length; i++) { ArrayList<Binding> bindings = bindingsByPrefix.get(prefs[i]); if (bindings != null) { bindings.remove(binding); } } } public void removeBinding(Binding binding) { if (!getId().equals(binding.getContextId())) { throw new IllegalArgumentException("Binding context " + binding.getContextId() //$NON-NLS-1$ + " does not match " + getId()); //$NON-NLS-1$ } ArrayList<Binding> bindingList = orderedBindingsByTrigger.get(binding.getTriggerSequence()); Binding possibleConflict = bindingsByTrigger.get(binding.getTriggerSequence()); if (possibleConflict == binding) { removeBindingSimple(binding); if (bindingList != null) { bindingList.remove(binding); if (bindingList.isEmpty()) { orderedBindingsByTrigger.remove(binding.getTriggerSequence()); } else { evaluateOrderedBindings(binding.getTriggerSequence(), null); } } } else if (bindingList != null) { bindingList.remove(binding); if (bindingList.isEmpty()) { orderedBindingsByTrigger.remove(binding.getTriggerSequence()); } else { evaluateOrderedBindings(binding.getTriggerSequence(), null); } } } /** * @param binding */ private void evaluateOrderedBindings(TriggerSequence sequence, Binding binding) { ArrayList<Binding> bindingList = orderedBindingsByTrigger.get(sequence); // calculate binding to be used or any conflicts if (bindingList != null) { if (bindingList.isEmpty()) { orderedBindingsByTrigger.remove(sequence); } else if (bindingList.size() > 1) { Binding msb = bindingList.get(0); Binding lsb = bindingList.get(1); int rc = compareSchemes(BEST_SEQUENCE.getActiveSchemes(), msb.getSchemeId(), lsb.getSchemeId()); if (rc == 0) { ArrayList<Binding> conflictList = conflicts.get(sequence); if (conflictList == null) { conflictList = new ArrayList<Binding>(); conflicts.put(sequence, conflictList); } else { conflictList.clear(); } Iterator<Binding> i = bindingList.iterator(); Binding prev = i.next(); conflictList.add(prev); while (i.hasNext() && rc == 0) { Binding next = i.next(); rc = compareSchemes(BEST_SEQUENCE.getActiveSchemes(), prev.getSchemeId(), next.getSchemeId()); if (rc == 0) { conflictList.add(next); } prev = next; } } else { conflicts.remove(sequence); if (bindingsByTrigger.get(sequence) == null) { addBindingSimple(msb); } } } else { if (bindingsByTrigger.get(sequence) == null) { addBindingSimple(bindingList.get(0)); } orderedBindingsByTrigger.remove(sequence); } } else if (binding != null) { conflicts.remove(sequence); if (bindingsByTrigger.get(sequence) == null) { addBindingSimple(binding); } } } public Binding getPerfectMatch(TriggerSequence trigger) { return bindingsByTrigger.get(trigger); } public Binding getBestSequenceFor(ParameterizedCommand command) { ArrayList<Binding> sequences = bindingsByCommand.get(command); if (sequences != null && sequences.size() > 0) { return sequences.get(0); } return null; } @SuppressWarnings("unchecked") public Collection<Binding> getSequencesFor(ParameterizedCommand command) { ArrayList<Binding> triggers = bindingsByCommand.get(command); return (Collection<Binding>) (triggers == null ? Collections.EMPTY_LIST : triggers.clone()); } public Collection<Binding> getPartialMatches(TriggerSequence sequence) { return bindingsByPrefix.get(sequence); } public boolean isPartialMatch(TriggerSequence seq) { return bindingsByPrefix.get(seq) != null; } public Collection<Binding> getBindings() { return Collections.unmodifiableCollection(bindings); } }