private async Task ProcessMultiKeySelectionResult( IList<Timestamped<PointAndKeyValue>> pointsAndKeyValues, TriggerSignal startSelectionTriggerSignal) { Log.DebugFormat("Multi-key selection captured a set of '{0}' PointAndKeyValues.", pointsAndKeyValues.Count); RequestSuspend(); //Pause everything (i.e. processing new points) while we perform the (CPU bound) word matching try { if (pointsAndKeyValues.Any()) { var timeSpan = pointsAndKeyValues.Last().Timestamp.Subtract(pointsAndKeyValues.First().Timestamp); var sequenceThreshold = (int)Math.Round( ((double)pointsAndKeyValues.Count / (double)timeSpan.TotalMilliseconds) * Settings.Default.MultiKeySelectionFixationMinDwellTime.TotalMilliseconds); Log.DebugFormat( "Multi-key selection capture lasted {0}ms. Minimum dwell time is {1}ms, or {2} points.", timeSpan.TotalMilliseconds, Settings.Default.MultiKeySelectionFixationMinDwellTime.TotalMilliseconds, sequenceThreshold); //Always assume the start trigger is reliable if it occurs on a letter string reliableFirstLetter = startMultiKeySelectionTriggerSignal != null && startMultiKeySelectionTriggerSignal.Value.PointAndKeyValue != null && startMultiKeySelectionTriggerSignal.Value.PointAndKeyValue.Value.StringIsLetter ? startMultiKeySelectionTriggerSignal.Value.PointAndKeyValue.Value.String : null; Log.DebugFormat( "First letter ('{0}') of multi-key selection capture {1} reliable.", reliableFirstLetter, reliableFirstLetter != null ? "IS" : "IS NOT"); //If we are using a fixation trigger and the stop trigger has occurred on a letter then it is reliable - use it string reliableLastLetter = selectionTriggerSource is IFixationTriggerSource && stopMultiKeySelectionTriggerSignal != null && stopMultiKeySelectionTriggerSignal.Value.PointAndKeyValue != null && stopMultiKeySelectionTriggerSignal.Value.PointAndKeyValue.Value.StringIsLetter ? stopMultiKeySelectionTriggerSignal.Value.PointAndKeyValue.Value.String : null; Log.DebugFormat( "Last letter ('{0}') of multi-key selection capture {1} reliable.", reliableLastLetter, reliableLastLetter != null ? "IS" : "IS NOT"); if (reliableLastLetter != null) { Log.Debug("Publishing selection event on last letter of multi-key selection capture."); PublishSelection(stopMultiKeySelectionTriggerSignal.Value.PointAndKeyValue.Value); } //Why am I wrapping this call in a Task.Run? Internally the MapCaptureToEntries method uses PLINQ which also blocks the UI thread - this frees it up. //This cannot be done inside the MapCaptureToEntries method as the method takes a ref param, which cannot be used inside an anonymous delegate or lambda. //The method cannot be made awaitable as async/await also does not support ref params. Tuple<List<Point>, FunctionKeys?, string, List<string>> result = null; await Task.Run(() => { result = dictionaryService.MapCaptureToEntries( pointsAndKeyValues.ToList(), sequenceThreshold, reliableFirstLetter, reliableLastLetter, ref mapToDictionaryMatchesCancellationTokenSource, exception => PublishError(this, exception)); }); if (result != null) { if (result.Item2 == null && result.Item3 == null && (result.Item4 == null || !result.Item4.Any())) { //Nothing useful in the result - play error message. Publish anyway as the points can be rendered in debugging mode. audioService.PlaySound(Settings.Default.ErrorSoundFile, Settings.Default.ErrorSoundVolume); } PublishSelectionResult(result); } } } finally { RequestResume(); } }
private void ProcessSelectionTrigger(TriggerSignal triggerSignal) { if (triggerSignal.Signal >= 1 && !CapturingMultiKeySelection) { //We are not currently capturing a multikey selection and have received a high (start) trigger signal if (triggerSignal.PointAndKeyValue != null) { Log.Debug("Selection trigger signal (with relevent PointAndKeyValue) detected."); if (SelectionMode == SelectionModes.Key) { if (triggerSignal.PointAndKeyValue.Value.KeyValue != null && (keyStateService.KeyEnabledStates == null || keyStateService.KeyEnabledStates[triggerSignal.PointAndKeyValue.Value.KeyValue.Value])) { Log.Debug("Selection mode is KEY and the key on which the trigger occurred is enabled."); if (MultiKeySelectionSupported && keyStateService.KeyDownStates[KeyValues.MultiKeySelectionKey].Value.IsDownOrLockedDown() && triggerSignal.PointAndKeyValue.Value.KeyValue != null && KeyValues.MultiKeySelectionKeys.Contains(triggerSignal.PointAndKeyValue.Value.KeyValue.Value)) { Log.Debug("Multi-key selection is currently enabled and the key on which the trigger occurred is a letter. Publishing the selection and beginning a new multi-key selection capture."); //Multi-key selection is allowed and the trigger occurred on a letter - start a capture startMultiKeySelectionTriggerSignal = triggerSignal; stopMultiKeySelectionTriggerSignal = null; CapturingMultiKeySelection = true; PublishSelection(triggerSignal.PointAndKeyValue.Value); multiKeySelectionSubscription = CreateMultiKeySelectionSubscription() .ObserveOnDispatcher() .Subscribe( async pointsAndKeyValues => await ProcessMultiKeySelectionResult(pointsAndKeyValues, triggerSignal), (exception => { PublishError(this, exception); stopMultiKeySelectionTriggerSignal = null; CapturingMultiKeySelection = false; }), () => { Log.Debug("Multi-key selection capture has completed."); stopMultiKeySelectionTriggerSignal = null; CapturingMultiKeySelection = false; }); } else { PublishSelection(triggerSignal.PointAndKeyValue.Value); PublishSelectionResult(new Tuple<List<Point>, FunctionKeys?, string, List<string>>( new List<Point> { triggerSignal.PointAndKeyValue.Value.Point }, triggerSignal.PointAndKeyValue.Value.KeyValue.Value.FunctionKey, triggerSignal.PointAndKeyValue.Value.KeyValue.Value.String, null)); } } else { Log.Debug("Selection mode is KEY, but the trigger occurred off a key or over a disabled key."); audioService.PlaySound(Settings.Default.ErrorSoundFile, Settings.Default.ErrorSoundVolume); } } else if (SelectionMode == SelectionModes.Point) { PublishSelection(triggerSignal.PointAndKeyValue.Value); PublishSelectionResult(new Tuple<List<Point>, FunctionKeys?, string, List<string>>( new List<Point> { triggerSignal.PointAndKeyValue.Value.Point }, null, null, null)); } } else { Log.Error("TriggerSignal.Signal==1, but TriggerSignal.PointAndKeyValue is null. " + "Discarding trigger as point source is down, or producing stale points. " + "Publishing error instead."); PublishError(this, new ApplicationException(Resources.TRIGGER_WITHOUT_POSITION_ERROR)); } } else if (CapturingMultiKeySelection) { //We are capturing and may have received the stop capturing signal if ((triggerSignal.Signal >= 1 && Settings.Default.MultiKeySelectionTriggerStopSignal == TriggerStopSignals.NextHigh) || (triggerSignal.Signal <= -1 && Settings.Default.MultiKeySelectionTriggerStopSignal == TriggerStopSignals.NextLow)) { //If we are using a fixation trigger source then the stop signal must occur on a letter if (!(selectionTriggerSource is IFixationTriggerSource) || (triggerSignal.PointAndKeyValue != null && triggerSignal.PointAndKeyValue.Value.StringIsLetter)) { Log.Debug("Trigger signal to stop the current multi-key selection capture detected."); stopMultiKeySelectionTriggerSignal = triggerSignal; } } } }