Mercurial > projects > dwt-addons
comparison dwtx/jface/bindings/BindingManager.d @ 16:e0f0aaf75edd
PopupDialog, bindings and actions
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Tue, 01 Apr 2008 08:00:31 +0200 |
parents | |
children | 6d1acb32839d |
comparison
equal
deleted
inserted
replaced
15:db8940420ed8 | 16:e0f0aaf75edd |
---|---|
1 /******************************************************************************* | |
2 * Copyright (c) 2004, 2007 IBM Corporation and others. | |
3 * All rights reserved. This program and the accompanying materials | |
4 * are made available under the terms of the Eclipse Public License v1.0 | |
5 * which accompanies this distribution, and is available at | |
6 * http://www.eclipse.org/legal/epl-v10.html | |
7 * | |
8 * Contributors: | |
9 * IBM Corporation - initial API and implementation | |
10 * Port to the D programming language: | |
11 * Frank Benoit <benoit@tionex.de> | |
12 *******************************************************************************/ | |
13 module dwtx.jface.bindings.BindingManager; | |
14 | |
15 import dwtx.jface.bindings.Binding; | |
16 import dwtx.jface.bindings.BindingManagerEvent; | |
17 import dwtx.jface.bindings.CachedBindingSet; | |
18 import dwtx.jface.bindings.IBindingManagerListener; | |
19 import dwtx.jface.bindings.ISchemeListener; | |
20 import dwtx.jface.bindings.Scheme; | |
21 import dwtx.jface.bindings.SchemeEvent; | |
22 import dwtx.jface.bindings.Trigger; | |
23 import dwtx.jface.bindings.TriggerSequence; | |
24 | |
25 // import java.io.BufferedWriter; | |
26 // import java.io.IOException; | |
27 // import java.io.StringWriter; | |
28 import tango.util.collection.HashMap; | |
29 import tango.util.collection.HashSet; | |
30 import tango.util.collection.ArraySeq; | |
31 import tango.util.collection.LinkSeq; | |
32 import tango.util.collection.model.Seq; | |
33 import tango.util.collection.model.Map; | |
34 import tango.util.collection.model.Set; | |
35 import tango.util.collection.model.View; | |
36 | |
37 import dwt.DWT; | |
38 import dwtx.core.commands.CommandManager; | |
39 import dwtx.core.commands.ParameterizedCommand; | |
40 import dwtx.core.commands.common.HandleObjectManager; | |
41 import dwtx.core.commands.common.NotDefinedException; | |
42 import dwtx.core.commands.contexts.Context; | |
43 import dwtx.core.commands.contexts.ContextManager; | |
44 import dwtx.core.commands.contexts.ContextManagerEvent; | |
45 import dwtx.core.commands.contexts.IContextManagerListener; | |
46 import dwtx.core.commands.util.Tracing; | |
47 import dwtx.core.runtime.IStatus; | |
48 import dwtx.core.runtime.MultiStatus; | |
49 import dwtx.core.runtime.Status; | |
50 import dwtx.jface.bindings.keys.IKeyLookup; | |
51 import dwtx.jface.bindings.keys.KeyLookupFactory; | |
52 import dwtx.jface.bindings.keys.KeyStroke; | |
53 import dwtx.jface.contexts.IContextIds; | |
54 import dwtx.jface.internal.InternalPolicy; | |
55 import dwtx.jface.util.Policy; | |
56 import dwtx.jface.util.Util; | |
57 | |
58 import dwt.dwthelper.utils; | |
59 static import tango.text.Text; | |
60 alias tango.text.Text.Text!(char) StringBuffer; | |
61 import tango.text.convert.Format; | |
62 import tango.text.locale.Core; | |
63 | |
64 /** | |
65 * <p> | |
66 * A central repository for bindings -- both in the defined and undefined | |
67 * states. Schemes and bindings can be created and retrieved using this manager. | |
68 * It is possible to listen to changes in the collection of schemes and bindings | |
69 * by adding a listener to the manager. | |
70 * </p> | |
71 * <p> | |
72 * The binding manager is very sensitive to performance. Misusing the manager | |
73 * can render an application unenjoyable to use. As such, each of the public | |
74 * methods states the current run-time performance. In future releases, it is | |
75 * guaranteed that the method will run in at least the stated time constraint -- | |
76 * though it might get faster. Where possible, we have also tried to be memory | |
77 * efficient. | |
78 * </p> | |
79 * | |
80 * @since 3.1 | |
81 */ | |
82 public final class BindingManager : HandleObjectManager, | |
83 IContextManagerListener, ISchemeListener { | |
84 | |
85 private static Map!(Object,Object) EMPTY_MAP; | |
86 /** | |
87 * This flag can be set to <code>true</code> if the binding manager should | |
88 * print information to <code>System.out</code> when certain boundary | |
89 * conditions occur. | |
90 */ | |
91 public static bool DEBUG = false; | |
92 | |
93 /** | |
94 * Returned for optimized lookup. | |
95 */ | |
96 private static const TriggerSequence[] EMPTY_TRIGGER_SEQUENCE = null; | |
97 | |
98 /** | |
99 * The separator character used in locales. | |
100 */ | |
101 private static const String LOCALE_SEPARATOR = "_"; //$NON-NLS-1$ | |
102 | |
103 /** | |
104 * </p> | |
105 * A utility method for adding entries to a map. The map is checked for | |
106 * entries at the key. If such an entry exists, it is expected to be a | |
107 * <code>Seq!(Object)</code>. The value is then appended to the collection. | |
108 * If no such entry exists, then a collection is created, and the value | |
109 * added to the collection. | |
110 * </p> | |
111 * | |
112 * @param map | |
113 * The map to modify; if this value is <code>null</code>, then | |
114 * this method simply returns. | |
115 * @param key | |
116 * The key to look up in the map; may be <code>null</code>. | |
117 * @param value | |
118 * The value to look up in the map; may be <code>null</code>. | |
119 */ | |
120 private static final void addReverseLookup(Map!(Object,Object) map, Object key, | |
121 Object value) { | |
122 if (map is null) { | |
123 return; | |
124 } | |
125 | |
126 Object currentValue = map.get(key); | |
127 if (currentValue !is null) { | |
128 auto values = cast(Seq!(Object)) currentValue; | |
129 values.append(value); | |
130 } else { // currentValue is null | |
131 auto values = new ArraySeq!(Object); | |
132 values.append(value); | |
133 map.add(key, values); | |
134 } | |
135 } | |
136 | |
137 /** | |
138 * <p> | |
139 * Takes a fully-specified string, and converts it into an array of | |
140 * increasingly less-specific strings. So, for example, "en_GB" would become | |
141 * ["en_GB", "en", "", null]. | |
142 * </p> | |
143 * <p> | |
144 * This method runs in linear time (O(n)) over the length of the string. | |
145 * </p> | |
146 * | |
147 * @param string | |
148 * The string to break apart into its less specific components; | |
149 * should not be <code>null</code>. | |
150 * @param separator | |
151 * The separator that indicates a separation between a degrees of | |
152 * specificity; should not be <code>null</code>. | |
153 * @return An array of strings from the most specific (i.e., | |
154 * <code>string</code>) to the least specific (i.e., | |
155 * <code>null</code>). | |
156 */ | |
157 private static final String[] expand(String string, String separator) { | |
158 // Test for boundary conditions. | |
159 if (string is null || separator is null) { | |
160 return new String[0]; | |
161 } | |
162 | |
163 auto strings = new LinkSeq!(String); | |
164 auto stringBuffer = new StringBuffer(); | |
165 string = string.trim(); // remove whitespace | |
166 if (string.length > 0) { | |
167 | |
168 auto tokens = tango.text.Util.delimit(string, separator); | |
169 foreach( tok; tokens ){ | |
170 if (stringBuffer.length() > 0) { | |
171 stringBuffer.append(separator); | |
172 } | |
173 stringBuffer.append(tok.trim()); | |
174 strings.prepend(stringBuffer.toString()); | |
175 } | |
176 } | |
177 strings.append(Util.ZERO_LENGTH_STRING); | |
178 strings.append(cast(char[])null); | |
179 return strings.toArray(); | |
180 } | |
181 | |
182 /** | |
183 * The active bindings. This is a map of triggers ( | |
184 * <code>TriggerSequence</code>) to bindings (<code>Binding</code>). | |
185 * This value will only be <code>null</code> if the active bindings have | |
186 * not yet been computed. Otherwise, this value may be empty. | |
187 */ | |
188 private Map!(Object,Object) activeBindings = null; | |
189 | |
190 /** | |
191 * The active bindings indexed by fully-parameterized commands. This is a | |
192 * map of fully-parameterized commands (<code>ParameterizedCommand</code>) | |
193 * to triggers ( <code>TriggerSequence</code>). This value will only be | |
194 * <code>null</code> if the active bindings have not yet been computed. | |
195 * Otherwise, this value may be empty. | |
196 */ | |
197 private Map!(Object,Object) activeBindingsByParameterizedCommand = null; | |
198 | |
199 private Set!(Object) triggerConflicts; | |
200 | |
201 /** | |
202 * The scheme that is currently active. An active scheme is the one that is | |
203 * currently dictating which bindings will actually work. This value may be | |
204 * <code>null</code> if there is no active scheme. If the active scheme | |
205 * becomes undefined, then this should automatically revert to | |
206 * <code>null</code>. | |
207 */ | |
208 private Scheme activeScheme = null; | |
209 | |
210 /** | |
211 * The array of scheme identifiers, starting with the active scheme and | |
212 * moving up through its parents. This value may be <code>null</code> if | |
213 * there is no active scheme. | |
214 */ | |
215 private String[] activeSchemeIds = null; | |
216 | |
217 /** | |
218 * The number of bindings in the <code>bindings</code> array. | |
219 */ | |
220 private int bindingCount = 0; | |
221 | |
222 /** | |
223 * A cache of context IDs that weren't defined. | |
224 */ | |
225 private Set!(Object) bindingErrors; | |
226 | |
227 /** | |
228 * The array of all bindings currently handled by this manager. This array | |
229 * is the raw list of bindings, as provided to this manager. This value may | |
230 * be <code>null</code> if there are no bindings. The size of this array | |
231 * is not necessarily the number of bindings. | |
232 */ | |
233 private Binding[] bindings = null; | |
234 | |
235 /** | |
236 * A cache of the bindings previously computed by this manager. This value | |
237 * may be empty, but it is never <code>null</code>. This is a map of | |
238 * <code>CachedBindingSet</code> to <code>CachedBindingSet</code>. | |
239 */ | |
240 private Map!(Object,Object) cachedBindings; | |
241 | |
242 /** | |
243 * The command manager for this binding manager. This manager is only needed | |
244 * for the <code>getActiveBindingsFor(String)</code> method. This value is | |
245 * guaranteed to never be <code>null</code>. | |
246 */ | |
247 private const CommandManager commandManager; | |
248 | |
249 /** | |
250 * The context manager for this binding manager. For a binding manager to | |
251 * function, it needs to listen for changes to the contexts. This value is | |
252 * guaranteed to never be <code>null</code>. | |
253 */ | |
254 private const ContextManager contextManager; | |
255 | |
256 /** | |
257 * The locale for this manager. This defaults to the current locale. The | |
258 * value will never be <code>null</code>. | |
259 */ | |
260 private String locale; | |
261 | |
262 /** | |
263 * The array of locales, starting with the active locale and moving up | |
264 * through less specific representations of the locale. For example, | |
265 * ["en_US", "en", "", null]. This value will never be <code>null</code>. | |
266 */ | |
267 private String[] locales; | |
268 | |
269 /** | |
270 * The platform for this manager. This defaults to the current platform. The | |
271 * value will never be <code>null</code>. | |
272 */ | |
273 private String platform; | |
274 | |
275 /** | |
276 * The array of platforms, starting with the active platform and moving up | |
277 * through less specific representations of the platform. For example, | |
278 * ["gtk", "", null]. This value will never be <code>null,/code>. | |
279 */ | |
280 private String[] platforms; | |
281 | |
282 /** | |
283 * A map of prefixes (<code>TriggerSequence</code>) to a map of | |
284 * available completions (possibly <code>null</code>, which means there | |
285 * is an exact match). The available completions is a map of trigger (<code>TriggerSequence</code>) | |
286 * to bindings (<code>Binding</code>). This value may be | |
287 * <code>null</code> if there is no existing solution. | |
288 */ | |
289 private Map!(Object,Object) prefixTable = null; | |
290 | |
291 /** | |
292 * <p> | |
293 * Constructs a new instance of <code>BindingManager</code>. | |
294 * </p> | |
295 * <p> | |
296 * This method completes in amortized constant time (O(1)). | |
297 * </p> | |
298 * | |
299 * @param contextManager | |
300 * The context manager that will support this binding manager. | |
301 * This value must not be <code>null</code>. | |
302 * @param commandManager | |
303 * The command manager that will support this binding manager. | |
304 * This value must not be <code>null</code>. | |
305 */ | |
306 public this(ContextManager contextManager, | |
307 CommandManager commandManager) { | |
308 triggerConflicts = new HashSet!(Object); | |
309 bindingErrors = new HashSet!(Object); | |
310 cachedBindings = new HashMap!(Object,Object); | |
311 locale = /+Locale+/Culture.current().toString(); | |
312 locales = expand(locale, LOCALE_SEPARATOR); | |
313 platform = DWT.getPlatform(); | |
314 platforms = expand(platform, Util.ZERO_LENGTH_STRING); | |
315 if (contextManager is null) { | |
316 throw new NullPointerException( | |
317 "A binding manager requires a context manager"); //$NON-NLS-1$ | |
318 } | |
319 | |
320 if (commandManager is null) { | |
321 throw new NullPointerException( | |
322 "A binding manager requires a command manager"); //$NON-NLS-1$ | |
323 } | |
324 | |
325 this.contextManager = contextManager; | |
326 contextManager.addContextManagerListener(this); | |
327 this.commandManager = commandManager; | |
328 } | |
329 | |
330 /** | |
331 * <p> | |
332 * Adds a single new binding to the existing array of bindings. If the array | |
333 * is currently <code>null</code>, then a new array is created and this | |
334 * binding is added to it. This method does not detect duplicates. | |
335 * </p> | |
336 * <p> | |
337 * This method completes in amortized <code>O(1)</code>. | |
338 * </p> | |
339 * | |
340 * @param binding | |
341 * The binding to be added; must not be <code>null</code>. | |
342 */ | |
343 public final void addBinding(Binding binding) { | |
344 if (binding is null) { | |
345 throw new NullPointerException("Cannot add a null binding"); //$NON-NLS-1$ | |
346 } | |
347 | |
348 if (bindings is null) { | |
349 bindings = new Binding[1]; | |
350 } else if (bindingCount >= bindings.length) { | |
351 Binding[] oldBindings = bindings; | |
352 bindings = new Binding[oldBindings.length * 2]; | |
353 System.arraycopy(oldBindings, 0, bindings, 0, oldBindings.length); | |
354 } | |
355 bindings[bindingCount++] = binding; | |
356 clearCache(); | |
357 } | |
358 | |
359 /** | |
360 * <p> | |
361 * Adds a listener to this binding manager. The listener will be notified | |
362 * when the set of defined schemes or bindings changes. This can be used to | |
363 * track the global appearance and disappearance of bindings. | |
364 * </p> | |
365 * <p> | |
366 * This method completes in amortized constant time (<code>O(1)</code>). | |
367 * </p> | |
368 * | |
369 * @param listener | |
370 * The listener to attach; must not be <code>null</code>. | |
371 */ | |
372 public final void addBindingManagerListener( | |
373 IBindingManagerListener listener) { | |
374 addListenerObject(cast(Object)listener); | |
375 } | |
376 | |
377 /** | |
378 * <p> | |
379 * Builds a prefix table look-up for a map of active bindings. | |
380 * </p> | |
381 * <p> | |
382 * This method takes <code>O(mn)</code>, where <code>m</code> is the | |
383 * length of the trigger sequences and <code>n</code> is the number of | |
384 * bindings. | |
385 * </p> | |
386 * | |
387 * @param activeBindings | |
388 * The map of triggers (<code>TriggerSequence</code>) to | |
389 * command ids (<code>String</code>) which are currently | |
390 * active. This value may be <code>null</code> if there are no | |
391 * active bindings, and it may be empty. It must not be | |
392 * <code>null</code>. | |
393 * @return A map of prefixes (<code>TriggerSequence</code>) to a map of | |
394 * available completions (possibly <code>null</code>, which means | |
395 * there is an exact match). The available completions is a map of | |
396 * trigger (<code>TriggerSequence</code>) to command identifier (<code>String</code>). | |
397 * This value will never be <code>null</code>, but may be empty. | |
398 */ | |
399 private final Map!(Object,Object) buildPrefixTable(Map!(Object,Object) activeBindings) { | |
400 auto prefixTable = new HashMap!(Object,Object); | |
401 foreach( k, v; activeBindings ){ | |
402 TriggerSequence triggerSequence = cast(TriggerSequence)k; | |
403 | |
404 // Add the perfect match. | |
405 if (!prefixTable.containsKey(triggerSequence)) { | |
406 prefixTable.add(triggerSequence, null); | |
407 } | |
408 | |
409 TriggerSequence[] prefixes = triggerSequence.getPrefixes(); | |
410 int prefixesLength = prefixes.length; | |
411 if (prefixesLength is 0) { | |
412 continue; | |
413 } | |
414 | |
415 // Break apart the trigger sequence. | |
416 Binding binding = cast(Binding) v; | |
417 for (int i = 0; i < prefixesLength; i++) { | |
418 TriggerSequence prefix = prefixes[i]; | |
419 Object value = prefixTable.get(prefix); | |
420 if ((prefixTable.containsKey(prefix)) && (cast(Map!(Object,Object))value )) { | |
421 (cast(Map!(Object,Object)) value).add(triggerSequence, binding); | |
422 } else { | |
423 auto map = new HashMap!(Object,Object); | |
424 prefixTable.add(prefix, map); | |
425 map.add(triggerSequence, binding); | |
426 } | |
427 } | |
428 } | |
429 | |
430 return prefixTable; | |
431 } | |
432 | |
433 /** | |
434 * <p> | |
435 * Clears the cache, and the existing solution. If debugging is turned on, | |
436 * then this will also print a message to standard out. | |
437 * </p> | |
438 * <p> | |
439 * This method completes in <code>O(1)</code>. | |
440 * </p> | |
441 */ | |
442 private final void clearCache() { | |
443 if (DEBUG) { | |
444 Tracing.printTrace("BINDINGS", "Clearing cache"); //$NON-NLS-1$ //$NON-NLS-2$ | |
445 } | |
446 cachedBindings.clear(); | |
447 clearSolution(); | |
448 } | |
449 | |
450 /** | |
451 * <p> | |
452 * Clears the existing solution. | |
453 * </p> | |
454 * <p> | |
455 * This method completes in <code>O(1)</code>. | |
456 */ | |
457 private final void clearSolution() { | |
458 setActiveBindings(null, null, null, null); | |
459 } | |
460 | |
461 /** | |
462 * Compares the identifier of two schemes, and decides which scheme is the | |
463 * youngest (i.e., the child) of the two. Both schemes should be active | |
464 * schemes. | |
465 * | |
466 * @param schemeId1 | |
467 * The identifier of the first scheme; must not be | |
468 * <code>null</code>. | |
469 * @param schemeId2 | |
470 * The identifier of the second scheme; must not be | |
471 * <code>null</code>. | |
472 * @return <code>0</code> if the two schemes are equal of if neither | |
473 * scheme is active; <code>1</code> if the second scheme is the | |
474 * youngest; and <code>-1</code> if the first scheme is the | |
475 * youngest. | |
476 * @since 3.2 | |
477 */ | |
478 private final int compareSchemes(String schemeId1, | |
479 String schemeId2) { | |
480 if (!schemeId2.equals(schemeId1)) { | |
481 for (int i = 0; i < activeSchemeIds.length; i++) { | |
482 String schemePointer = activeSchemeIds[i]; | |
483 if (schemeId2.equals(schemePointer)) { | |
484 return 1; | |
485 | |
486 } else if (schemeId1.equals(schemePointer)) { | |
487 return -1; | |
488 | |
489 } | |
490 | |
491 } | |
492 } | |
493 | |
494 return 0; | |
495 } | |
496 | |
497 /** | |
498 * <p> | |
499 * Computes the bindings given the context tree, and inserts them into the | |
500 * <code>commandIdsByTrigger</code>. It is assumed that | |
501 * <code>locales</code>,<code>platforsm</code> and | |
502 * <code>schemeIds</code> correctly reflect the state of the application. | |
503 * This method does not deal with caching. | |
504 * </p> | |
505 * <p> | |
506 * This method completes in <code>O(n)</code>, where <code>n</code> is | |
507 * the number of bindings. | |
508 * </p> | |
509 * | |
510 * @param activeContextTree | |
511 * The map representing the tree of active contexts. The map is | |
512 * one of child to parent, each being a context id ( | |
513 * <code>String</code>). The keys are never <code>null</code>, | |
514 * but the values may be (i.e., no parent). This map may be | |
515 * empty. It may be <code>null</code> if we shouldn't consider | |
516 * contexts. | |
517 * @param bindingsByTrigger | |
518 * The empty of map that is intended to be filled with triggers ( | |
519 * <code>TriggerSequence</code>) to bindings ( | |
520 * <code>Binding</code>). This value must not be | |
521 * <code>null</code> and must be empty. | |
522 * @param triggersByCommandId | |
523 * The empty of map that is intended to be filled with command | |
524 * identifiers (<code>String</code>) to triggers ( | |
525 * <code>TriggerSequence</code>). This value must either be | |
526 * <code>null</code> (indicating that these values are not | |
527 * needed), or empty (indicating that this map should be | |
528 * computed). | |
529 */ | |
530 private final void computeBindings(Map!(Object,Object) activeContextTree, | |
531 Map!(Object,Object) bindingsByTrigger, Map!(Object,Object) triggersByCommandId, | |
532 Map!(Object,Object) conflictsByTrigger) { | |
533 /* | |
534 * FIRST PASS: Remove all of the bindings that are marking deletions. | |
535 */ | |
536 Binding[] trimmedBindings = removeDeletions(bindings); | |
537 | |
538 /* | |
539 * SECOND PASS: Just throw in bindings that match the current state. If | |
540 * there is more than one match for a binding, then create a list. | |
541 */ | |
542 auto possibleBindings = new HashMap!(Object,Object); | |
543 int length = trimmedBindings.length; | |
544 for (int i = 0; i < length; i++) { | |
545 Binding binding = trimmedBindings[i]; | |
546 bool found; | |
547 | |
548 // Check the context. | |
549 String contextId = binding.getContextId(); | |
550 if ((activeContextTree !is null) | |
551 && (!activeContextTree.containsKey( new ArrayWrapperString(contextId)))) { | |
552 continue; | |
553 } | |
554 | |
555 // Check the locale. | |
556 if (!localeMatches(binding)) { | |
557 continue; | |
558 } | |
559 | |
560 // Check the platform. | |
561 if (!platformMatches(binding)) { | |
562 continue; | |
563 } | |
564 | |
565 // Check the scheme ids. | |
566 String schemeId = binding.getSchemeId(); | |
567 found = false; | |
568 if (activeSchemeIds !is null) { | |
569 for (int j = 0; j < activeSchemeIds.length; j++) { | |
570 if (Util.opEquals(schemeId, activeSchemeIds[j])) { | |
571 found = true; | |
572 break; | |
573 } | |
574 } | |
575 } | |
576 if (!found) { | |
577 continue; | |
578 } | |
579 | |
580 // Insert the match into the list of possible matches. | |
581 TriggerSequence trigger = binding.getTriggerSequence(); | |
582 Object existingMatch = possibleBindings.get(trigger); | |
583 if (cast(Binding)existingMatch ) { | |
584 possibleBindings.remove(trigger); | |
585 auto matches = new ArraySeq!(Object); | |
586 matches.append(existingMatch); | |
587 matches.append(binding); | |
588 possibleBindings.add(trigger, matches); | |
589 | |
590 } else if (cast(Seq!(Object))existingMatch ) { | |
591 auto matches = cast(Seq!(Object)) existingMatch; | |
592 matches.append(binding); | |
593 | |
594 } else { | |
595 possibleBindings.add(trigger, binding); | |
596 } | |
597 } | |
598 | |
599 MultiStatus conflicts = new MultiStatus("dwtx.jface", 0, //$NON-NLS-1$ | |
600 "Keybinding conflicts occurred. They may interfere with normal accelerator operation.", //$NON-NLS-1$ | |
601 null); | |
602 /* | |
603 * THIRD PASS: In this pass, we move any non-conflicting bindings | |
604 * directly into the map. In the case of conflicts, we apply some | |
605 * further logic to try to resolve them. If the conflict can't be | |
606 * resolved, then we log the problem. | |
607 */ | |
608 foreach( k,v; possibleBindings ){ | |
609 // Iterator possibleBindingItr = possibleBindings.entrySet() | |
610 // .iterator(); | |
611 // while (possibleBindingItr.hasNext()) { | |
612 // Map.Entry entry = cast(Map.Entry) possibleBindingItr.next(); | |
613 TriggerSequence trigger = cast(TriggerSequence) k;//entry.getKey(); | |
614 Object match = v;//entry.getValue(); | |
615 /* | |
616 * What we do depends slightly on whether we are trying to build a | |
617 * list of all possible bindings (disregarding context), or a flat | |
618 * map given the currently active contexts. | |
619 */ | |
620 if (activeContextTree is null) { | |
621 // We are building the list of all possible bindings. | |
622 auto bindings = new ArraySeq!(Object); | |
623 if (cast(Binding)match ) { | |
624 bindings.append(match); | |
625 bindingsByTrigger.add(trigger, bindings); | |
626 addReverseLookup(triggersByCommandId, (cast(Binding) match) | |
627 .getParameterizedCommand(), trigger); | |
628 | |
629 } else if (cast(View!(Object))match ) { | |
630 bindings.append( (cast(View!(Object)) match).elements ); | |
631 bindingsByTrigger.add(trigger, bindings); | |
632 | |
633 foreach( e; bindings ){ | |
634 // Iterator matchItr = bindings.iterator(); | |
635 // while (matchItr.hasNext()) { | |
636 addReverseLookup(triggersByCommandId, | |
637 (cast(Binding) e) | |
638 .getParameterizedCommand(), trigger); | |
639 } | |
640 } | |
641 | |
642 } else { | |
643 // We are building the flat map of trigger to commands. | |
644 if (cast(Binding)match ) { | |
645 Binding binding = cast(Binding) match; | |
646 bindingsByTrigger.add(trigger, binding); | |
647 addReverseLookup(triggersByCommandId, binding | |
648 .getParameterizedCommand(), trigger); | |
649 | |
650 } else if (cast(Seq!(Object))match ) { | |
651 Binding winner = resolveConflicts(cast(Seq!(Object)) match, | |
652 activeContextTree); | |
653 if (winner is null) { | |
654 // warn once ... so as not to flood the logs | |
655 conflictsByTrigger.add(trigger, match); | |
656 if (!triggerConflicts.contains(trigger)) { | |
657 triggerConflicts.add(trigger); | |
658 // StringWriter sw = new StringWriter(); | |
659 // BufferedWriter buffer = new BufferedWriter(sw); | |
660 StringBuffer sb = new StringBuffer(); | |
661 try { | |
662 sb.append("A conflict occurred for "); //$NON-NLS-1$ | |
663 sb.append(trigger.toString()); | |
664 sb.append(':'); | |
665 foreach( e; cast(Seq!(Object)) match){ | |
666 // Iterator i = (cast(Seq!(Object)) match).iterator(); | |
667 // while (i.hasNext()) { | |
668 sb.append('\n'); | |
669 sb.append( e.toString() ); | |
670 } | |
671 } catch (IOException e) { | |
672 // we should not get this | |
673 } | |
674 conflicts.add(new Status(IStatus.WARNING, | |
675 "dwtx.jface", //$NON-NLS-1$ | |
676 sb.toString())); | |
677 } | |
678 if (DEBUG) { | |
679 Tracing.printTrace("BINDINGS", //$NON-NLS-1$ | |
680 "A conflict occurred for " ~ trigger.toString); //$NON-NLS-1$ | |
681 Tracing.printTrace("BINDINGS", " " ~ match.toString); //$NON-NLS-1$ //$NON-NLS-2$ | |
682 } | |
683 } else { | |
684 bindingsByTrigger.add(trigger, winner); | |
685 addReverseLookup(triggersByCommandId, winner | |
686 .getParameterizedCommand(), trigger); | |
687 } | |
688 } | |
689 } | |
690 } | |
691 if (conflicts.getSeverity() !is IStatus.OK) { | |
692 Policy.getLog().log(conflicts); | |
693 } | |
694 } | |
695 | |
696 /** | |
697 * <p> | |
698 * Notifies this manager that the context manager has changed. This method | |
699 * is intended for internal use only. | |
700 * </p> | |
701 * <p> | |
702 * This method completes in <code>O(1)</code>. | |
703 * </p> | |
704 */ | |
705 public final void contextManagerChanged( | |
706 ContextManagerEvent contextManagerEvent) { | |
707 if (contextManagerEvent.isActiveContextsChanged()) { | |
708 // clearSolution(); | |
709 recomputeBindings(); | |
710 } | |
711 } | |
712 | |
713 /** | |
714 * Returns the number of strokes in an array of triggers. It is assumed that | |
715 * there is one natural key per trigger. The strokes are counted based on | |
716 * the type of key. Natural keys are worth one; ctrl is worth two; shift is | |
717 * worth four; and alt is worth eight. | |
718 * | |
719 * @param triggers | |
720 * The triggers on which to count strokes; must not be | |
721 * <code>null</code>. | |
722 * @return The value of the strokes in the triggers. | |
723 * @since 3.2 | |
724 */ | |
725 private final int countStrokes(Trigger[] triggers) { | |
726 int strokeCount = triggers.length; | |
727 for (int i = 0; i < triggers.length; i++) { | |
728 Trigger trigger = triggers[i]; | |
729 if (cast(KeyStroke)trigger ) { | |
730 KeyStroke keyStroke = cast(KeyStroke) trigger; | |
731 int modifierKeys = keyStroke.getModifierKeys(); | |
732 IKeyLookup lookup = KeyLookupFactory.getDefault(); | |
733 if ((modifierKeys & lookup.getAlt()) !is 0) { | |
734 strokeCount += 8; | |
735 } | |
736 if ((modifierKeys & lookup.getCtrl()) !is 0) { | |
737 strokeCount += 2; | |
738 } | |
739 if ((modifierKeys & lookup.getShift()) !is 0) { | |
740 strokeCount += 4; | |
741 } | |
742 if ((modifierKeys & lookup.getCommand()) !is 0) { | |
743 strokeCount += 2; | |
744 } | |
745 } else { | |
746 strokeCount += 99; | |
747 } | |
748 } | |
749 | |
750 return strokeCount; | |
751 } | |
752 | |
753 /** | |
754 * <p> | |
755 * Creates a tree of context identifiers, representing the hierarchical | |
756 * structure of the given contexts. The tree is structured as a mapping from | |
757 * child to parent. | |
758 * </p> | |
759 * <p> | |
760 * This method completes in <code>O(n)</code>, where <code>n</code> is | |
761 * the height of the context tree. | |
762 * </p> | |
763 * | |
764 * @param contextIds | |
765 * The set of context identifiers to be converted into a tree; | |
766 * must not be <code>null</code>. | |
767 * @return The tree of contexts to use; may be empty, but never | |
768 * <code>null</code>. The keys and values are both strings. | |
769 */ | |
770 private final Map!(Object,Object) createContextTreeFor(Set!(Object) contextIds) { | |
771 auto contextTree = new HashMap!(Object,Object); | |
772 | |
773 foreach( e; contextIds ){ | |
774 auto childContextId = (cast(ArrayWrapperString)e).array; | |
775 while (childContextId !is null) { | |
776 // Check if we've already got the part of the tree from here up. | |
777 if (contextTree.containsKey(/+childContextId+/e)) { | |
778 break; | |
779 } | |
780 | |
781 // Retrieve the context. | |
782 Context childContext = contextManager | |
783 .getContext(childContextId); | |
784 | |
785 // Add the child-parent pair to the tree. | |
786 try { | |
787 String parentContextId = childContext.getParentId(); | |
788 contextTree.add(new ArrayWrapperString(childContextId), new ArrayWrapperString(parentContextId)); | |
789 childContextId = parentContextId; | |
790 } catch (NotDefinedException e) { | |
791 break; // stop ascending | |
792 } | |
793 } | |
794 } | |
795 | |
796 return contextTree; | |
797 } | |
798 | |
799 /** | |
800 * <p> | |
801 * Creates a tree of context identifiers, representing the hierarchical | |
802 * structure of the given contexts. The tree is structured as a mapping from | |
803 * child to parent. In this tree, the key binding specific filtering of | |
804 * contexts will have taken place. | |
805 * </p> | |
806 * <p> | |
807 * This method completes in <code>O(n^2)</code>, where <code>n</code> | |
808 * is the height of the context tree. | |
809 * </p> | |
810 * | |
811 * @param contextIds | |
812 * The set of context identifiers to be converted into a tree; | |
813 * must not be <code>null</code>. | |
814 * @return The tree of contexts to use; may be empty, but never | |
815 * <code>null</code>. The keys and values are both strings. | |
816 */ | |
817 private final Map!(Object,Object) createFilteredContextTreeFor(Set!(Object) contextIds) { | |
818 // Check to see whether a dialog or window is active. | |
819 bool dialog = false; | |
820 bool window = false; | |
821 foreach( e; contextIds ){ | |
822 String contextId = (cast(ArrayWrapperString) e).array; | |
823 if (IContextIds.CONTEXT_ID_DIALOG.equals(contextId)) { | |
824 dialog = true; | |
825 continue; | |
826 } | |
827 if (IContextIds.CONTEXT_ID_WINDOW.equals(contextId)) { | |
828 window = true; | |
829 continue; | |
830 } | |
831 } | |
832 | |
833 /* | |
834 * Remove all context identifiers for contexts whose parents are dialog | |
835 * or window, and the corresponding dialog or window context is not | |
836 * active. | |
837 */ | |
838 foreach( e; contextIds.dup ){ | |
839 String contextId = (cast(ArrayWrapperString) e).array; | |
840 Context context = contextManager.getContext(contextId); | |
841 try { | |
842 String parentId = context.getParentId(); | |
843 while (parentId !is null) { | |
844 if (IContextIds.CONTEXT_ID_DIALOG.equals(parentId)) { | |
845 if (!dialog) { | |
846 contextIds.remove(e); | |
847 // contextIdItr.remove(); | |
848 } | |
849 break; | |
850 } | |
851 if (IContextIds.CONTEXT_ID_WINDOW.equals(parentId)) { | |
852 if (!window) { | |
853 contextIds.remove(e); | |
854 // contextIdItr.remove(); | |
855 } | |
856 break; | |
857 } | |
858 if (IContextIds.CONTEXT_ID_DIALOG_AND_WINDOW | |
859 .equals(parentId)) { | |
860 if ((!window) && (!dialog)) { | |
861 contextIds.remove(e); | |
862 // contextIdItr.remove(); | |
863 } | |
864 break; | |
865 } | |
866 | |
867 context = contextManager.getContext(parentId); | |
868 parentId = context.getParentId(); | |
869 } | |
870 } catch (NotDefinedException e) { | |
871 // since this context was part of an undefined hierarchy, | |
872 // I'm going to yank it out as a bad bet | |
873 contextIds.remove(e); | |
874 // contextIdItr.remove(); | |
875 | |
876 // This is a logging optimization, only log the error once. | |
877 if (context is null || !bindingErrors.contains(new ArrayWrapperString(context.getId()))) { | |
878 if (context !is null) { | |
879 bindingErrors.add(new ArrayWrapperString(context.getId())); | |
880 } | |
881 | |
882 // now log like you've never logged before! | |
883 Policy.getLog().log(new Status( IStatus.ERROR, Policy.JFACE, IStatus.OK, | |
884 "Undefined context while filtering dialog/window contexts", //$NON-NLS-1$ | |
885 e)); | |
886 } | |
887 } | |
888 } | |
889 | |
890 return createContextTreeFor(contextIds); | |
891 } | |
892 | |
893 /** | |
894 * <p> | |
895 * Notifies all of the listeners to this manager that the defined or active | |
896 * schemes of bindings have changed. | |
897 * </p> | |
898 * <p> | |
899 * The time this method takes to complete is dependent on external | |
900 * listeners. | |
901 * </p> | |
902 * | |
903 * @param event | |
904 * The event to send to all of the listeners; must not be | |
905 * <code>null</code>. | |
906 */ | |
907 private final void fireBindingManagerChanged(BindingManagerEvent event) { | |
908 if (event is null) { | |
909 throw new NullPointerException(); | |
910 } | |
911 | |
912 Object[] listeners = getListeners(); | |
913 for (int i = 0; i < listeners.length; i++) { | |
914 IBindingManagerListener listener = cast(IBindingManagerListener) listeners[i]; | |
915 listener.bindingManagerChanged(event); | |
916 } | |
917 } | |
918 | |
919 /** | |
920 * <p> | |
921 * Returns the active bindings. The caller must not modify the returned map. | |
922 * </p> | |
923 * <p> | |
924 * This method completes in <code>O(1)</code>. If the active bindings are | |
925 * not yet computed, then this completes in <code>O(nn)</code>, where | |
926 * <code>n</code> is the number of bindings. | |
927 * </p> | |
928 * | |
929 * @return The map of triggers (<code>TriggerSequence</code>) to | |
930 * bindings (<code>Binding</code>) which are currently active. | |
931 * This value may be <code>null</code> if there are no active | |
932 * bindings, and it may be empty. | |
933 */ | |
934 private final Map!(Object,Object) getActiveBindings() { | |
935 if (activeBindings is null) { | |
936 recomputeBindings(); | |
937 } | |
938 | |
939 return activeBindings; | |
940 } | |
941 | |
942 /** | |
943 * <p> | |
944 * Returns the active bindings indexed by command identifier. The caller | |
945 * must not modify the returned map. | |
946 * </p> | |
947 * <p> | |
948 * This method completes in <code>O(1)</code>. If the active bindings are | |
949 * not yet computed, then this completes in <code>O(nn)</code>, where | |
950 * <code>n</code> is the number of bindings. | |
951 * </p> | |
952 * | |
953 * @return The map of fully-parameterized commands (<code>ParameterizedCommand</code>) | |
954 * to triggers (<code>TriggerSequence</code>) which are | |
955 * currently active. This value may be <code>null</code> if there | |
956 * are no active bindings, and it may be empty. | |
957 */ | |
958 private final Map!(Object,Object) getActiveBindingsByParameterizedCommand() { | |
959 if (activeBindingsByParameterizedCommand is null) { | |
960 recomputeBindings(); | |
961 } | |
962 | |
963 return activeBindingsByParameterizedCommand; | |
964 } | |
965 | |
966 /** | |
967 * <p> | |
968 * Computes the bindings for the current state of the application, but | |
969 * disregarding the current contexts. This can be useful when trying to | |
970 * display all the possible bindings. | |
971 * </p> | |
972 * <p> | |
973 * This method completes in <code>O(n)</code>, where <code>n</code> is | |
974 * the number of bindings. | |
975 * </p> | |
976 * | |
977 * @return A map of trigger (<code>TriggerSequence</code>) to bindings ( | |
978 * <code>Seq!(Object)</code> containing <code>Binding</code>). | |
979 * This map may be empty, but it is never <code>null</code>. | |
980 */ | |
981 public final Map!(Object,Object) getActiveBindingsDisregardingContext() { | |
982 if (bindings is null) { | |
983 // Not yet initialized. This is happening too early. Do nothing. | |
984 if( EMPTY_MAP is null ) EMPTY_MAP = new HashMap!(Object,Object); | |
985 return EMPTY_MAP; | |
986 } | |
987 | |
988 // Build a cached binding set for that state. | |
989 CachedBindingSet bindingCache = new CachedBindingSet(null, | |
990 locales, platforms, activeSchemeIds); | |
991 | |
992 /* | |
993 * Check if the cached binding set already exists. If so, simply set the | |
994 * active bindings and return. | |
995 */ | |
996 CachedBindingSet existingCache = cast(CachedBindingSet) cachedBindings | |
997 .get(bindingCache); | |
998 if (existingCache is null) { | |
999 existingCache = bindingCache; | |
1000 cachedBindings.add(existingCache, existingCache); | |
1001 } | |
1002 auto commandIdsByTrigger = existingCache.getBindingsByTrigger(); | |
1003 if (commandIdsByTrigger !is null) { | |
1004 if (DEBUG) { | |
1005 Tracing.printTrace("BINDINGS", "Cache hit"); //$NON-NLS-1$ //$NON-NLS-2$ | |
1006 } | |
1007 | |
1008 return commandIdsByTrigger; | |
1009 } | |
1010 | |
1011 // There is no cached entry for this. | |
1012 if (DEBUG) { | |
1013 Tracing.printTrace("BINDINGS", "Cache miss"); //$NON-NLS-1$ //$NON-NLS-2$ | |
1014 } | |
1015 | |
1016 // Compute the active bindings. | |
1017 commandIdsByTrigger = new HashMap!(Object,Object); | |
1018 auto triggersByParameterizedCommand = new HashMap!(Object,Object); | |
1019 auto conflictsByTrigger = new HashMap!(Object,Object); | |
1020 computeBindings(null, commandIdsByTrigger, | |
1021 triggersByParameterizedCommand, conflictsByTrigger); | |
1022 existingCache.setBindingsByTrigger(commandIdsByTrigger); | |
1023 existingCache.setTriggersByCommandId(triggersByParameterizedCommand); | |
1024 existingCache.setConflictsByTrigger(conflictsByTrigger); | |
1025 return /+Collections.unmodifiableMap(+/commandIdsByTrigger; | |
1026 } | |
1027 | |
1028 /** | |
1029 * <p> | |
1030 * Computes the bindings for the current state of the application, but | |
1031 * disregarding the current contexts. This can be useful when trying to | |
1032 * display all the possible bindings. | |
1033 * </p> | |
1034 * <p> | |
1035 * This method completes in <code>O(n)</code>, where <code>n</code> is | |
1036 * the number of bindings. | |
1037 * </p> | |
1038 * | |
1039 * @return A map of trigger (<code>TriggerSequence</code>) to bindings ( | |
1040 * <code>Seq!(Object)</code> containing <code>Binding</code>). | |
1041 * This map may be empty, but it is never <code>null</code>. | |
1042 * @since 3.2 | |
1043 */ | |
1044 private final Map!(Object,Object) getActiveBindingsDisregardingContextByParameterizedCommand() { | |
1045 if (bindings is null) { | |
1046 // Not yet initialized. This is happening too early. Do nothing. | |
1047 if( EMPTY_MAP is null ) EMPTY_MAP = new HashMap!(Object,Object); | |
1048 return EMPTY_MAP; | |
1049 } | |
1050 | |
1051 // Build a cached binding set for that state. | |
1052 CachedBindingSet bindingCache = new CachedBindingSet(null, | |
1053 locales, platforms, activeSchemeIds); | |
1054 | |
1055 /* | |
1056 * Check if the cached binding set already exists. If so, simply set the | |
1057 * active bindings and return. | |
1058 */ | |
1059 CachedBindingSet existingCache = cast(CachedBindingSet) cachedBindings | |
1060 .get(bindingCache); | |
1061 if (existingCache is null) { | |
1062 existingCache = bindingCache; | |
1063 cachedBindings.add(existingCache, existingCache); | |
1064 } | |
1065 auto triggersByParameterizedCommand = existingCache | |
1066 .getTriggersByCommandId(); | |
1067 if (triggersByParameterizedCommand !is null) { | |
1068 if (DEBUG) { | |
1069 Tracing.printTrace("BINDINGS", "Cache hit"); //$NON-NLS-1$ //$NON-NLS-2$ | |
1070 } | |
1071 | |
1072 return /+Collections.unmodifiableMap(+/triggersByParameterizedCommand; | |
1073 } | |
1074 | |
1075 // There is no cached entry for this. | |
1076 if (DEBUG) { | |
1077 Tracing.printTrace("BINDINGS", "Cache miss"); //$NON-NLS-1$ //$NON-NLS-2$ | |
1078 } | |
1079 | |
1080 // Compute the active bindings. | |
1081 auto commandIdsByTrigger = new HashMap!(Object,Object); | |
1082 auto conflictsByTrigger = new HashMap!(Object,Object); | |
1083 triggersByParameterizedCommand = new HashMap!(Object,Object); | |
1084 computeBindings(null, commandIdsByTrigger, | |
1085 triggersByParameterizedCommand, conflictsByTrigger); | |
1086 existingCache.setBindingsByTrigger(commandIdsByTrigger); | |
1087 existingCache.setTriggersByCommandId(triggersByParameterizedCommand); | |
1088 existingCache.setConflictsByTrigger(conflictsByTrigger); | |
1089 | |
1090 return /+Collections.unmodifiableMap(+/triggersByParameterizedCommand; | |
1091 } | |
1092 | |
1093 /** | |
1094 * <p> | |
1095 * Computes the bindings for the current state of the application, but | |
1096 * disregarding the current contexts. This can be useful when trying to | |
1097 * display all the possible bindings. | |
1098 * </p> | |
1099 * <p> | |
1100 * This method completes in <code>O(n)</code>, where <code>n</code> is | |
1101 * the number of bindings. | |
1102 * </p> | |
1103 * | |
1104 * @return All of the active bindings (<code>Binding</code>), not sorted | |
1105 * in any fashion. This collection may be empty, but it is never | |
1106 * <code>null</code>. | |
1107 */ | |
1108 public final View!(Object) getActiveBindingsDisregardingContextFlat() { | |
1109 auto mergedBindings = new ArraySeq!(Object); | |
1110 | |
1111 foreach( k,v; getActiveBindingsDisregardingContext() ){ | |
1112 auto bindingCollection = cast(View!(Object))v; | |
1113 if ((bindingCollection !is null) && (!bindingCollection.drained())) { | |
1114 foreach( e; bindingCollection ){ | |
1115 mergedBindings.append(e); | |
1116 } | |
1117 } | |
1118 } | |
1119 | |
1120 return mergedBindings; | |
1121 } | |
1122 | |
1123 /** | |
1124 * <p> | |
1125 * Returns the active bindings for a particular command identifier, but | |
1126 * discounting the current contexts. This method operates in O(n) time over | |
1127 * the number of bindings. | |
1128 * </p> | |
1129 * <p> | |
1130 * This method completes in <code>O(1)</code>. If the active bindings are | |
1131 * not yet computed, then this completes in <code>O(nn)</code>, where | |
1132 * <code>n</code> is the number of bindings. | |
1133 * </p> | |
1134 * | |
1135 * @param parameterizedCommand | |
1136 * The fully-parameterized command whose bindings are requested. | |
1137 * This argument may be <code>null</code>. | |
1138 * @return The array of active triggers (<code>TriggerSequence</code>) | |
1139 * for a particular command identifier. This value is guaranteed to | |
1140 * never be <code>null</code>, but it may be empty. | |
1141 * @since 3.2 | |
1142 */ | |
1143 public final TriggerSequence[] getActiveBindingsDisregardingContextFor( | |
1144 ParameterizedCommand parameterizedCommand) { | |
1145 Object object = getActiveBindingsDisregardingContextByParameterizedCommand() | |
1146 .get(parameterizedCommand); | |
1147 if (auto collection = cast(Seq!(Object))object ) { | |
1148 return arraycast!(TriggerSequence)(collection.toArray()); | |
1149 } | |
1150 | |
1151 return EMPTY_TRIGGER_SEQUENCE; | |
1152 } | |
1153 | |
1154 /** | |
1155 * <p> | |
1156 * Returns the active bindings for a particular command identifier. This | |
1157 * method operates in O(n) time over the number of bindings. | |
1158 * </p> | |
1159 * <p> | |
1160 * This method completes in <code>O(1)</code>. If the active bindings are | |
1161 * not yet computed, then this completes in <code>O(nn)</code>, where | |
1162 * <code>n</code> is the number of bindings. | |
1163 * </p> | |
1164 * | |
1165 * @param parameterizedCommand | |
1166 * The fully-parameterized command whose bindings are requested. | |
1167 * This argument may be <code>null</code>. | |
1168 * @return The array of active triggers (<code>TriggerSequence</code>) | |
1169 * for a particular command identifier. This value is guaranteed to | |
1170 * never be <code>null</code>, but it may be empty. | |
1171 */ | |
1172 public final TriggerSequence[] getActiveBindingsFor( | |
1173 ParameterizedCommand parameterizedCommand) { | |
1174 Object object = getActiveBindingsByParameterizedCommand().get( | |
1175 parameterizedCommand); | |
1176 if ( auto collection = cast(Seq!(Object))object ) { | |
1177 return arraycast!(TriggerSequence)(collection.toArray()); | |
1178 } | |
1179 | |
1180 return EMPTY_TRIGGER_SEQUENCE; | |
1181 } | |
1182 | |
1183 /** | |
1184 * <p> | |
1185 * Returns the active bindings for a particular command identifier. This | |
1186 * method operates in O(n) time over the number of bindings. | |
1187 * </p> | |
1188 * <p> | |
1189 * This method completes in <code>O(1)</code>. If the active bindings are | |
1190 * not yet computed, then this completes in <code>O(nn)</code>, where | |
1191 * <code>n</code> is the number of bindings. | |
1192 * </p> | |
1193 * | |
1194 * @param commandId | |
1195 * The identifier of the command whose bindings are requested. | |
1196 * This argument may be <code>null</code>. It is assumed that | |
1197 * the command has no parameters. | |
1198 * @return The array of active triggers (<code>TriggerSequence</code>) | |
1199 * for a particular command identifier. This value is guaranteed not | |
1200 * to be <code>null</code>, but it may be empty. | |
1201 */ | |
1202 public final TriggerSequence[] getActiveBindingsFor(String commandId) { | |
1203 ParameterizedCommand parameterizedCommand = new ParameterizedCommand( | |
1204 commandManager.getCommand(commandId), null); | |
1205 Object object = getActiveBindingsByParameterizedCommand().get( | |
1206 parameterizedCommand); | |
1207 if ( auto collection = cast(Seq!(Object))object ) { | |
1208 return arraycast!(TriggerSequence)(collection.toArray()); | |
1209 } | |
1210 | |
1211 return EMPTY_TRIGGER_SEQUENCE; | |
1212 } | |
1213 | |
1214 /** | |
1215 * A variation on {@link BindingManager#getActiveBindingsFor(String)} that | |
1216 * returns an array of bindings, rather than trigger sequences. This method | |
1217 * is needed for doing "best" calculations on the active bindings. | |
1218 * | |
1219 * @param commandId | |
1220 * The identifier of the command for which the active bindings | |
1221 * should be retrieved; must not be <code>null</code>. | |
1222 * @return The active bindings for the given command; this value may be | |
1223 * <code>null</code> if there are no active bindings. | |
1224 * @since 3.2 | |
1225 */ | |
1226 private final Binding[] getActiveBindingsFor1(String commandId) { | |
1227 TriggerSequence[] triggers = getActiveBindingsFor(commandId); | |
1228 if (triggers.length is 0) { | |
1229 return null; | |
1230 } | |
1231 | |
1232 auto activeBindings = getActiveBindings(); | |
1233 if (activeBindings !is null) { | |
1234 Binding[] bindings = new Binding[triggers.length]; | |
1235 for (int i = 0; i < triggers.length; i++) { | |
1236 TriggerSequence triggerSequence = triggers[i]; | |
1237 Object object = activeBindings.get(triggerSequence); | |
1238 Binding binding = cast(Binding) object; | |
1239 bindings[i] = binding; | |
1240 } | |
1241 return bindings; | |
1242 } | |
1243 | |
1244 return null; | |
1245 } | |
1246 | |
1247 /** | |
1248 * <p> | |
1249 * Gets the currently active scheme. | |
1250 * </p> | |
1251 * <p> | |
1252 * This method completes in <code>O(1)</code>. | |
1253 * </p> | |
1254 * | |
1255 * @return The active scheme; may be <code>null</code> if there is no | |
1256 * active scheme. If a scheme is returned, it is guaranteed to be | |
1257 * defined. | |
1258 */ | |
1259 public final Scheme getActiveScheme() { | |
1260 return activeScheme; | |
1261 } | |
1262 | |
1263 /** | |
1264 * Gets the best active binding for a command. The best binding is the one | |
1265 * that would be most appropriate to show in a menu. Bindings which belong | |
1266 * to a child scheme are given preference over those in a parent scheme. | |
1267 * Bindings which belong to a particular locale or platform are given | |
1268 * preference over those that do not. The rest of the calculaton is based | |
1269 * most on various concepts of "length", as well as giving some modifier | |
1270 * keys preference (e.g., <code>Alt</code> is less likely to appear than | |
1271 * <code>Ctrl</code>). | |
1272 * | |
1273 * @param commandId | |
1274 * The identifier of the command for which the best active | |
1275 * binding should be retrieved; must not be <code>null</code>. | |
1276 * @return The trigger sequence for the best binding; may be | |
1277 * <code>null</code> if no bindings are active for the given | |
1278 * command. | |
1279 * @since 3.2 | |
1280 */ | |
1281 public final TriggerSequence getBestActiveBindingFor(String commandId) { | |
1282 Binding[] bindings = getActiveBindingsFor1(commandId); | |
1283 if ((bindings is null) || (bindings.length is 0)) { | |
1284 return null; | |
1285 } | |
1286 | |
1287 Binding bestBinding = bindings[0]; | |
1288 int compareTo; | |
1289 for (int i = 1; i < bindings.length; i++) { | |
1290 Binding currentBinding = bindings[i]; | |
1291 | |
1292 // Bindings in a child scheme are always given preference. | |
1293 String bestSchemeId = bestBinding.getSchemeId(); | |
1294 String currentSchemeId = currentBinding.getSchemeId(); | |
1295 compareTo = compareSchemes(bestSchemeId, currentSchemeId); | |
1296 if (compareTo > 0) { | |
1297 bestBinding = currentBinding; | |
1298 } | |
1299 if (compareTo !is 0) { | |
1300 continue; | |
1301 } | |
1302 | |
1303 /* | |
1304 * Bindings with a locale are given preference over those that do | |
1305 * not. | |
1306 */ | |
1307 String bestLocale = bestBinding.getLocale(); | |
1308 String currentLocale = currentBinding.getLocale(); | |
1309 if ((bestLocale is null) && (currentLocale !is null)) { | |
1310 bestBinding = currentBinding; | |
1311 } | |
1312 if (!(Util.opEquals(bestLocale, currentLocale))) { | |
1313 continue; | |
1314 } | |
1315 | |
1316 /* | |
1317 * Bindings with a platform are given preference over those that do | |
1318 * not. | |
1319 */ | |
1320 String bestPlatform = bestBinding.getPlatform(); | |
1321 String currentPlatform = currentBinding.getPlatform(); | |
1322 if ((bestPlatform is null) && (currentPlatform !is null)) { | |
1323 bestBinding = currentBinding; | |
1324 } | |
1325 if (!(Util.opEquals(bestPlatform, currentPlatform))) { | |
1326 continue; | |
1327 } | |
1328 | |
1329 /* | |
1330 * Check to see which has the least number of triggers in the | |
1331 * trigger sequence. | |
1332 */ | |
1333 TriggerSequence bestTriggerSequence = bestBinding | |
1334 .getTriggerSequence(); | |
1335 TriggerSequence currentTriggerSequence = currentBinding | |
1336 .getTriggerSequence(); | |
1337 Trigger[] bestTriggers = bestTriggerSequence.getTriggers(); | |
1338 Trigger[] currentTriggers = currentTriggerSequence | |
1339 .getTriggers(); | |
1340 compareTo = bestTriggers.length - currentTriggers.length; | |
1341 if (compareTo > 0) { | |
1342 bestBinding = currentBinding; | |
1343 } | |
1344 if (compareTo !is 0) { | |
1345 continue; | |
1346 } | |
1347 | |
1348 /* | |
1349 * Compare the number of keys pressed in each trigger sequence. Some | |
1350 * types of keys count less than others (i.e., some types of | |
1351 * modifiers keys are less likely to be chosen). | |
1352 */ | |
1353 compareTo = countStrokes(bestTriggers) | |
1354 - countStrokes(currentTriggers); | |
1355 if (compareTo > 0) { | |
1356 bestBinding = currentBinding; | |
1357 } | |
1358 if (compareTo !is 0) { | |
1359 continue; | |
1360 } | |
1361 | |
1362 // If this is still a tie, then just chose the shortest text. | |
1363 compareTo = bestTriggerSequence.format().length | |
1364 - currentTriggerSequence.format().length; | |
1365 if (compareTo > 0) { | |
1366 bestBinding = currentBinding; | |
1367 } | |
1368 } | |
1369 | |
1370 return bestBinding.getTriggerSequence(); | |
1371 } | |
1372 | |
1373 /** | |
1374 * Gets the formatted string representing the best active binding for a | |
1375 * command. The best binding is the one that would be most appropriate to | |
1376 * show in a menu. Bindings which belong to a child scheme are given | |
1377 * preference over those in a parent scheme. The rest of the calculaton is | |
1378 * based most on various concepts of "length", as well as giving some | |
1379 * modifier keys preference (e.g., <code>Alt</code> is less likely to | |
1380 * appear than <code>Ctrl</code>). | |
1381 * | |
1382 * @param commandId | |
1383 * The identifier of the command for which the best active | |
1384 * binding should be retrieved; must not be <code>null</code>. | |
1385 * @return The formatted string for the best binding; may be | |
1386 * <code>null</code> if no bindings are active for the given | |
1387 * command. | |
1388 * @since 3.2 | |
1389 */ | |
1390 public final String getBestActiveBindingFormattedFor(String commandId) { | |
1391 TriggerSequence binding = getBestActiveBindingFor(commandId); | |
1392 if (binding !is null) { | |
1393 return binding.format(); | |
1394 } | |
1395 | |
1396 return null; | |
1397 } | |
1398 | |
1399 /** | |
1400 * <p> | |
1401 * Returns the set of all bindings managed by this class. | |
1402 * </p> | |
1403 * <p> | |
1404 * This method completes in <code>O(1)</code>. | |
1405 * </p> | |
1406 * | |
1407 * @return The array of all bindings. This value may be <code>null</code> | |
1408 * and it may be empty. | |
1409 */ | |
1410 public final Binding[] getBindings() { | |
1411 if (bindings is null) { | |
1412 return null; | |
1413 } | |
1414 | |
1415 Binding[] returnValue = new Binding[bindingCount]; | |
1416 System.arraycopy(bindings, 0, returnValue, 0, bindingCount); | |
1417 return returnValue; | |
1418 } | |
1419 | |
1420 /** | |
1421 * <p> | |
1422 * Returns the array of schemes that are defined. | |
1423 * </p> | |
1424 * <p> | |
1425 * This method completes in <code>O(1)</code>. | |
1426 * </p> | |
1427 * | |
1428 * @return The array of defined schemes; this value may be empty or | |
1429 * <code>null</code>. | |
1430 */ | |
1431 public final Scheme[] getDefinedSchemes() { | |
1432 return arraycast!(Scheme)(definedHandleObjects.toArray()); | |
1433 } | |
1434 | |
1435 /** | |
1436 * <p> | |
1437 * Returns the active locale for this binding manager. The locale is in the | |
1438 * same format as <code>Locale.getDefault().toString()</code>. | |
1439 * </p> | |
1440 * <p> | |
1441 * This method completes in <code>O(1)</code>. | |
1442 * </p> | |
1443 * | |
1444 * @return The active locale; never <code>null</code>. | |
1445 */ | |
1446 public final String getLocale() { | |
1447 return locale; | |
1448 } | |
1449 | |
1450 /** | |
1451 * <p> | |
1452 * Returns all of the possible bindings that start with the given trigger | |
1453 * (but are not equal to the given trigger). | |
1454 * </p> | |
1455 * <p> | |
1456 * This method completes in <code>O(1)</code>. If the bindings aren't | |
1457 * currently computed, then this completes in <code>O(n)</code>, where | |
1458 * <code>n</code> is the number of bindings. | |
1459 * </p> | |
1460 * | |
1461 * @param trigger | |
1462 * The prefix to look for; must not be <code>null</code>. | |
1463 * @return A map of triggers (<code>TriggerSequence</code>) to bindings (<code>Binding</code>). | |
1464 * This map may be empty, but it is never <code>null</code>. | |
1465 */ | |
1466 public final Map!(Object,Object) getPartialMatches(TriggerSequence trigger) { | |
1467 auto partialMatches = cast(Map!(Object,Object)) getPrefixTable().get(trigger); | |
1468 if (partialMatches is null) { | |
1469 if( EMPTY_MAP is null ) EMPTY_MAP = new HashMap!(Object,Object); | |
1470 return EMPTY_MAP; | |
1471 } | |
1472 | |
1473 return partialMatches; | |
1474 } | |
1475 | |
1476 /** | |
1477 * <p> | |
1478 * Returns the command identifier for the active binding matching this | |
1479 * trigger, if any. | |
1480 * </p> | |
1481 * <p> | |
1482 * This method completes in <code>O(1)</code>. If the bindings aren't | |
1483 * currently computed, then this completes in <code>O(n)</code>, where | |
1484 * <code>n</code> is the number of bindings. | |
1485 * </p> | |
1486 * | |
1487 * @param trigger | |
1488 * The trigger to match; may be <code>null</code>. | |
1489 * @return The binding that matches, if any; <code>null</code> otherwise. | |
1490 */ | |
1491 public final Binding getPerfectMatch(TriggerSequence trigger) { | |
1492 return cast(Binding) getActiveBindings().get(trigger); | |
1493 } | |
1494 | |
1495 /** | |
1496 * <p> | |
1497 * Returns the active platform for this binding manager. The platform is in | |
1498 * the same format as <code>DWT.getPlatform()</code>. | |
1499 * </p> | |
1500 * <p> | |
1501 * This method completes in <code>O(1)</code>. | |
1502 * </p> | |
1503 * | |
1504 * @return The active platform; never <code>null</code>. | |
1505 */ | |
1506 public final String getPlatform() { | |
1507 return platform; | |
1508 } | |
1509 | |
1510 /** | |
1511 * <p> | |
1512 * Returns the prefix table. The caller must not modify the returned map. | |
1513 * </p> | |
1514 * <p> | |
1515 * This method completes in <code>O(1)</code>. If the active bindings are | |
1516 * not yet computed, then this completes in <code>O(n)</code>, where | |
1517 * <code>n</code> is the number of bindings. | |
1518 * </p> | |
1519 * | |
1520 * @return A map of prefixes (<code>TriggerSequence</code>) to a map of | |
1521 * available completions (possibly <code>null</code>, which means | |
1522 * there is an exact match). The available completions is a map of | |
1523 * trigger (<code>TriggerSequence</code>) to binding (<code>Binding</code>). | |
1524 * This value will never be <code>null</code> but may be empty. | |
1525 */ | |
1526 private final Map!(Object,Object) getPrefixTable() { | |
1527 if (prefixTable is null) { | |
1528 recomputeBindings(); | |
1529 } | |
1530 | |
1531 return prefixTable; | |
1532 } | |
1533 | |
1534 /** | |
1535 * <p> | |
1536 * Gets the scheme with the given identifier. If the scheme does not already | |
1537 * exist, then a new (undefined) scheme is created with that identifier. | |
1538 * This guarantees that schemes will remain unique. | |
1539 * </p> | |
1540 * <p> | |
1541 * This method completes in amortized <code>O(1)</code>. | |
1542 * </p> | |
1543 * | |
1544 * @param schemeId | |
1545 * The identifier for the scheme to retrieve; must not be | |
1546 * <code>null</code>. | |
1547 * @return A scheme with the given identifier. | |
1548 */ | |
1549 public final Scheme getScheme(String schemeId) { | |
1550 checkId(schemeId); | |
1551 | |
1552 Scheme scheme = cast(Scheme) handleObjectsById.get(schemeId); | |
1553 if (scheme is null) { | |
1554 scheme = new Scheme(schemeId); | |
1555 handleObjectsById.add(schemeId, scheme); | |
1556 scheme.addSchemeListener(this); | |
1557 } | |
1558 | |
1559 return scheme; | |
1560 } | |
1561 | |
1562 /** | |
1563 * <p> | |
1564 * Ascends all of the parents of the scheme until no more parents are found. | |
1565 * </p> | |
1566 * <p> | |
1567 * This method completes in <code>O(n)</code>, where <code>n</code> is | |
1568 * the height of the context tree. | |
1569 * </p> | |
1570 * | |
1571 * @param schemeId | |
1572 * The id of the scheme for which the parents should be found; | |
1573 * may be <code>null</code>. | |
1574 * @return The array of scheme ids (<code>String</code>) starting with | |
1575 * <code>schemeId</code> and then ascending through its ancestors. | |
1576 */ | |
1577 private final String[] getSchemeIds(String schemeId) { | |
1578 auto strings = new ArraySeq!(Object); | |
1579 while (schemeId !is null) { | |
1580 strings.append( stringcast(schemeId)); | |
1581 try { | |
1582 schemeId = getScheme(schemeId).getParentId(); | |
1583 } catch (NotDefinedException e) { | |
1584 Policy.getLog().log( new Status( | |
1585 IStatus.ERROR, Policy.JFACE, IStatus.OK, | |
1586 "Failed ascending scheme parents", //$NON-NLS-1$ | |
1587 e)); | |
1588 return null; | |
1589 } | |
1590 } | |
1591 | |
1592 return stringcast(strings.toArray()); | |
1593 } | |
1594 | |
1595 /** | |
1596 * <p> | |
1597 * Returns whether the given trigger sequence is a partial match for the | |
1598 * given sequence. | |
1599 * </p> | |
1600 * <p> | |
1601 * This method completes in <code>O(1)</code>. If the bindings aren't | |
1602 * currently computed, then this completes in <code>O(n)</code>, where | |
1603 * <code>n</code> is the number of bindings. | |
1604 * </p> | |
1605 * | |
1606 * @param trigger | |
1607 * The sequence which should be the prefix for some binding; | |
1608 * should not be <code>null</code>. | |
1609 * @return <code>true</code> if the trigger can be found in the active | |
1610 * bindings; <code>false</code> otherwise. | |
1611 */ | |
1612 public final bool isPartialMatch(TriggerSequence trigger) { | |
1613 return (getPrefixTable().get(trigger) !is null); | |
1614 } | |
1615 | |
1616 /** | |
1617 * <p> | |
1618 * Returns whether the given trigger sequence is a perfect match for the | |
1619 * given sequence. | |
1620 * </p> | |
1621 * <p> | |
1622 * This method completes in <code>O(1)</code>. If the bindings aren't | |
1623 * currently computed, then this completes in <code>O(n)</code>, where | |
1624 * <code>n</code> is the number of bindings. | |
1625 * </p> | |
1626 * | |
1627 * @param trigger | |
1628 * The sequence which should match exactly; should not be | |
1629 * <code>null</code>. | |
1630 * @return <code>true</code> if the trigger can be found in the active | |
1631 * bindings; <code>false</code> otherwise. | |
1632 */ | |
1633 public final bool isPerfectMatch(TriggerSequence trigger) { | |
1634 return getActiveBindings().containsKey(trigger); | |
1635 } | |
1636 | |
1637 /** | |
1638 * <p> | |
1639 * Tests whether the locale for the binding matches one of the active | |
1640 * locales. | |
1641 * </p> | |
1642 * <p> | |
1643 * This method completes in <code>O(n)</code>, where <code>n</code> is | |
1644 * the number of active locales. | |
1645 * </p> | |
1646 * | |
1647 * @param binding | |
1648 * The binding with which to test; must not be <code>null</code>. | |
1649 * @return <code>true</code> if the binding's locale matches; | |
1650 * <code>false</code> otherwise. | |
1651 */ | |
1652 private final bool localeMatches(Binding binding) { | |
1653 bool matches = false; | |
1654 | |
1655 String locale = binding.getLocale(); | |
1656 if (locale is null) { | |
1657 return true; // shortcut a common case | |
1658 } | |
1659 | |
1660 for (int i = 0; i < locales.length; i++) { | |
1661 if (Util.opEquals(locales[i], locale)) { | |
1662 matches = true; | |
1663 break; | |
1664 } | |
1665 } | |
1666 | |
1667 return matches; | |
1668 } | |
1669 | |
1670 /** | |
1671 * <p> | |
1672 * Tests whether the platform for the binding matches one of the active | |
1673 * platforms. | |
1674 * </p> | |
1675 * <p> | |
1676 * This method completes in <code>O(n)</code>, where <code>n</code> is | |
1677 * the number of active platforms. | |
1678 * </p> | |
1679 * | |
1680 * @param binding | |
1681 * The binding with which to test; must not be <code>null</code>. | |
1682 * @return <code>true</code> if the binding's platform matches; | |
1683 * <code>false</code> otherwise. | |
1684 */ | |
1685 private final bool platformMatches(Binding binding) { | |
1686 bool matches = false; | |
1687 | |
1688 String platform = binding.getPlatform(); | |
1689 if (platform is null) { | |
1690 return true; // shortcut a common case | |
1691 } | |
1692 | |
1693 for (int i = 0; i < platforms.length; i++) { | |
1694 if (Util.opEquals(platforms[i], platform)) { | |
1695 matches = true; | |
1696 break; | |
1697 } | |
1698 } | |
1699 | |
1700 return matches; | |
1701 } | |
1702 | |
1703 /** | |
1704 * <p> | |
1705 * This recomputes the bindings based on changes to the state of the world. | |
1706 * This computation can be triggered by changes to contexts, the active | |
1707 * scheme, the locale, or the platform. This method tries to use the cache | |
1708 * of pre-computed bindings, if possible. When this method completes, | |
1709 * <code>activeBindings</code> will be set to the current set of bindings | |
1710 * and <code>cachedBindings</code> will contain an instance of | |
1711 * <code>CachedBindingSet</code> representing these bindings. | |
1712 * </p> | |
1713 * <p> | |
1714 * This method completes in <code>O(n+pn)</code>, where <code>n</code> | |
1715 * is the number of bindings, and <code>p</code> is the average number of | |
1716 * triggers in a trigger sequence. | |
1717 * </p> | |
1718 */ | |
1719 private final void recomputeBindings() { | |
1720 if (bindings is null) { | |
1721 // Not yet initialized. This is happening too early. Do nothing. | |
1722 if( EMPTY_MAP is null ) EMPTY_MAP = new HashMap!(Object,Object); | |
1723 setActiveBindings(EMPTY_MAP, EMPTY_MAP, | |
1724 EMPTY_MAP, EMPTY_MAP); | |
1725 return; | |
1726 } | |
1727 | |
1728 // Figure out the current state. | |
1729 auto activeContextIds = new HashSet!(Object); | |
1730 foreach( e; contextManager.getActiveContextIds()){ | |
1731 activeContextIds.add(stringcast(e)); | |
1732 } | |
1733 auto activeContextTree = createFilteredContextTreeFor(activeContextIds); | |
1734 | |
1735 // Build a cached binding set for that state. | |
1736 CachedBindingSet bindingCache = new CachedBindingSet( | |
1737 activeContextTree, locales, platforms, activeSchemeIds); | |
1738 | |
1739 /* | |
1740 * Check if the cached binding set already exists. If so, simply set the | |
1741 * active bindings and return. | |
1742 */ | |
1743 CachedBindingSet existingCache = cast(CachedBindingSet) cachedBindings | |
1744 .get(bindingCache); | |
1745 if (existingCache is null) { | |
1746 existingCache = bindingCache; | |
1747 cachedBindings.add(existingCache, existingCache); | |
1748 } | |
1749 auto commandIdsByTrigger = existingCache.getBindingsByTrigger(); | |
1750 if (commandIdsByTrigger !is null) { | |
1751 if (DEBUG) { | |
1752 Tracing.printTrace("BINDINGS", "Cache hit"); //$NON-NLS-1$ //$NON-NLS-2$ | |
1753 } | |
1754 setActiveBindings(commandIdsByTrigger, existingCache | |
1755 .getTriggersByCommandId(), existingCache.getPrefixTable(), | |
1756 existingCache.getConflictsByTrigger()); | |
1757 return; | |
1758 } | |
1759 | |
1760 // There is no cached entry for this. | |
1761 if (DEBUG) { | |
1762 Tracing.printTrace("BINDINGS", "Cache miss"); //$NON-NLS-1$ //$NON-NLS-2$ | |
1763 } | |
1764 | |
1765 // Compute the active bindings. | |
1766 commandIdsByTrigger = new HashMap!(Object,Object); | |
1767 auto triggersByParameterizedCommand = new HashMap!(Object,Object); | |
1768 auto conflictsByTrigger = new HashMap!(Object,Object); | |
1769 computeBindings(activeContextTree, commandIdsByTrigger, | |
1770 triggersByParameterizedCommand, conflictsByTrigger); | |
1771 existingCache.setBindingsByTrigger(commandIdsByTrigger); | |
1772 existingCache.setTriggersByCommandId(triggersByParameterizedCommand); | |
1773 existingCache.setConflictsByTrigger(conflictsByTrigger); | |
1774 setActiveBindings(commandIdsByTrigger, triggersByParameterizedCommand, | |
1775 buildPrefixTable(commandIdsByTrigger), | |
1776 conflictsByTrigger); | |
1777 existingCache.setPrefixTable(prefixTable); | |
1778 } | |
1779 | |
1780 /** | |
1781 * <p> | |
1782 * Remove the specific binding by identity. Does nothing if the binding is | |
1783 * not in the manager. | |
1784 * </p> | |
1785 * <p> | |
1786 * This method completes in <code>O(n)</code>, where <code>n</code> is | |
1787 * the number of bindings. | |
1788 * </p> | |
1789 * | |
1790 * @param binding | |
1791 * The binding to be removed; must not be <code>null</code>. | |
1792 * @since 3.2 | |
1793 */ | |
1794 public final void removeBinding(Binding binding) { | |
1795 if (bindings is null || bindings.length < 1) { | |
1796 return; | |
1797 } | |
1798 | |
1799 Binding[] newBindings = new Binding[bindings.length]; | |
1800 bool bindingsChanged = false; | |
1801 int index = 0; | |
1802 for (int i = 0; i < bindingCount; i++) { | |
1803 Binding b = bindings[i]; | |
1804 if (b is binding) { | |
1805 bindingsChanged = true; | |
1806 } else { | |
1807 newBindings[index++] = b; | |
1808 } | |
1809 } | |
1810 | |
1811 if (bindingsChanged) { | |
1812 this.bindings = newBindings; | |
1813 bindingCount = index; | |
1814 clearCache(); | |
1815 } | |
1816 } | |
1817 | |
1818 /** | |
1819 * <p> | |
1820 * Removes a listener from this binding manager. | |
1821 * </p> | |
1822 * <p> | |
1823 * This method completes in amortized <code>O(1)</code>. | |
1824 * </p> | |
1825 * | |
1826 * @param listener | |
1827 * The listener to be removed; must not be <code>null</code>. | |
1828 */ | |
1829 public final void removeBindingManagerListener( | |
1830 IBindingManagerListener listener) { | |
1831 removeListenerObject(cast(Object)listener); | |
1832 } | |
1833 | |
1834 /** | |
1835 * <p> | |
1836 * Removes any binding that matches the given values -- regardless of | |
1837 * command identifier. | |
1838 * </p> | |
1839 * <p> | |
1840 * This method completes in <code>O(n)</code>, where <code>n</code> is | |
1841 * the number of bindings. | |
1842 * </p> | |
1843 * | |
1844 * @param sequence | |
1845 * The sequence to match; may be <code>null</code>. | |
1846 * @param schemeId | |
1847 * The scheme id to match; may be <code>null</code>. | |
1848 * @param contextId | |
1849 * The context id to match; may be <code>null</code>. | |
1850 * @param locale | |
1851 * The locale to match; may be <code>null</code>. | |
1852 * @param platform | |
1853 * The platform to match; may be <code>null</code>. | |
1854 * @param windowManager | |
1855 * The window manager to match; may be <code>null</code>. TODO | |
1856 * Currently ignored. | |
1857 * @param type | |
1858 * The type to look for. | |
1859 * | |
1860 */ | |
1861 public final void removeBindings(TriggerSequence sequence, | |
1862 String schemeId, String contextId, String locale, | |
1863 String platform, String windowManager, int type) { | |
1864 if ((bindings is null) || (bindingCount < 1)) { | |
1865 return; | |
1866 } | |
1867 | |
1868 Binding[] newBindings = new Binding[bindings.length]; | |
1869 bool bindingsChanged = false; | |
1870 int index = 0; | |
1871 for (int i = 0; i < bindingCount; i++) { | |
1872 Binding binding = bindings[i]; | |
1873 bool equals = true; | |
1874 equals &= Util.opEquals(sequence, binding.getTriggerSequence()); | |
1875 equals &= Util.opEquals(schemeId, binding.getSchemeId()); | |
1876 equals &= Util.opEquals(contextId, binding.getContextId()); | |
1877 equals &= Util.opEquals(locale, binding.getLocale()); | |
1878 equals &= Util.opEquals(platform, binding.getPlatform()); | |
1879 equals &= (type is binding.getType()); | |
1880 if (equals) { | |
1881 bindingsChanged = true; | |
1882 } else { | |
1883 newBindings[index++] = binding; | |
1884 } | |
1885 } | |
1886 | |
1887 if (bindingsChanged) { | |
1888 this.bindings = newBindings; | |
1889 bindingCount = index; | |
1890 clearCache(); | |
1891 } | |
1892 } | |
1893 | |
1894 /** | |
1895 * <p> | |
1896 * Attempts to remove deletion markers from the collection of bindings. | |
1897 * </p> | |
1898 * <p> | |
1899 * This method completes in <code>O(n)</code>, where <code>n</code> is | |
1900 * the number of bindings. | |
1901 * </p> | |
1902 * | |
1903 * @param bindings | |
1904 * The bindings from which the deleted items should be removed. | |
1905 * This array should not be <code>null</code>, but may be | |
1906 * empty. | |
1907 * @return The array of bindings with the deletions removed; never | |
1908 * <code>null</code>, but may be empty. Contains only instances | |
1909 * of <code>Binding</code>. | |
1910 */ | |
1911 private final Binding[] removeDeletions(Binding[] bindings) { | |
1912 auto deletions = new HashMap!(Object,Object); | |
1913 Binding[] bindingsCopy = new Binding[bindingCount]; | |
1914 System.arraycopy(bindings, 0, bindingsCopy, 0, bindingCount); | |
1915 int deletedCount = 0; | |
1916 | |
1917 // Extract the deletions. | |
1918 for (int i = 0; i < bindingCount; i++) { | |
1919 Binding binding = bindingsCopy[i]; | |
1920 if ((binding.getParameterizedCommand() is null) | |
1921 && (localeMatches(binding)) && (platformMatches(binding))) { | |
1922 TriggerSequence sequence = binding.getTriggerSequence(); | |
1923 Object currentValue = deletions.get(sequence); | |
1924 if (cast(Binding)currentValue ) { | |
1925 auto collection = new ArraySeq!(Object); | |
1926 collection.append(currentValue); | |
1927 collection.append(binding); | |
1928 deletions.add(sequence, collection); | |
1929 } else if ( auto collection = cast(Seq!(Object))currentValue ) { | |
1930 collection.append(binding); | |
1931 } else { | |
1932 deletions.add(sequence, binding); | |
1933 } | |
1934 bindingsCopy[i] = null; | |
1935 deletedCount++; | |
1936 } | |
1937 } | |
1938 | |
1939 if (DEBUG) { | |
1940 Tracing.printTrace("BINDINGS", Format("There are {} deletion markers", deletions.size()) //$NON-NLS-1$ //$NON-NLS-2$ | |
1941 ); //$NON-NLS-1$ | |
1942 } | |
1943 | |
1944 // Remove the deleted items. | |
1945 for (int i = 0; i < bindingCount; i++) { | |
1946 Binding binding = bindingsCopy[i]; | |
1947 if (binding !is null) { | |
1948 Object deletion = deletions.get(binding | |
1949 .getTriggerSequence()); | |
1950 if (cast(Binding)deletion ) { | |
1951 if ((cast(Binding) deletion).deletes(binding)) { | |
1952 bindingsCopy[i] = null; | |
1953 deletedCount++; | |
1954 } | |
1955 | |
1956 } else if (cast(Seq!(Object))deletion ) { | |
1957 Seq!(Object) collection = cast(Seq!(Object)) deletion; | |
1958 foreach( e; collection){ | |
1959 // Iterator iterator = collection.iterator(); | |
1960 // while (iterator.hasNext()) { | |
1961 Object deletionBinding = e;//iterator.next(); | |
1962 if (cast(Binding)deletionBinding ) { | |
1963 if ((cast(Binding) deletionBinding).deletes(binding)) { | |
1964 bindingsCopy[i] = null; | |
1965 deletedCount++; | |
1966 break; | |
1967 } | |
1968 } | |
1969 } | |
1970 | |
1971 } | |
1972 } | |
1973 } | |
1974 | |
1975 // Compact the array. | |
1976 Binding[] returnValue = new Binding[bindingCount - deletedCount]; | |
1977 int index = 0; | |
1978 for (int i = 0; i < bindingCount; i++) { | |
1979 Binding binding = bindingsCopy[i]; | |
1980 if (binding !is null) { | |
1981 returnValue[index++] = binding; | |
1982 } | |
1983 } | |
1984 | |
1985 return returnValue; | |
1986 } | |
1987 | |
1988 /** | |
1989 * <p> | |
1990 * Attempts to resolve the conflicts for the given bindings. | |
1991 * </p> | |
1992 * <p> | |
1993 * This method completes in <code>O(n)</code>, where <code>n</code> is | |
1994 * the number of bindings. | |
1995 * </p> | |
1996 * | |
1997 * @param bindings | |
1998 * The bindings which all match the same trigger sequence; must | |
1999 * not be <code>null</code>, and should contain at least two | |
2000 * items. This collection should only contain instances of | |
2001 * <code>Binding</code> (i.e., no <code>null</code> values). | |
2002 * @param activeContextTree | |
2003 * The tree of contexts to be used for all of the comparison. All | |
2004 * of the keys should be active context identifiers (i.e., never | |
2005 * <code>null</code>). The values will be their parents (i.e., | |
2006 * possibly <code>null</code>). Both keys and values are | |
2007 * context identifiers (<code>String</code>). This map should | |
2008 * never be empty, and must never be <code>null</code>. | |
2009 * @return The binding which best matches the current state. If there is a | |
2010 * tie, then return <code>null</code>. | |
2011 */ | |
2012 private final Binding resolveConflicts(View!(Object) bindings, | |
2013 Map!(Object,Object) activeContextTree) { | |
2014 /* | |
2015 * This flag is used to indicate when the bestMatch binding conflicts | |
2016 * with another binding. We keep the best match binding so that we know | |
2017 * if we find a better binding. However, if we don't find a better | |
2018 * binding, then we known to return null. | |
2019 */ | |
2020 bool conflict = false; | |
2021 | |
2022 // Iterator bindingItr = bindings.iterator(); | |
2023 Binding bestMatch; | |
2024 bool first = true; | |
2025 foreach( b; bindings ){ | |
2026 if( first ){ | |
2027 first = false; | |
2028 bestMatch = cast(Binding) b;//bindingItr.next(); | |
2029 continue; | |
2030 } | |
2031 | |
2032 /* | |
2033 * Iterate over each binding and compare it with the best match. If a | |
2034 * better match is found, then replace the best match and set the | |
2035 * conflict flag to false. If a conflict is found, then leave the best | |
2036 * match and set the conflict flag. Otherwise, just continue. | |
2037 */ | |
2038 // while (bindingItr.hasNext()) { | |
2039 Binding current = cast(Binding) b;//bindingItr.next(); | |
2040 | |
2041 /* | |
2042 * SCHEME: Test whether the current is in a child scheme. Bindings | |
2043 * defined in a child scheme will always take priority over bindings | |
2044 * defined in a parent scheme. | |
2045 */ | |
2046 String currentSchemeId = current.getSchemeId(); | |
2047 String bestSchemeId = bestMatch.getSchemeId(); | |
2048 int compareTo = compareSchemes(bestSchemeId, currentSchemeId); | |
2049 if (compareTo > 0) { | |
2050 bestMatch = current; | |
2051 conflict = false; | |
2052 } | |
2053 if (compareTo !is 0) { | |
2054 continue; | |
2055 } | |
2056 | |
2057 /* | |
2058 * CONTEXTS: Check for context superiority. Bindings defined in a | |
2059 * child context will take priority over bindings defined in a | |
2060 * parent context -- assuming that the schemes lead to a conflict. | |
2061 */ | |
2062 String currentContext = current.getContextId(); | |
2063 String bestContext = bestMatch.getContextId(); | |
2064 if (!currentContext.equals(bestContext)) { | |
2065 bool goToNextBinding = false; | |
2066 | |
2067 // Ascend the current's context tree. | |
2068 String contextPointer = currentContext; | |
2069 while (contextPointer !is null) { | |
2070 if (contextPointer.equals(bestContext)) { | |
2071 // the current wins | |
2072 bestMatch = current; | |
2073 conflict = false; | |
2074 goToNextBinding = true; | |
2075 break; | |
2076 } | |
2077 contextPointer = stringcast(activeContextTree | |
2078 .get(stringcast(contextPointer))); | |
2079 } | |
2080 | |
2081 // Ascend the best match's context tree. | |
2082 contextPointer = bestContext; | |
2083 while (contextPointer !is null) { | |
2084 if (contextPointer.equals(currentContext)) { | |
2085 // the best wins | |
2086 goToNextBinding = true; | |
2087 break; | |
2088 } | |
2089 contextPointer = stringcast( activeContextTree | |
2090 .get(stringcast(contextPointer))); | |
2091 } | |
2092 | |
2093 if (goToNextBinding) { | |
2094 continue; | |
2095 } | |
2096 } | |
2097 | |
2098 /* | |
2099 * TYPE: Test for type superiority. | |
2100 */ | |
2101 if (current.getType() > bestMatch.getType()) { | |
2102 bestMatch = current; | |
2103 conflict = false; | |
2104 continue; | |
2105 } else if (bestMatch.getType() > current.getType()) { | |
2106 continue; | |
2107 } | |
2108 | |
2109 // We could not resolve the conflict between these two. | |
2110 conflict = true; | |
2111 } | |
2112 | |
2113 // If the best match represents a conflict, then return null. | |
2114 if (conflict) { | |
2115 return null; | |
2116 } | |
2117 | |
2118 // Otherwise, we have a winner.... | |
2119 return bestMatch; | |
2120 } | |
2121 | |
2122 /** | |
2123 * <p> | |
2124 * Notifies this manager that a scheme has changed. This method is intended | |
2125 * for internal use only. | |
2126 * </p> | |
2127 * <p> | |
2128 * This method calls out to listeners, and so the time it takes to complete | |
2129 * is dependent on third-party code. | |
2130 * </p> | |
2131 * | |
2132 * @param schemeEvent | |
2133 * An event describing the change in the scheme. | |
2134 */ | |
2135 public final void schemeChanged(SchemeEvent schemeEvent) { | |
2136 if (schemeEvent.isDefinedChanged()) { | |
2137 Scheme scheme = schemeEvent.getScheme(); | |
2138 bool schemeIdAdded = scheme.isDefined(); | |
2139 bool activeSchemeChanged = false; | |
2140 if (schemeIdAdded) { | |
2141 definedHandleObjects.add(scheme); | |
2142 } else { | |
2143 definedHandleObjects.remove(scheme); | |
2144 | |
2145 if (activeScheme is scheme) { | |
2146 activeScheme = null; | |
2147 activeSchemeIds = null; | |
2148 activeSchemeChanged = true; | |
2149 | |
2150 // Clear the binding solution. | |
2151 clearSolution(); | |
2152 } | |
2153 } | |
2154 | |
2155 if (isListenerAttached()) { | |
2156 fireBindingManagerChanged(new BindingManagerEvent(this, false, | |
2157 null, activeSchemeChanged, scheme, schemeIdAdded, | |
2158 false, false)); | |
2159 } | |
2160 } | |
2161 } | |
2162 | |
2163 /** | |
2164 * Sets the active bindings and the prefix table. This ensures that the two | |
2165 * values change at the same time, and that any listeners are notified | |
2166 * appropriately. | |
2167 * | |
2168 * @param activeBindings | |
2169 * This is a map of triggers ( <code>TriggerSequence</code>) | |
2170 * to bindings (<code>Binding</code>). This value will only | |
2171 * be <code>null</code> if the active bindings have not yet | |
2172 * been computed. Otherwise, this value may be empty. | |
2173 * @param activeBindingsByCommandId | |
2174 * This is a map of fully-parameterized commands (<code>ParameterizedCommand</code>) | |
2175 * to triggers ( <code>TriggerSequence</code>). This value | |
2176 * will only be <code>null</code> if the active bindings have | |
2177 * not yet been computed. Otherwise, this value may be empty. | |
2178 * @param prefixTable | |
2179 * A map of prefixes (<code>TriggerSequence</code>) to a map | |
2180 * of available completions (possibly <code>null</code>, which | |
2181 * means there is an exact match). The available completions is a | |
2182 * map of trigger (<code>TriggerSequence</code>) to binding (<code>Binding</code>). | |
2183 * This value may be <code>null</code> if there is no existing | |
2184 * solution. | |
2185 */ | |
2186 private final void setActiveBindings(Map!(Object,Object) activeBindings, | |
2187 Map!(Object,Object) activeBindingsByCommandId, Map!(Object,Object) prefixTable, | |
2188 Map!(Object,Object) conflicts) { | |
2189 this.activeBindings = activeBindings; | |
2190 Map!(Object,Object) previousBindingsByParameterizedCommand = this.activeBindingsByParameterizedCommand; | |
2191 this.activeBindingsByParameterizedCommand = activeBindingsByCommandId; | |
2192 this.prefixTable = prefixTable; | |
2193 InternalPolicy.currentConflicts = conflicts; | |
2194 | |
2195 fireBindingManagerChanged(new BindingManagerEvent(this, true, | |
2196 previousBindingsByParameterizedCommand, false, null, false, | |
2197 false, false)); | |
2198 } | |
2199 | |
2200 /** | |
2201 * <p> | |
2202 * Selects one of the schemes as the active scheme. This scheme must be | |
2203 * defined. | |
2204 * </p> | |
2205 * <p> | |
2206 * This method completes in <code>O(n)</code>, where <code>n</code> is | |
2207 * the height of the context tree. | |
2208 * </p> | |
2209 * | |
2210 * @param scheme | |
2211 * The scheme to become active; must not be <code>null</code>. | |
2212 * @throws NotDefinedException | |
2213 * If the given scheme is currently undefined. | |
2214 */ | |
2215 public final void setActiveScheme(Scheme scheme) { | |
2216 if (scheme is null) { | |
2217 throw new NullPointerException("Cannot activate a null scheme"); //$NON-NLS-1$ | |
2218 } | |
2219 | |
2220 if ((scheme is null) || (!scheme.isDefined())) { | |
2221 throw new NotDefinedException( | |
2222 "Cannot activate an undefined scheme. " //$NON-NLS-1$ | |
2223 ~ scheme.getId()); | |
2224 } | |
2225 | |
2226 if (Util.opEquals(activeScheme, scheme)) { | |
2227 return; | |
2228 } | |
2229 | |
2230 activeScheme = scheme; | |
2231 activeSchemeIds = getSchemeIds(activeScheme.getId()); | |
2232 clearSolution(); | |
2233 fireBindingManagerChanged(new BindingManagerEvent(this, false, null, | |
2234 true, null, false, false, false)); | |
2235 } | |
2236 | |
2237 /** | |
2238 * <p> | |
2239 * Changes the set of bindings for this binding manager. Changing the set of | |
2240 * bindings all at once ensures that: (1) duplicates are removed; and (2) | |
2241 * avoids unnecessary intermediate computations. This method clears the | |
2242 * existing bindings, but does not trigger a recomputation (other method | |
2243 * calls are required to do that). | |
2244 * </p> | |
2245 * <p> | |
2246 * This method completes in <code>O(n)</code>, where <code>n</code> is | |
2247 * the number of bindings. | |
2248 * </p> | |
2249 * | |
2250 * @param bindings | |
2251 * The new array of bindings; may be <code>null</code>. This | |
2252 * set is copied into a local data structure. | |
2253 */ | |
2254 public final void setBindings(Binding[] bindings) { | |
2255 if (Arrays.equals(this.bindings, bindings)) { | |
2256 return; // nothing has changed | |
2257 } | |
2258 | |
2259 if ((bindings is null) || (bindings.length is 0)) { | |
2260 this.bindings = null; | |
2261 bindingCount = 0; | |
2262 } else { | |
2263 int bindingsLength = bindings.length; | |
2264 this.bindings = new Binding[bindingsLength]; | |
2265 System.arraycopy(bindings, 0, this.bindings, 0, bindingsLength); | |
2266 bindingCount = bindingsLength; | |
2267 } | |
2268 clearCache(); | |
2269 } | |
2270 | |
2271 /** | |
2272 * <p> | |
2273 * Changes the locale for this binding manager. The locale can be used to | |
2274 * provide locale-specific bindings. If the locale is different than the | |
2275 * current locale, this will force a recomputation of the bindings. The | |
2276 * locale is in the same format as | |
2277 * <code>Locale.getDefault().toString()</code>. | |
2278 * </p> | |
2279 * <p> | |
2280 * This method completes in <code>O(1)</code>. | |
2281 * </p> | |
2282 * | |
2283 * @param locale | |
2284 * The new locale; must not be <code>null</code>. | |
2285 * @see Locale#getDefault() | |
2286 */ | |
2287 public final void setLocale(String locale) { | |
2288 if (locale is null) { | |
2289 throw new NullPointerException("The locale cannot be null"); //$NON-NLS-1$ | |
2290 } | |
2291 | |
2292 if (!Util.opEquals(this.locale, locale)) { | |
2293 this.locale = locale; | |
2294 this.locales = expand(locale, LOCALE_SEPARATOR); | |
2295 clearSolution(); | |
2296 fireBindingManagerChanged(new BindingManagerEvent(this, false, | |
2297 null, false, null, false, true, false)); | |
2298 } | |
2299 } | |
2300 | |
2301 /** | |
2302 * <p> | |
2303 * Changes the platform for this binding manager. The platform can be used | |
2304 * to provide platform-specific bindings. If the platform is different than | |
2305 * the current platform, then this will force a recomputation of the | |
2306 * bindings. The locale is in the same format as | |
2307 * <code>DWT.getPlatform()</code>. | |
2308 * </p> | |
2309 * <p> | |
2310 * This method completes in <code>O(1)</code>. | |
2311 * </p> | |
2312 * | |
2313 * @param platform | |
2314 * The new platform; must not be <code>null</code>. | |
2315 * @see dwt.DWT#getPlatform() | |
2316 */ | |
2317 public final void setPlatform(String platform) { | |
2318 if (platform is null) { | |
2319 throw new NullPointerException("The platform cannot be null"); //$NON-NLS-1$ | |
2320 } | |
2321 | |
2322 if (!Util.opEquals(this.platform, platform)) { | |
2323 this.platform = platform; | |
2324 this.platforms = expand(platform, Util.ZERO_LENGTH_STRING); | |
2325 clearSolution(); | |
2326 fireBindingManagerChanged(new BindingManagerEvent(this, false, | |
2327 null, false, null, false, false, true)); | |
2328 } | |
2329 } | |
2330 } |