/// <summary> /// /// </summary> /// <param name="ism"></param> /// <returns></returns> internal object AddImageAnnotation(UW.ClassroomPresenter.Network.Messages.Presentation.ImageSheetMessage ism) { if (ism.SheetCollectionSelector != UW.ClassroomPresenter.Network.Messages.Presentation.SheetMessage.SheetCollection.AnnotationSheets) { //We only support annotation sheets, not content sheets return(null); } if ((ism.Parent == null) || !(ism.Parent is CP3Msgs.SlideInformationMessage)) { warning += "Failed to locate slide for a image sheet. Ignoring Image Annotation. "; return(null); } Guid slideId = (Guid)ism.Parent.TargetId; TableOfContents.TocEntry tocEntry = toc.LookupBySlideId(slideId); if (tocEntry == null) { warning += "Warning: Failed to find table of contents entry for a image annotation. Ignoring the annotation. "; return(null); } //WebViewer wants things scaled to 500x500 (this was a CP2 convention). Rectangle r = ((CP3Msgs.SheetMessage)ism).Bounds; float fX = (float)r.X * 500F / getCurrentSlideWidth(); float fY = (float)r.Y * 500F / getCurrentSlideHeight(); Point scaledOrigin = new Point((int)Math.Round(fX), (int)Math.Round(fY)); int scaledWidth = r.Width * 500 / (int)getCurrentSlideWidth(); int scaledHeight = r.Height * 500 / (int)getCurrentSlideHeight(); RTImageAnnotation rtia = new RTImageAnnotation(scaledOrigin, (Guid)ism.TargetId, tocEntry.DeckId, tocEntry.SlideIndex, scaledWidth, scaledHeight, ism.Img); return(rtia); }
///// <summary> ///// These messages allow us to create a map of slides and md5 hashes. Unused. ///// </summary> ///// <param name="ism"></param> //internal void AddImageSheetMessage(UW.ClassroomPresenter.Network.Messages.Presentation.ImageSheetMessage ism) { // if ((ism.Parent != null) && (ism.Parent.Parent != null) && // (ism.Parent is CP3Msgs.SlideInformationMessage) && // (ism.Parent.Parent is CP3Msgs.DeckInformationMessage)) { // Debug.WriteLine("ImageSheetMessage: sheet=" + ism.TargetId.ToString() + ";slide=" + ism.Parent.TargetId.ToString() + // ";deck=" + ism.Parent.Parent.TargetId.ToString() + ";hash=" + ism.MD5.ToString()); // } //} /// <summary> /// Text annotation add or update operation. /// </summary> /// <param name="textSheetMessage"></param> /// <returns></returns> internal object AddTextSheet(UW.ClassroomPresenter.Network.Messages.Presentation.TextSheetMessage tsm) { if ((tsm.Parent == null) || !(tsm.Parent is CP3Msgs.SlideInformationMessage)) { warning += "Failed to locate slide for a text sheet. Ignoring Text Annotation. "; return(null); } Guid slideId = (Guid)tsm.Parent.TargetId; TableOfContents.TocEntry tocEntry = toc.LookupBySlideId(slideId); if (tocEntry == null) { warning += "Warning: Failed to find table of contents entry for a text annotation. Ignoring the annotation. "; return(null); } //WebViewer wants things scaled to 500x500 (this was a CP2 convention). Rectangle r = ((CP3Msgs.SheetMessage)tsm).Bounds; float fX = (float)r.X * 500F / getCurrentSlideWidth(); float fY = (float)r.Y * 500F / getCurrentSlideHeight(); Point scaledOrigin = new Point((int)Math.Round(fX), (int)Math.Round(fY)); Font scaledFont = new Font(tsm.font_.FontFamily, tsm.font_.Size * 500F / getCurrentSlideWidth(), tsm.font_.Style); int scaledWidth = r.Width * 500 / (int)getCurrentSlideWidth(); int scaledHeight = r.Height * 500 / (int)getCurrentSlideHeight(); RTTextAnnotation rtta = new RTTextAnnotation(scaledOrigin, scaledFont, tsm.color_, tsm.Text, (Guid)tsm.TargetId, tocEntry.DeckId, tocEntry.SlideIndex, scaledWidth, scaledHeight); return(rtta); }
/// <summary> /// return a list of rtDrawStroke messages if ink has been cached for this slide, else null. /// The expectation is that the toc entry for slideId does exist before this is called. /// </summary> /// <param name="slideId"></param> private List <object> getCachedStrokes(Guid slideId) { List <object> outputMessages = null; TableOfContents.TocEntry tocEntry = toc.LookupBySlideId(slideId); if (tocEntry == null) { Debug.WriteLine("Warning: getCachedStrokes failed to find TOC entry."); return(null); } if (this.pendingInk.ContainsKey(slideId)) { foreach (Ink ink in pendingInk[slideId]) { if (ink.Strokes.Count <= 0) { continue; } Guid strokeId = Guid.NewGuid(); //Pull out the identifier which is used if we need to delete the stroke later: if (ink.Strokes[0].ExtendedProperties.DoesPropertyExist(StrokeIdExtendedProperty)) { strokeId = new Guid((string)ink.Strokes[0].ExtendedProperties[StrokeIdExtendedProperty].Data); } else { Debug.WriteLine("Warning: Failed to find stroke Id."); } //WebViewer looks for the CP2 extended property, so add it too. ink.Strokes[0].ExtendedProperties.Add(CP2StrokeIdExtendedProperty, (object)strokeId.ToString()); //WebViewer wants ink to be scaled to 500x500 ink.Strokes.Scale(500f / getCurrentSlideWidth(), 500f / getCurrentSlideHeight()); RTDrawStroke rtds = new RTDrawStroke(ink, strokeId, true, tocEntry.DeckId, tocEntry.SlideIndex); //Add the stroke to our list to optimize deletes if (!strokeCountsBySlideId.ContainsKey(slideId)) { strokeCountsBySlideId.Add(slideId, 1); } else { strokeCountsBySlideId[slideId]++; } if (outputMessages == null) { outputMessages = new List <object>(); } outputMessages.Add(rtds); Debug.WriteLine("***** getCachedStrokes:Adding Stroke ID=" + strokeId.ToString()); } pendingInk.Remove(slideId); } return(outputMessages); }
/// <summary> /// On stylus-up we send a message to delete real-time ink. Note we wouldn't need to do this if we could map real-time ink /// to completed strokes. /// </summary> /// <param name="rtissu"></param> /// <returns></returns> internal object AddRealTimeInkSheetStylusUp(UW.ClassroomPresenter.Network.Messages.Presentation.RealTimeInkSheetStylusUpMessage rtissu) { //Debug.WriteLine("***** Realtime Ink stylus Up StrokeID=" + rtissu.StrokeId.ToString() + "; stylusId=" + rtissu.StylusId.ToString()); //Resolve sheetId to slideID, then use slideId to get the TOC entry. if (!this.sheetToSlideLookup.ContainsKey(rtissu.TargetId)) { if (currentSlideId.Equals(Guid.Empty)) { warning += "Warning: Failed to find slide for a sheet which was the target of a stylus-up message. May result in stray ink. "; return(null); } //Can we assume current slide?? Probably.. sheetToSlideLookup.Add(rtissu.TargetId, currentSlideId); } Guid slideId = (Guid)this.sheetToSlideLookup[rtissu.TargetId]; TableOfContents.TocEntry tocEntry = toc.LookupBySlideId(slideId); if (tocEntry == null) { warning += "Warning: Failed to find table of contents entry for sheet which was the target of a stylus-up message. May result in stray ink. "; return(null); } RTStrokeData rtsData; if (!this.realTimeStrokesPending.TryGetValue(rtissu.StrokeId, out rtsData)) { //warning += "Warning: Failed to find stroke ID for a real-time ink stylus-up message. May result in stray ink. "; //Note that this seems to happen fairly frequently when using text annotations. I suspect CP3 is sending stylus up //when the annotations are moved, etc. This case has no consequences for stray ink, so we'll just remove the warning for now. Debug.WriteLine("Warning: Failed to find stroke ID for a real-time ink stylus-up message. Could result in stray ink. The warning could be bogus if text annotations were used."); return(null); } Debug.WriteLine("***** Removing Real-time stroke in response to stylus-up. Stroke ID=" + rtsData.StrokeId.ToString()); RTDeleteStroke rtds = rtsData.GetRTDeleteStroke(); this.realTimeStrokesPending.Remove(rtissu.StrokeId); return(rtds); }
/// <summary> /// This is used when deleting annotation sheets: text or image /// </summary> /// <param name="sheetRemovedMessage"></param> /// <returns></returns> internal object AddSheetRemoved(UW.ClassroomPresenter.Network.Messages.Presentation.SheetRemovedMessage srm) { if ((srm.Parent == null) || !(srm.Parent is CP3Msgs.SlideInformationMessage)) { warning += "Failed to locate slide for a annotation sheet. Ignoring Text/image deletion "; return(null); } Guid slideId = (Guid)srm.Parent.TargetId; TableOfContents.TocEntry tocEntry = toc.LookupBySlideId(slideId); if (tocEntry == null) { warning += "Warning: Failed to find table of contents entry for a text annotation. Ignoring the annotation. "; return(null); } RTDeleteAnnotation rtdta = new RTDeleteAnnotation((Guid)srm.TargetId, tocEntry.DeckId, tocEntry.SlideIndex); return(rtdta); }
/// <summary> /// When a new quickpoll is started we get this with parents: QuickPollInformationMessage, Slide..,Deck..,Presentation.. /// The SheetMessage base class has dimensions, but they appear to all be zeros. /// </summary> /// <param name="quickPollSheetMessage"></param> /// <returns></returns> internal object AddQuickPollSheet(UW.ClassroomPresenter.Network.Messages.Presentation.QuickPollSheetMessage qpsm) { if ((qpsm.Parent is CP3Msgs.QuickPollInformationMessage) && (qpsm.Parent.Parent is CP3Msgs.SlideInformationMessage) && (qpsm.Parent.Parent.Parent is CP3Msgs.DeckInformationMessage) && (qpsm.Parent.Parent.Parent.Parent is CP3Msgs.PresentationInformationMessage)) { CP3Msgs.QuickPollInformationMessage qpim = (CP3Msgs.QuickPollInformationMessage)qpsm.Parent; CP3Msgs.SlideInformationMessage sim = (CP3Msgs.SlideInformationMessage)qpsm.Parent.Parent; CP3Msgs.DeckInformationMessage dim = (CP3Msgs.DeckInformationMessage)qpsm.Parent.Parent.Parent; CP3Msgs.QuickPollMessage qpm = (CP3Msgs.QuickPollMessage)qpim; // Note: OriginalSlideId is not in the TOC. What is that?? //TableOfContents.TocEntry te = toc.LookupBySlideId(qpModel.OriginalSlideId); /// sim.TargetID seems to give us a good TOC entry for the quickpoll /// slide with association information filled in correctly: TableOfContents.TocEntry qptoc = toc.LookupBySlideId((Guid)sim.TargetId); if (qptoc == null) { Debug.WriteLine("***Failed to find slide for QuickPoll Sheet!"); return(null); } m_QuickPollAggregator.AddQuickPoll(qpm.Model); toc.AddQuickPollIdForSlide((Guid)sim.TargetId, qpm.Model.Id); //Send the initial RtQuickPoll with empty results int[] results = new int[0]; ArchiveRTNav.RTQuickPoll rtqp = new ArchiveRTNav.RTQuickPoll((ArchiveRTNav.QuickPollStyle)qpm.Model.PollStyle, results, qptoc.DeckId, qptoc.SlideIndex); return(rtqp); } else { Debug.WriteLine("****Unexpected QuickPollSheetMessage: " + qpsm.ToString()); } return(null); }
/// <summary> /// When there is a vote we get this with parents: QuickPollInformationMessage, Presentation.. /// Contains a owner ID and a result string such as "C" or "Yes". /// Presumably the owner ID is the id of the client, so this is how we would know if a client changed his vote. /// </summary> /// <param name="quickPollResultInformationMessage"></param> /// <returns></returns> internal object AddQuickResultInformation(UW.ClassroomPresenter.Network.Messages.Presentation.QuickPollResultInformationMessage qprim) { if (qprim.Parent is CP3Msgs.QuickPollInformationMessage) { CP3Msgs.QuickPollInformationMessage qpim = (CP3Msgs.QuickPollInformationMessage)qprim.Parent; TableOfContents.TocEntry qptoc = toc.LookupByQuickPollId((Guid)qpim.TargetId); if (qptoc == null) { Debug.WriteLine("***QuickPoll Result received for unknown QuickPoll!!"); return(null); } int[] currentvotes = m_QuickPollAggregator.AcceptResult(qprim.Result, (Guid)qpim.TargetId); ArchiveRTNav.RTQuickPoll rtqp = new ArchiveRTNav.RTQuickPoll((ArchiveRTNav.QuickPollStyle)qpim.Model.PollStyle, currentvotes, qptoc.DeckId, qptoc.SlideIndex); return(rtqp); } else { Debug.WriteLine("****Unexpected QuickPollResultInformation Message."); } return(null); }
/// <summary> /// Translate real-time ink packets to a stroke and return RTStrokeAdded. /// </summary> /// <param name="rtispm"></param> /// <returns></returns> internal object AddRealTimeInkSheetPackets(UW.ClassroomPresenter.Network.Messages.Presentation.RealTimeInkSheetPacketsMessage rtispm) { //Resolve the sheetId to a slideId, and use it to look up a TOC entry. if (!sheetToSlideLookup.ContainsKey(rtispm.TargetId)) { if (this.currentSlideId.Equals(Guid.Empty)) { warning += "Warning: found real-time ink on an unknown sheet. Ignoring the ink. "; return(null); } //Can we assume current slide?? Probably.. sheetToSlideLookup.Add(rtispm.TargetId, currentSlideId); } TableOfContents.TocEntry tocEntry = toc.LookupBySlideId((Guid)sheetToSlideLookup[rtispm.TargetId]); if (tocEntry == null) { warning += "Warning: Failed to find a TOC entry for a slide when applying real-time ink. Ignoring the ink. "; return(null); } //Tablet Properties should have been received in a StylusDown message. if (previousTabletProperties == null) { warning += "Warning: Received real-time ink without tablet properties. Ignoring the ink. "; return(null); } //Debug.WriteLine("***** Realtime Ink packets StrokeID=" + rtispm.StrokeId.ToString() + "; stylusId=" + rtispm.StylusId.ToString()); // 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.) if (this.previousRealTimeStroke != rtispm.StrokeId) { previousRealTimeStroke = rtispm.StrokeId; previousRealTimePackets = new int[] { }; } // Get the DrawingAttributes which were in effect on StylusDown. We should have received this in a // RealTimeInkSheetInformationMessage previously. if (!this.sheetToDrawingAttributesLookup.ContainsKey(rtispm.TargetId)) { //Note: this seems to happen all the time, but I don't notice any ill effects. Ignore the ink but leave out the warning. //this.warning += "Warning: Real-time ink was found that lacks DrawingAttributes. The ink will be ignored. "; return(null); } DrawingAttributes atts = (DrawingAttributes)this.sheetToDrawingAttributesLookup[rtispm.TargetId]; // Ink packets for this stroke so far. Initial packets should have been received in the Stylus Down message. if (this.previousRealTimePackets == null) { this.warning += "Warning: Failed to find previous real-time ink packets. The ink will be ignored. "; return(null); } // Assemble the completed information we'll need to create the mini-stroke. int[] combinedPackets = new int[this.previousRealTimePackets.Length + rtispm.Packets.Length]; this.previousRealTimePackets.CopyTo(combinedPackets, 0); rtispm.Packets.CopyTo(combinedPackets, this.previousRealTimePackets.Length); // Store the new data. this.previousRealTimePackets = combinedPackets; // Now that we have the data, we're ready to create the temporary stroke. Ink ink = new Ink(); Stroke stroke = ink.CreateStroke(combinedPackets, previousTabletProperties); stroke.DrawingAttributes = atts; //Look up the data for this stroke, or assign a new Guid if needed. RTStrokeData rtsData; if (!realTimeStrokesPending.TryGetValue(rtispm.StrokeId, out rtsData)) { rtsData = new RTStrokeData(Guid.NewGuid(), tocEntry.DeckId, tocEntry.SlideIndex); realTimeStrokesPending.Add(rtispm.StrokeId, rtsData); } Guid strokeId = rtsData.StrokeId; //WebViewer requires the CP2 extended property to allow deletion of the stroke ink.Strokes[0].ExtendedProperties.Add(CP2StrokeIdExtendedProperty, (object)strokeId.ToString()); //WebViewer wants ink to be scaled to 500x500 ink.Strokes.Scale(500f / getCurrentSlideWidth(), 500f / getCurrentSlideHeight()); Debug.WriteLine("***** Adding Real-time Stroke ID=" + strokeId.ToString()); RTDrawStroke rtds = new RTDrawStroke(ink, strokeId, false, tocEntry.DeckId, tocEntry.SlideIndex); return(rtds); }
/// <summary> /// Return one or more RTDeleteStroke messages. /// </summary> /// <param name="issdm"></param> /// <returns></returns> internal List <object> AddInkSheetStrokesDeleting(UW.ClassroomPresenter.Network.Messages.Presentation.InkSheetStrokesDeletingMessage issdm) { //Resolve the SheetId to a slideId if (!sheetToSlideLookup.ContainsKey(issdm.TargetId)) { if (currentSlideId.Equals(Guid.Empty)) { warning += "Warning: Failed to lookup slide from sheet during ink erase operation."; return(null); } //Can we assume current slide?? Probably.. sheetToSlideLookup.Add(issdm.TargetId, currentSlideId); } //Use the slideId to get DeckID and Slide index from toc. Guid slideId = (Guid)sheetToSlideLookup[issdm.TargetId]; TableOfContents.TocEntry tocEntry = toc.LookupBySlideId(slideId); if (tocEntry == null) { warning += "Warning: InkSheetStrokesDeleted does not have a Toc entry. Ignoring erase. "; return(null); } if (issdm.StrokeIds.Length == 0) { return(null); } List <object> outputMessages = new List <object>(); //If more than one stroke, and count matches the total we have recorded for this slide, send one // message to erase all strokes. if (issdm.StrokeIds.Length > 1) { if (strokeCountsBySlideId.ContainsKey(slideId)) { if (strokeCountsBySlideId[slideId] == issdm.StrokeIds.Length) { strokeCountsBySlideId[slideId] = 0; RTEraseLayer rtel = new RTEraseLayer(tocEntry.DeckId, tocEntry.SlideIndex); Trace.WriteLine("*****Returning RTEraseLayer deck=" + tocEntry.DeckId.ToString() + ";slide=" + tocEntry.SlideIndex.ToString()); outputMessages.Add(rtel); //note: this message also takes care of any stray RT strokes, so clear this list: this.realTimeStrokesPending.Clear(); return(outputMessages); } } } //If there are any stray real-time strokes, delete them here. foreach (RTStrokeData rtsd in this.realTimeStrokesPending.Values) { outputMessages.Add(rtsd.GetRTDeleteStroke()); Debug.WriteLine("***** Deleting stray real-time stroke id=" + rtsd.StrokeId.ToString()); } this.realTimeStrokesPending.Clear(); //Delete individual strokes as indicated foreach (string s in issdm.StrokeIds) { Guid g = new Guid(s); RTDeleteStroke rtds = new RTDeleteStroke(g, tocEntry.DeckId, tocEntry.SlideIndex); outputMessages.Add(rtds); int strokesRemaining = -1; if ((strokeCountsBySlideId.ContainsKey(slideId)) && (strokeCountsBySlideId[slideId] > 0)) { strokeCountsBySlideId[slideId]--; strokesRemaining = strokeCountsBySlideId[slideId]; } Debug.WriteLine("***** Deleting static stroke id=" + g.ToString() + ";strokes remaining=" + strokesRemaining.ToString()); } return(outputMessages); }
/// <summary> /// This is the message received when there is a completed stroke. Translate to RTDrawStroke. /// </summary> /// <param name="issam"></param> /// <returns></returns> internal RTDrawStroke AddInkSheetStrokesAdded(CP3Msgs.InkSheetStrokesAddedMessage issam) { //Notice that we tend to get a fair number of these messages that have nothing in the SavedInks property.. presenter bug? byte[][] saved = issam.SavedInks; if (saved.Length == 0) { return(null); } if (saved[0].Length == 0) { return(null); } if (saved.Length > 1) { //This does not seem to occur in practice. If it ever does, we need to generate multiple RTDrawStroke messages: warning += "Warning: Valid ink may be ignored because we only support one byte[] per ink message. "; } Ink ink = new Ink(); ink.Load(saved[0]); if (ink.Strokes.Count <= 0) { return(null); } //This message has a targetID identifying a Sheet which we use to look up a toc entry. Debug.WriteLine("***** InkSheetStrokesAdded targetid=" + issam.TargetId.ToString()); if (!sheetToSlideLookup.ContainsKey(issam.TargetId)) { if (issam.SlideId.Equals(Guid.Empty)) { //Don't think this should ever happen. warning += "Warning: InkSheetStrokesAdded does not match a known sheet. Ignoring ink. "; return(null); } sheetToSlideLookup.Add(issam.TargetId, issam.SlideId); } Guid slideId = (Guid)sheetToSlideLookup[issam.TargetId]; //Get DeckID and Slide index from toc. Return RTDrawStroke. TableOfContents.TocEntry tocEntry = toc.LookupBySlideId(slideId); if (tocEntry == null) { //In some cases ink arrives before the TOC entry. // Save the ink to send later when the TOC entry is available. if (!pendingInk.ContainsKey(slideId)) { pendingInk.Add(slideId, new List <Ink>()); } pendingInk[slideId].Add(ink); Debug.WriteLine("InkSheetStrokesAdded does not have a Toc entry. Caching for later."); return(null); } Guid strokeId = Guid.NewGuid(); //Pull out the identifier which is used if we need to delete the stroke later: if (ink.Strokes[0].ExtendedProperties.DoesPropertyExist(StrokeIdExtendedProperty)) { strokeId = new Guid((string)ink.Strokes[0].ExtendedProperties[StrokeIdExtendedProperty].Data); } else { warning += "Warning: Failed to find stroke Id. "; } //WebViewer looks for the CP2 extended property, so add it too. ink.Strokes[0].ExtendedProperties.Add(CP2StrokeIdExtendedProperty, (object)strokeId.ToString()); //WebViewer wants ink to be scaled to 500x500 ink.Strokes.Scale(500f / getCurrentSlideWidth(), 500f / getCurrentSlideHeight()); //Debug.WriteLine("***** Adding Stroke ID=" + strokeId.ToString()); RTDrawStroke rtds = new RTDrawStroke(ink, strokeId, true, tocEntry.DeckId, tocEntry.SlideIndex); //Add the stroke to our list to optimize deletes if (!strokeCountsBySlideId.ContainsKey(slideId)) { strokeCountsBySlideId.Add(slideId, 1); } else { strokeCountsBySlideId[slideId]++; } return(rtds); }