Ejemplo n.º 1
0
        /// <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);
        }
Ejemplo n.º 2
0
        ///// <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);
        }
Ejemplo n.º 3
0
        /// <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);
        }
Ejemplo n.º 4
0
        /// <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);
        }
Ejemplo n.º 5
0
        /// <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);
        }
Ejemplo n.º 6
0
        /// <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);
        }
Ejemplo n.º 7
0
        /// <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);
        }
Ejemplo n.º 8
0
        /// <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);
        }
Ejemplo n.º 9
0
        /// <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);
        }
Ejemplo n.º 10
0
        /// <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);
        }