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 }