1 /*******************************************************************************
2  * Copyright (c) 2010, 2015 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  ******************************************************************************/
14 
15 package org.eclipse.e4.ui.bindings.internal;
16 
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.List;
20 import java.util.ListIterator;
21 import javax.inject.Inject;
22 import org.eclipse.core.commands.ParameterizedCommand;
23 import org.eclipse.core.commands.contexts.Context;
24 import org.eclipse.e4.core.contexts.IEclipseContext;
25 import org.eclipse.jface.bindings.Binding;
26 import org.eclipse.jface.bindings.TriggerSequence;
27 
28 /**
29  * manage tables of bindings that can be used to look up commands from keys.
30  */
31 public class BindingTableManager {
32 	private static final String BINDING_TABLE_PREFIX = "bindingTable:"; //$NON-NLS-1$
33 
34 	@Inject
35 	private IEclipseContext eclipseContext;
36 
37 	private ContextSet definedTables = ContextSet.EMPTY;
38 
39 	private String[] activeSchemeIds;
40 
addTable(BindingTable table)41 	public void addTable(BindingTable table) {
42 		String contextId = getTableId(table.getId());
43 		if (eclipseContext.containsKey(contextId)) {
44 			return; // it's already there
45 			//			throw new IllegalArgumentException("Already contains table " + contextId); //$NON-NLS-1$
46 		}
47 		eclipseContext.set(contextId, table);
48 		final List<Context> contexts = definedTables.getContexts();
49 		if (!contexts.contains(table.getTableId())) {
50 			// this is only valid because I'm throwing away the old definedTables contextSet
51 			contexts.add(table.getTableId());
52 			definedTables = createContextSet(contexts);
53 		}
54 	}
55 
getTableId(String id)56 	private String getTableId(String id) {
57 		return BINDING_TABLE_PREFIX + id;
58 	}
59 
removeTable(BindingTable table)60 	public void removeTable(BindingTable table) {
61 		String contextId = getTableId(table.getId());
62 		if (!eclipseContext.containsKey(contextId)) {
63 			throw new IllegalArgumentException("Does not contains table " + contextId); //$NON-NLS-1$
64 		}
65 		eclipseContext.remove(contextId);
66 		final List<Context> contexts = definedTables.getContexts();
67 		if (contexts.contains(table.getTableId())) {
68 			// this is only valid because I'm throwing away the old definedTables contextSet
69 			contexts.remove(table.getTableId());
70 			definedTables = createContextSet(contexts);
71 		}
72 	}
73 
getTable(String id)74 	public BindingTable getTable(String id) {
75 		return (BindingTable) eclipseContext.get(getTableId(id));
76 	}
77 
78 	// we're just going through each binding table, and returning a
79 	// flat list of bindings here
getActiveBindings()80 	public Collection<Binding> getActiveBindings() {
81 		ArrayList<Binding> bindings = new ArrayList<>();
82 		for (Context ctx : definedTables.getContexts()) {
83 			BindingTable table = getTable(ctx.getId());
84 			if (table != null) {
85 				bindings.addAll(table.getBindings());
86 			}
87 		}
88 		return bindings;
89 	}
90 
createContextSet(Collection<Context> contexts)91 	public ContextSet createContextSet(Collection<Context> contexts) {
92 		return new ContextSet(contexts);
93 	}
94 
getConflictsFor(ContextSet contextSet, TriggerSequence triggerSequence)95 	public Collection<Binding> getConflictsFor(ContextSet contextSet,
96 			TriggerSequence triggerSequence) {
97 		Collection<Binding> matches = new ArrayList<>();
98 		for (Context ctx : contextSet.getContexts()) {
99 			BindingTable table = getTable(ctx.getId());
100 			if (table != null) {
101 				final Collection<Binding> matchesFor = table.getConflictsFor(triggerSequence);
102 				if (matchesFor != null) {
103 					matches.addAll(matchesFor);
104 				}
105 			}
106 		}
107 		return matches.isEmpty() ? null : matches;
108 	}
109 
getAllConflicts()110 	public Collection<Binding> getAllConflicts() {
111 		Collection<Binding> conflictsList = new ArrayList<>();
112 		for (Context ctx : definedTables.getContexts()) {
113 			BindingTable table = getTable(ctx.getId());
114 			if (table != null) {
115 				Collection<Binding> conflictsInTable = table.getConflicts();
116 				if (conflictsInTable != null) {
117 					conflictsList.addAll(conflictsInTable);
118 				}
119 			}
120 		}
121 		return conflictsList;
122 	}
123 
getPerfectMatch(ContextSet contextSet, TriggerSequence triggerSequence)124 	public Binding getPerfectMatch(ContextSet contextSet, TriggerSequence triggerSequence) {
125 		Binding result = null;
126 		Binding currentResult = null;
127 		List<Context> contexts = contextSet.getContexts();
128 		ListIterator<Context> it = contexts.listIterator(contexts.size());
129 		while (it.hasPrevious()) {
130 			Context c = it.previous();
131 			BindingTable table = getTable(c.getId());
132 			if (table != null) {
133 				currentResult = table.getPerfectMatch(triggerSequence);
134 			}
135 			if (currentResult != null) {
136 				if (isMostActiveScheme(currentResult)) {
137 					return currentResult;
138 				}
139 				if (result == null) {
140 					result = currentResult;
141 				} else {
142 					int rc = compareSchemes(result.getSchemeId(), currentResult.getSchemeId());
143 					if (rc > 0) {
144 						result = currentResult;
145 					}
146 				}
147 			}
148 		}
149 		return result;
150 	}
151 
152 	/**
153 	 * @param currentResult
154 	 * @return
155 	 */
isMostActiveScheme(Binding currentResult)156 	private boolean isMostActiveScheme(Binding currentResult) {
157 		if (activeSchemeIds == null || activeSchemeIds.length < 2) {
158 			return true;
159 		}
160 		final String mostActive = activeSchemeIds[0];
161 		return mostActive == null ? false : mostActive.equals(currentResult.getSchemeId());
162 	}
163 
getBestSequenceFor(ContextSet contextSet, ParameterizedCommand parameterizedCommand)164 	public Binding getBestSequenceFor(ContextSet contextSet,
165 			ParameterizedCommand parameterizedCommand) {
166 		ArrayList<Binding> bindings = (ArrayList<Binding>) getSequencesFor(contextSet,
167 				parameterizedCommand);
168 		if (bindings.isEmpty()) {
169 			return null;
170 		}
171 		return bindings.get(0);
172 	}
173 
getSequencesFor(ContextSet contextSet, ParameterizedCommand parameterizedCommand)174 	public Collection<Binding> getSequencesFor(ContextSet contextSet,
175 			ParameterizedCommand parameterizedCommand) {
176 		ArrayList<Binding> bindings = new ArrayList<>();
177 		List<Context> contexts = contextSet.getContexts();
178 		ListIterator<Context> it = contexts.listIterator(contexts.size());
179 		while (it.hasPrevious()) {
180 			Context c = it.previous();
181 			BindingTable table = getTable(c.getId());
182 			if (table != null) {
183 				Collection<Binding> sequences = table.getSequencesFor(parameterizedCommand);
184 				if (sequences != null) {
185 					bindings.addAll(sequences);
186 				}
187 			}
188 		}
189 		bindings.sort(BindingTable.BEST_SEQUENCE);
190 		return bindings;
191 	}
192 
getBindingsFor(ContextSet contextSet, ParameterizedCommand cmd)193 	public Collection<Binding> getBindingsFor(ContextSet contextSet, ParameterizedCommand cmd) {
194 		Collection<Binding> bindings = new ArrayList<>();
195 		for (Context ctx : contextSet.getContexts()) {
196 			BindingTable table = getTable(ctx.getId());
197 			if (table != null) {
198 				Collection<Binding> matches = table.getSequencesFor(cmd);
199 				if (matches != null) {
200 					bindings.addAll(matches);
201 				}
202 			}
203 		}
204 		return bindings;
205 	}
206 
isPartialMatch(ContextSet contextSet, TriggerSequence sequence)207 	public boolean isPartialMatch(ContextSet contextSet, TriggerSequence sequence) {
208 		List<Context> contexts = contextSet.getContexts();
209 		ListIterator<Context> it = contexts.listIterator(contexts.size());
210 		while (it.hasPrevious()) {
211 			Context c = it.previous();
212 			BindingTable table = getTable(c.getId());
213 			if (table != null) {
214 				if (table.isPartialMatch(sequence)) {
215 					return true;
216 				}
217 			}
218 		}
219 		return false;
220 	}
221 
getPartialMatches(ContextSet contextSet, TriggerSequence sequence)222 	public Collection<Binding> getPartialMatches(ContextSet contextSet, TriggerSequence sequence) {
223 		ArrayList<Binding> bindings = new ArrayList<>();
224 		List<Context> contexts = contextSet.getContexts();
225 		ListIterator<Context> it = contexts.listIterator(contexts.size());
226 		while (it.hasPrevious()) {
227 			Context c = it.previous();
228 			BindingTable table = getTable(c.getId());
229 			if (table != null) {
230 				Collection<Binding> partialMatches = table.getPartialMatches(sequence);
231 				if (partialMatches != null) {
232 					bindings.addAll(partialMatches);
233 				}
234 			}
235 		}
236 		return bindings;
237 	}
238 
239 	/**
240 	 * @param activeSchemeIds
241 	 */
setActiveSchemes(String[] activeSchemeIds)242 	public void setActiveSchemes(String[] activeSchemeIds) {
243 		this.activeSchemeIds = activeSchemeIds;
244 		BindingTable.BEST_SEQUENCE.setActiveSchemes(activeSchemeIds);
245 	}
246 
247 	/*
248 	 * Copied from org.eclipse.jface.bindings.BindingManager.compareSchemes(String, String)
249 	 *
250 	 * Returns an in based on scheme 1 < scheme 2
251 	 */
compareSchemes(final String schemeId1, final String schemeId2)252 	private final int compareSchemes(final String schemeId1, final String schemeId2) {
253 		if (activeSchemeIds == null) {
254 			return 0;
255 		}
256 		if (!schemeId2.equals(schemeId1)) {
257 			for (final String schemePointer : activeSchemeIds) {
258 				if (schemeId2.equals(schemePointer)) {
259 					return 1;
260 				} else if (schemeId1.equals(schemePointer)) {
261 					return -1;
262 				}
263 			}
264 		}
265 		return 0;
266 	}
267 }
268