/// <summary> /// Re-renders all strokes which are currently being drawn. /// </summary> /// <remarks> /// This method should be invoked whenever the control onto which the /// <see cref="Core"/> is drawing is <see cref="Control.Invalidated">invalidated</see>. /// </remarks> /// <remarks> /// This method re-renders only the ink packets which have been received since the last <c>StylusDown</c> event /// but before a <c>StylusUp</c> event. Once <c>StylusUp</c> is received, the packets are discarded and must /// be rendered elsewhere. /// </remarks> protected internal void Refresh(IntPtr hdc) { using (Synchronizer.Lock(this.m_SlideDisplay.SyncRoot)) { foreach (int stylusId in this.m_CollectedPacketsTable.Keys) { // Get the DrawingAttributes which were in effect on StylusDown. DrawingAttributes atts = ((DrawingAttributes)this.m_DrawingAttributesTable[stylusId]); // Discard the stroke if we have no DrawingAttributes. if (atts == null) { continue; } // Get the tablet description which the renderer will use to interpret the data. TabletPropertyDescriptionCollection tabletProperties = ((TabletPropertyDescriptionCollection)this.m_TabletPropertiesTable[stylusId]); Debug.Assert(tabletProperties != null); // Also add the packets to the collected packets list. List <int> collected = this.m_CollectedPacketsTable[stylusId]; int[] packets = collected.ToArray(); // Create the temporary stroke which we will immediately render. Stroke stroke = this.Ink.CreateStroke(packets, tabletProperties); stroke.DrawingAttributes = atts; // Now draw the stroke. this.m_Renderer.Draw(hdc, stroke); // Immediately delete the stroke, since there's no easy way to save it and modify it for the next data. this.Ink.DeleteStroke(stroke); } } }
/// <summary> /// Used by <see cref="Packets"/> and <see cref="StylusUp"/> to render ink. /// </summary> private void HandleStroke(int stylusId) { // Also add the packets to the collected packets list. List <int> collected = this.collectedPacketsTable[stylusId]; TabletPropertyDescriptionCollection tabletProperties = this.tabletPropertiesTable[stylusId]; // Construct the final stroke Stroke finalStroke = this.ink.CreateStroke(collected.ToArray(), tabletProperties); // Now interpret the stroke Point[] pts = finalStroke.GetPoints(); Point firstPoint = pts[0]; Point lastPoint = pts[pts.Length - 1]; Rectangle bounding = finalStroke.GetBoundingBox(); // Ensure we sort of draw a horizontal shape if (bounding.Width > HIMETRIC_WINDOW_WIDTH && bounding.Height < HIMETRIC_WINDOW_HEIGHT) { // Ensure that the first and last points are far enough apart if (Math.Abs(firstPoint.X - lastPoint.X) > HIMETRIC_WINDOW_WIDTH) { // Go forward or back depending on the direction if (firstPoint.X < lastPoint.X) { this.NextSlide(); } else { this.PrevSlide(); } } } }
/// <summary> /// Notifies the <see cref="Core"/> of the start of a new stroke. /// </summary> /// <remarks> /// Note that some properties of the <see cref="Core"/> take effect only after a <see cref="StylusDown"/> event. /// </remarks> /// <param name="stylusId">The stylus which is being used to draw the stroke.</param> /// <param name="packets">The packet data describing the stroke.</param> /// <param name="tabletProperties"> /// The <see cref="TabletPropertyDescriptionCollection"/> /// used to interpret the packet data, and also subsequent packet data from <see cref="Packets"/> and /// <see cref="StylusUp"/> events which have the same <paramref name="stylusId"/>. /// </param> public void StylusDown(int stylusId, int strokeId, int[] packets, TabletPropertyDescriptionCollection tabletProperties) { using (Synchronizer.Lock(this.m_SlideDisplay.SyncRoot)) { if (!this.Enabled) { return; } // Insert the data into a hashtable using the stylus id as a key. this.m_PacketsTable[stylusId] = packets; // Also store the current DrawingAttributes. This is necessary to emulate the default // DynamicRenderer which only updates its DrawingAttributes on StylusDown. this.m_DrawingAttributesTable[stylusId] = this.DrawingAttributes; // Store the packets in the collected packets list so we can retrieve them on Refresh(). List <int> collected = new List <int>(packets); this.m_CollectedPacketsTable[stylusId] = collected; // Store the strokeId so RenderIncomingPackets will know when to cancel an incomplete // stroke if a StylusUp event is missed. this.m_CurrentStrokeIdTable[stylusId] = strokeId; // Finally store the tablet properties, which will be used to interpret the packets when rendering a stroke. this.m_TabletPropertiesTable[stylusId] = tabletProperties; } }
private void LoadInkIntoTarget(InkSheetModel sheet, Ink extracted, out int[] ids) { Ink restored = extracted; using (Synchronizer.Lock(sheet.Ink.Strokes.SyncRoot)) { ids = new int[restored.Strokes.Count]; for (int i = 0; i < ids.Length; i++) { Stroke stroke = restored.Strokes[i]; // Remove any strokes that have the same remote Id as the new one. // Unfortunately, because the InkSheetUndoService cannot preserve stroke referential identity, // we must do a full search each time and cannot simply keep a table. if (stroke.ExtendedProperties.DoesPropertyExist(InkSheetMessage.StrokeIdExtendedProperty)) { object id = stroke.ExtendedProperties[InkSheetMessage.StrokeIdExtendedProperty].Data; foreach (Stroke existing in sheet.Ink.Strokes) { if (existing.ExtendedProperties.DoesPropertyExist(InkSheetMessage.StrokeIdExtendedProperty)) { if (id.Equals(existing.ExtendedProperties[InkSheetMessage.StrokeIdExtendedProperty].Data)) { StrokesEventArgs args = new StrokesEventArgs(new int[] { existing.Id }); sheet.OnInkDeleting(args); sheet.Ink.DeleteStroke(existing); sheet.OnInkDeleted(args); } } } } // The stroke has no association with the current Ink object. // Therefore, we have to recreate it by copying the raw packet data. // This first requires recreating the TabletPropertyDescriptionCollection, // which, for some stupid reason, must be done manually for lack of a better API. TabletPropertyDescriptionCollection properties = new TabletPropertyDescriptionCollection(); foreach (Guid property in stroke.PacketDescription) { properties.Add(new TabletPropertyDescription(property, stroke.GetPacketDescriptionPropertyMetrics(property))); } // Create a new stroke from the raw packet data. Stroke created = sheet.Ink.CreateStroke(stroke.GetPacketData(), properties); // Copy the DrawingAttributes and all application data // (especially the StrokesIdExtendedProperty) to the new stroke. created.DrawingAttributes = stroke.DrawingAttributes; foreach (ExtendedProperty prop in stroke.ExtendedProperties) { created.ExtendedProperties.Add(prop.Id, prop.Data); } ids[i] = created.Id; } } }
private void AddInk() { using (Synchronizer.Lock(this)) { // Create an array of stroke Ids in order to fire the InkAdded event later. int[] ids = new int[this.m_StrokesToAdd.Count]; using (Synchronizer.Lock(this.m_Watcher.m_InkSheet.Ink.Strokes.SyncRoot)) { for (int i = 0; i < ids.Length; i++) { Stroke stroke = this.m_StrokesToAdd[i]; // The stroke probably has no association with the current Ink object. // Therefore, we have to recreate it by copying the raw packet data. // This first requires recreating the TabletPropertyDescriptionCollection, // which, for some stupid reason, must be done manually for lack of a better API. TabletPropertyDescriptionCollection properties = new TabletPropertyDescriptionCollection(); foreach (Guid property in stroke.PacketDescription) { properties.Add(new TabletPropertyDescription(property, stroke.GetPacketDescriptionPropertyMetrics(property))); } // Create a new stroke from the raw packet data. Stroke created = this.m_Watcher.m_InkSheet.Ink.CreateStroke(stroke.GetPacketData(), properties); // Copy the DrawingAttributes and all application data // (especially the StrokesIdExtendedProperty) to the new stroke. created.DrawingAttributes = stroke.DrawingAttributes; foreach (ExtendedProperty prop in stroke.ExtendedProperties) { created.ExtendedProperties.Add(prop.Id, prop.Data); } // Get the new stroke's Id so we can fire the InkAdded event. ids[i] = created.Id; } // If the added strokes don't yet have StrokeIdExtendedProperty properties, // create new Guids for them. Regardless, set this.m_StrokesToRemove // to the list of stroke Guids. this.UpdateAndSetStrokesToRemoveIds(this.m_StrokesToAdd); // Then, unset this.m_StrokesToAdd since they're already added. this.m_StrokesToAdd = null; } // Create the event arguments and add them to the ignore list so the // InkSheetUndoService won't create an InkUndoer for this change. StrokesEventArgs args = new StrokesEventArgs(ids); using (Synchronizer.Lock(this.m_Watcher.m_Ignore.SyncRoot)) { this.m_Watcher.m_Ignore.Add(args); } // Finally fire the appropriate InkAdded event from the InkSheetModel. this.m_Watcher.m_InkSheet.OnInkAdded(args); } }
/// <summary> /// On stylus-down we record initial ink packets, stroke ID and Tablet properties. Don't return any ink messages yet. /// </summary> /// <param name="rtissd"></param> /// <remarks> /// By definition CP3 real-time ink is ink that only exists during the act of inking, then is promptly deleted. The static /// completed strokes are created and deleted by separate messages (InkSheetStrokesAddedMessage/...DeletedMessage). /// CP3 generates three types of real-time ink messages: stylus down, packets, and stylus up. We translate the accumulated /// packets into ink strokes (using a constant ID, so that each RT ink message will replace the previous one on WebViewer), /// then on stylus up, we delete the last RT stroke, and a completed static ink stroke is added by InkSheetStrokesAddedMessage. /// By definition, real-time messages may be dropped by the CP3 sender, so we need to design with this expectation. /// The only serious impact of lost RT messages is that they may cause stray RT ink to remain after /// the static ink is deleted. To work around this, we will keep a list of the RT ink strokes which have been /// added, but not yet deleted, then on InkSheetStrokesDeleted, we will examine the list, and generate deletes for any strays. /// </remarks> internal void AddRealTimeInkSheetStylusDown(UW.ClassroomPresenter.Network.Messages.Presentation.RealTimeInkSheetStylusDownMessage rtissd) { //Debug.WriteLine("***** Realtime Ink stylus down StrokeID=" + rtissd.StrokeId.ToString() + "; stylusId=" + rtissd.StylusId.ToString()); // Store the packets array. In CP3 they use a hashtable keyed on the stylusId, but we can probably just assume one int[]. // Also store the current stroke id so that we won't apply the wrong packets if a message is lost. this.previousRealTimePackets = rtissd.Packets; this.previousRealTimeStroke = rtissd.StrokeId; this.previousTabletProperties = rtissd.TabletProperties.CreateTabletPropertyDescriptionCollection(); //For now we assume only one stylus. }
public TabletPropertyDescriptionCollection CreateTabletPropertyDescriptionCollection() { TabletPropertyDescriptionCollection tp = new TabletPropertyDescriptionCollection(this.InkToDeviceScaleX, this.InkToDeviceScaleY); foreach (TabletPropertyDescriptionInformation info in this.TabletPropertyDescriptions) { tp.Add(info.CreateTabletPropertyDescription()); } return(tp); }
/// <summary> /// Occurs when the stylus leaves the digitizer surface. /// Retrieve the packet array for this stylus and use it to create /// a new stoke. /// </summary> /// <param name="sender">The real time stylus associated with the notification</param> /// <param name="data">The notification data</param> void IStylusAsyncPlugin.StylusUp(RealTimeStylus sender, StylusUpData data) { // Retrieve the packet array from the hashtable using the cursor id as a key. int stylusId = data.Stylus.Id; List <int> collected; if (!this.m_PacketsTable.TryGetValue(stylusId, out collected)) { // If ink collection was disabled on StylusDown or if the user is erasing, // then ignore these packets. return; } int strokeId = this.m_StrokeIdTable[stylusId]; // Also get the DrawingAttributes which were in effect on StylusDown. DrawingAttributes atts = this.m_DrawingAttributesTable[stylusId]; // Remove this entry from the hash tables since it is no longer needed. this.m_PacketsTable.Remove(stylusId); this.m_DrawingAttributesTable.Remove(stylusId); // Add the newly collected packet data from StylusUp to the array. collected.AddRange(data.GetData()); // Assemble the completed information we'll need to create the stroke. int[] packets = collected.ToArray(); TabletPropertyDescriptionCollection tabletProperties = sender.GetTabletPropertyDescriptionCollection(data.Stylus.TabletContextId); // Now that we have the data, we're ready to create the stroke and add it to our Ink object. using (Synchronizer.Lock(this)) { // Ensure that this.(RealTime)InkSheetModel aren't changed unexpectedly. // If there is no Ink object, then probably the SlideViewer is not looking at a slide, // so discard the stroke. Otherwise, create the stroke from the collected packets. // Also discard the stroke if we have no DrawingAttributes. if (this.InkSheetModel != null && atts != null) { int inkStrokeId; using (Synchronizer.Lock(this.InkSheetModel.Ink.Strokes.SyncRoot)) { Stroke stroke = this.InkSheetModel.Ink.CreateStroke(packets, tabletProperties); stroke.DrawingAttributes = atts.Clone(); inkStrokeId = stroke.Id; } // Note that this ink stroke's Id is different from the strokeId used by the RealTimeInkSheetModel. this.InkSheetModel.OnInkAdded(new StrokesEventArgs(new int[] { inkStrokeId })); } if (this.RealTimeInkSheetModel != null) { this.RealTimeInkSheetModel.OnStylusUp(stylusId, strokeId, data.GetData()); } } }
public TabletPropertyDescriptionCollectionInformation(TabletPropertyDescriptionCollection tp) { this.InkToDeviceScaleX = tp.InkToDeviceScaleX; this.InkToDeviceScaleY = tp.InkToDeviceScaleY; this.TabletPropertyDescriptions = new ArrayList(tp.Count); foreach (TabletPropertyDescription d in tp) { this.TabletPropertyDescriptions.Add(new TabletPropertyDescriptionInformation(d)); } }
public void OnStylusDown(int stylusId, int strokeId, int[] packets, TabletPropertyDescriptionCollection tabletProperties) { StylusDownEventHandler handler; using (Synchronizer.Lock(this)) { handler = (this.m_DrawingAttributes != null) ? this.m_StylusDownDelegate : null; } if (handler != null) { handler(this, stylusId, strokeId, packets, tabletProperties); } }
private Dictionary <Guid, SizeF> customSlideBounds; //XPS decks use non-standard slide bounds. Map deckID to bounds. public CP3StateCache() { currentPresentationId = Guid.Empty; currentDeckId = Guid.Empty; previousTabletProperties = null; currentSlideId = Guid.Empty; realTimeStrokesPending = new Dictionary <int, RTStrokeData>(); sheetToSlideLookup = new Hashtable(); sheetToDrawingAttributesLookup = new Hashtable(); strokeCountsBySlideId = new Dictionary <Guid, int>(); toc = new TableOfContents(); pendingInk = new Dictionary <Guid, List <Ink> >(); m_QuickPollAggregator = new QuickPollAggregator(); customSlideBounds = new Dictionary <Guid, SizeF>(); }
/// <summary> /// Occurs when the stylus leaves the digitizer surface. /// Retrieve the packet array for this stylus and use it to create /// a new stoke. /// </summary> /// <param name="sender">The real time stylus associated with the notification</param> /// <param name="data">The notification data</param> public void StylusUp(RealTimeStylus sender, StylusUpData data) { // Retrieve the packet array from the hashtable using the cursor id // as a key. Then, clean this entry from the hash since it is no // longer needed. ArrayList collectedPackets = (ArrayList)myPackets[data.Stylus.Id]; myPackets.Remove(data.Stylus.Id); // Add the packet data from StylusUp to the array collectedPackets.AddRange(data.GetData()); // Create the stroke using the specified drawing attributes. int[] packets = (int[])(collectedPackets.ToArray(typeof(int))); TabletPropertyDescriptionCollection tabletProperties = myRealTimeStylus.GetTabletPropertyDescriptionCollection(data.Stylus.TabletContextId); Stroke stroke = myInk.CreateStroke(packets, tabletProperties); if (stroke != null) { stroke.DrawingAttributes.Color = myDynamicRenderer.DrawingAttributes.Color; stroke.DrawingAttributes.Width = myDynamicRenderer.DrawingAttributes.Width; } }
private void HandleStylusDownHelper(object sender, int stylusId, int strokeId, int[] packets, TabletPropertyDescriptionCollection tabletProperties) { this.FlushPackets(stylusId, strokeId); if (ViewerStateModel.NonStandardDpi) { RealTimeInkSheetModel.ScalePackets(packets, ViewerStateModel.DpiNormalizationSendMatrix); } Message message = new RealTimeInkSheetStylusDownMessage(this.m_Sheet, stylusId, strokeId, packets, tabletProperties); message.Tags = new MessageTags(); message.Tags.SlideID = m_SlideID; message.Tags.Priority = MessagePriority.RealTime; message.Tags.BridgePriority = MessagePriority.RealTime; this.Sender.Send(message, MessagePriority.RealTime); }
private void HandleStylusDown(object sender, int stylusId, int strokeId, int[] packets, TabletPropertyDescriptionCollection tabletProperties) { this.StylusDown(stylusId, strokeId, packets, tabletProperties); }
private void LoadInkIntoTarget(InkSheetModel sheet, byte[] saved, out int[] ids) { Ink restored = new Ink(); restored.Load(saved); using(Synchronizer.Lock(sheet.Ink.Strokes.SyncRoot)) { ids = new int[restored.Strokes.Count]; for(int i = 0; i < ids.Length; i++) { Stroke stroke = restored.Strokes[i]; // Remove any strokes that have the same remote Id as the new one. // Unfortunately, because the InkSheetUndoService cannot preserve stroke referential identity, // we must do a full search each time and cannot simply keep a table. if(stroke.ExtendedProperties.DoesPropertyExist(StrokeIdExtendedProperty)) { object id = stroke.ExtendedProperties[StrokeIdExtendedProperty].Data; foreach(Stroke existing in sheet.Ink.Strokes) { if(existing.ExtendedProperties.DoesPropertyExist(StrokeIdExtendedProperty)) { if(id.Equals(existing.ExtendedProperties[StrokeIdExtendedProperty].Data)) { StrokesEventArgs args = new StrokesEventArgs(new int[] { existing.Id }); sheet.OnInkDeleting(args); sheet.Ink.DeleteStroke(existing); sheet.OnInkDeleted(args); } } } } // The stroke has no association with the current Ink object. // Therefore, we have to recreate it by copying the raw packet data. // This first requires recreating the TabletPropertyDescriptionCollection, // which, for some stupid reason, must be done manually for lack of a better API. TabletPropertyDescriptionCollection properties = new TabletPropertyDescriptionCollection(); foreach(Guid property in stroke.PacketDescription) properties.Add(new TabletPropertyDescription(property, stroke.GetPacketDescriptionPropertyMetrics(property))); // Create a new stroke from the raw packet data. Stroke created = sheet.Ink.CreateStroke(stroke.GetPacketData(), properties); // Copy the DrawingAttributes and all application data // (especially the StrokesIdExtendedProperty) to the new stroke. created.DrawingAttributes = stroke.DrawingAttributes; foreach(ExtendedProperty prop in stroke.ExtendedProperties) created.ExtendedProperties.Add(prop.Id, prop.Data); ids[i] = created.Id; if (ViewerStateModel.NonStandardDpi) created.Transform(ViewerStateModel.DpiNormalizationReceiveMatrix); } } }
public void OnStylusDown(int stylusId, int strokeId, int[] packets, TabletPropertyDescriptionCollection tabletProperties) { StylusDownEventHandler handler; using(Synchronizer.Lock(this)) { handler = (this.m_DrawingAttributes != null) ? this.m_StylusDownDelegate : null; } if(handler != null) handler(this, stylusId, strokeId, packets, tabletProperties); }
private void HandleStylusDownHelper(object sender, int stylusId, int strokeId, int[] packets, TabletPropertyDescriptionCollection tabletProperties) { this.FlushPackets(stylusId, strokeId); Message message = new RealTimeInkSheetStylusDownMessage(this.m_Sheet, stylusId, strokeId, packets, tabletProperties); message.Tags = new MessageTags(); message.Tags.SlideID = this.m_SlideID; message.Tags.Priority = MessagePriority.RealTime; message.Tags.BridgePriority = MessagePriority.RealTime; this.Sender.Send(message, MessagePriority.RealTime); }
public RealTimeInkSheetStylusDownMessage(RealTimeInkSheetModel sheet, int stylusId, int strokeId, int[] packets, TabletPropertyDescriptionCollection tabletProperties) : base(sheet, stylusId, strokeId, packets) { this.TabletProperties = new TabletPropertyDescriptionCollectionInformation(tabletProperties); }
/// <summary> /// Helper function to marshal stylus down events /// </summary> /// <param name="sender">The object that sent the message</param> /// <param name="stylusId">The id of the stylus that was pressed down</param> /// <param name="packets">The packets sent by the stylus</param> /// <param name="tabletProperties">The properties of the tablet</param> private void HandleStylusDownHelper(object sender, int stylusId, int strokeId, int[] packets, TabletPropertyDescriptionCollection tabletProperties) { if (this.m_DestSheet != null) { this.m_DestSheet.OnStylusDown(stylusId, strokeId, packets, tabletProperties); } }
public TabletPropertyDescriptionCollectionInformation(TabletPropertyDescriptionCollection tp) { this.InkToDeviceScaleX = tp.InkToDeviceScaleX; this.InkToDeviceScaleY = tp.InkToDeviceScaleY; this.TabletPropertyDescriptions = new ArrayList(tp.Count); foreach(TabletPropertyDescription d in tp) this.TabletPropertyDescriptions.Add(new TabletPropertyDescriptionInformation(d)); }
private void AddInk() { using(Synchronizer.Lock(this)) { // Create an array of stroke Ids in order to fire the InkAdded event later. int[] ids = new int[this.m_StrokesToAdd.Count]; using (Synchronizer.Lock(this.m_Watcher.m_InkSheet.Ink.Strokes.SyncRoot)) { for (int i = 0; i < ids.Length; i++) { Stroke stroke = this.m_StrokesToAdd[i]; // The stroke probably has no association with the current Ink object. // Therefore, we have to recreate it by copying the raw packet data. // This first requires recreating the TabletPropertyDescriptionCollection, // which, for some stupid reason, must be done manually for lack of a better API. TabletPropertyDescriptionCollection properties = new TabletPropertyDescriptionCollection(); foreach (Guid property in stroke.PacketDescription) properties.Add(new TabletPropertyDescription(property, stroke.GetPacketDescriptionPropertyMetrics(property))); // Create a new stroke from the raw packet data. Stroke created = this.m_Watcher.m_InkSheet.Ink.CreateStroke(stroke.GetPacketData(), properties); // Copy the DrawingAttributes and all application data // (especially the StrokesIdExtendedProperty) to the new stroke. created.DrawingAttributes = stroke.DrawingAttributes; foreach (ExtendedProperty prop in stroke.ExtendedProperties) created.ExtendedProperties.Add(prop.Id, prop.Data); // Get the new stroke's Id so we can fire the InkAdded event. ids[i] = created.Id; } // If the added strokes don't yet have StrokeIdExtendedProperty properties, // create new Guids for them. Regardless, set this.m_StrokesToRemove // to the list of stroke Guids. this.UpdateAndSetStrokesToRemoveIds(this.m_StrokesToAdd); // Then, unset this.m_StrokesToAdd since they're already added. this.m_StrokesToAdd = null; } // Create the event arguments and add them to the ignore list so the // InkSheetUndoService won't create an InkUndoer for this change. StrokesEventArgs args = new StrokesEventArgs(ids); using(Synchronizer.Lock(this.m_Watcher.m_Ignore.SyncRoot)) { this.m_Watcher.m_Ignore.Add(args); } // Finally fire the appropriate InkAdded event from the InkSheetModel. this.m_Watcher.m_InkSheet.OnInkAdded(args); } }
/// <summary> /// Helper function to marshal stylus down events /// </summary> /// <param name="sender">The object that sent the message</param> /// <param name="stylusId">The id of the stylus that was pressed down</param> /// <param name="packets">The packets sent by the stylus</param> /// <param name="tabletProperties">The properties of the tablet</param> private void HandleStylusDownHelper(object sender, int stylusId, int strokeId, int[] packets, TabletPropertyDescriptionCollection tabletProperties) { if (this.m_DestSheet != null) this.m_DestSheet.OnStylusDown(stylusId, strokeId, packets, tabletProperties); }
private void HandleStylusDown(object sender, int stylusId, int strokeId, int[] packets, TabletPropertyDescriptionCollection tabletProperties) { this.Sender.Post(delegate() { this.HandleStylusDownHelper(sender, stylusId, strokeId, packets, tabletProperties); }); }
// Copied from the RealTimeStylus InkCollection example from // the Microsoft Tablet PC API Sample Applications collection. /// <summary> /// Occurs when the stylus touches the digitizer surface. /// Allocate a new array to store the packet data for this stylus. /// </summary> /// <param name="sender">The real time stylus associated with the notification</param> /// <param name="data">The notification data</param> void IStylusAsyncPlugin.StylusDown(RealTimeStylus sender, StylusDownData data) { // An inverted stylus is reserved for the eraser. if (data.Stylus.Inverted) { return; } // Ignore if a touch input; if (m_TouchSupported) { try { if (sender.GetTabletFromTabletContextId(data.Stylus.TabletContextId).DeviceKind == TabletDeviceKind.Touch) { return; } } catch { m_TouchSupported = false; } } int stylusId = data.Stylus.Id; // Allocate an empty array to store the packet data that will be // collected for this stylus. List <int> collectedPackets = new List <int>(); // Add the packet data from StylusDown to the array collectedPackets.AddRange(data.GetData()); // Insert the array into a hashtable using the stylus id as a key. this.m_PacketsTable[stylusId] = collectedPackets; // Also store the current DrawingAttributes. This is necessary because the default // DynamicRenderer (which will be used in conjunction with the ink stored by this collector) // only updates its DrawingAttributes on StylusDown. this.m_DrawingAttributesTable[stylusId] = this.m_DrawingAttributes; // Increment the current stroke id. int strokeId; if (!this.m_StrokeIdTable.TryGetValue(stylusId, out strokeId)) { this.m_StrokeIdTable[stylusId] = (strokeId = 0); } else { this.m_StrokeIdTable[stylusId] = ++strokeId; } // And send the packets to anybody who's listening to the RealTimeInk. using (Synchronizer.Lock(this)) { if (this.RealTimeInkSheetModel != null) { TabletPropertyDescriptionCollection tabletProperties = sender.GetTabletPropertyDescriptionCollection(data.Stylus.TabletContextId); this.RealTimeInkSheetModel.OnStylusDown(stylusId, strokeId, data.GetData(), tabletProperties); } } }
/// <summary> /// Used by <see cref="Packets"/> and <see cref="StylusUp"/> to render ink. /// </summary> private void RenderIncomingPackets(int stylusId, int strokeId, int[] packets) { using (Synchronizer.Lock(this.m_SlideDisplay.SyncRoot)) { if (!this.Enabled) { return; } //Allow rendering packets on disposed TransformableDynamicRenderer. //if (this.m_Disposed) { // Debug.WriteLine("Warning: Ignoring request to RenderIncomingPackets on disposed TransformableDynamicRenderer."); // return; //} // Verify that the stroke we're about to render matches the StrokeId // from the most recent StylusDown event. (If not, then something // probably got lost over the network.) int currentStrokeId; if (!this.m_CurrentStrokeIdTable.TryGetValue(stylusId, out currentStrokeId) || currentStrokeId != strokeId) { // First cancel the previous stroke to avoid // drawing a bogus connecting line between the endpoints. this.CancelStroke(stylusId); // Then attempt to start a new stroke. Since we didn't get the // StylusDown event, we'll have to re-use the old TabletPropertyDescriptionCollection. // (Theoretically this might have changed between strokes, but that's unlikely.) // But if there was no previous stroke, and therefore no TabletPropertyDescriptionCollection // we can use to render the packets, we'll have to give up. TabletPropertyDescriptionCollection props; if (!this.m_TabletPropertiesTable.TryGetValue(stylusId, out props)) { return; // Give up! } this.StylusDown(stylusId, strokeId, new int[] { }, props); } // At this point we're guaranteed to have executed a (real or simulated) // StylusDown event, so the entries in all the other dictionaries must exist. // Get the DrawingAttributes which were in effect on StylusDown. DrawingAttributes atts = this.m_DrawingAttributesTable[stylusId]; // Discard the stroke if we have no DrawingAttributes. if (atts == null) { return; } // Get the tablet description which the renderer will use to interpret the data. TabletPropertyDescriptionCollection tabletProperties = this.m_TabletPropertiesTable[stylusId]; Debug.Assert(tabletProperties != null); // Retrieve the packet array from the hashtable using the cursor id as a key. int[] previousData = this.m_PacketsTable[stylusId]; Debug.Assert(previousData != null); // Store the new data so that the next rendering job will only have to "connect the dots". this.m_PacketsTable[stylusId] = packets; // Also add the packets to the collected packets list. List <int> collected = this.m_CollectedPacketsTable[stylusId]; collected.AddRange(packets); //Calculate the distance between the last point of previous stroke, and first point of current stroke. //If the distance is smaller than distanceThreshold, then draw both previous and current strokes. bool combinedMode = true; double distanceThreshold = 2500; Stroke previousStroke = this.Ink.CreateStroke(previousData, tabletProperties); Stroke currentStroke = this.Ink.CreateStroke(packets, tabletProperties); Point lastPointInPrevious = previousStroke.GetPoint(previousStroke.GetPoints().Length - 1); Point firstPointInCurrent = currentStroke.GetPoint(0); int x = lastPointInPrevious.X - firstPointInCurrent.X; int y = lastPointInPrevious.Y - firstPointInCurrent.Y; if (Math.Sqrt(x * x + y * y) > distanceThreshold) { combinedMode = false; } // Assemble the completed information we'll need to create the mini-stroke. int[] combinedPackets = new int[previousData.Length + packets.Length]; previousData.CopyTo(combinedPackets, 0); packets.CopyTo(combinedPackets, previousData.Length); // Now that we have the data, we're ready to create the temporary stroke // which we will immediately render. Stroke stroke = null; if (combinedMode) { stroke = this.Ink.CreateStroke(combinedPackets, tabletProperties); } else { stroke = this.Ink.CreateStroke(packets, tabletProperties); } stroke.DrawingAttributes = atts; // Render the stroke onto the canvas. using (Graphics graphics = this.m_SlideDisplay.CreateGraphics()) { IntPtr hdc = graphics.GetHdc(); try { int saved = SaveDC(hdc); try { // Copy the clip region from the slide viewer to the DC. Rectangle bounds = this.m_SlideDisplay.Bounds; IntersectClipRect(hdc, bounds.Left, bounds.Top, bounds.Right, bounds.Bottom); // Draw the ink! this.m_Renderer.Draw(hdc, stroke); } catch (COMException) { //0x80004005. Observed this happening when the public node was in a minimized window //of a remote desktop connection. Ignoring the exception fixes the issue. } finally { RestoreDC(hdc, saved); } } finally { graphics.ReleaseHdc(); } } // Immediately delete the stroke, since there's no easy way to save it and modify it for the next data. this.Ink.DeleteStroke(stroke); } }
/// <summary> /// Notifies the <see cref="Core"/> of the start of a new stroke. /// </summary> /// <remarks> /// Note that some properties of the <see cref="Core"/> take effect only after a <see cref="StylusDown"/> event. /// </remarks> /// <param name="stylusId">The stylus which is being used to draw the stroke.</param> /// <param name="packets">The packet data describing the stroke.</param> /// <param name="tabletProperties"> /// The <see cref="TabletPropertyDescriptionCollection"/> /// used to interpret the packet data, and also subsequent packet data from <see cref="Packets"/> and /// <see cref="StylusUp"/> events which have the same <paramref name="stylusId"/>. /// </param> public void StylusDown(int stylusId, int strokeId, int[] packets, TabletPropertyDescriptionCollection tabletProperties) { using(Synchronizer.Lock(this.m_SlideDisplay.SyncRoot)) { if(!this.Enabled) return; // Insert the data into a hashtable using the stylus id as a key. this.m_PacketsTable[stylusId] = packets; // Also store the current DrawingAttributes. This is necessary to emulate the default // DynamicRenderer which only updates its DrawingAttributes on StylusDown. this.m_DrawingAttributesTable[stylusId] = this.DrawingAttributes; // Store the packets in the collected packets list so we can retrieve them on Refresh(). List<int> collected = new List<int>(packets); this.m_CollectedPacketsTable[stylusId] = collected; // Store the strokeId so RenderIncomingPackets will know when to cancel an incomplete // stroke if a StylusUp event is missed. this.m_CurrentStrokeIdTable[stylusId] = strokeId; // Finally store the tablet properties, which will be used to interpret the packets when rendering a stroke. this.m_TabletPropertiesTable[stylusId] = tabletProperties; } }
public TabletPropertyDescriptionCollection CreateTabletPropertyDescriptionCollection() { TabletPropertyDescriptionCollection tp = new TabletPropertyDescriptionCollection(this.InkToDeviceScaleX, this.InkToDeviceScaleY); foreach(TabletPropertyDescriptionInformation info in this.TabletPropertyDescriptions) tp.Add(info.CreateTabletPropertyDescription()); return tp; }