/// <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>
        /// Send the current ink as a student submission slide
        /// </summary>
        /// <param name="sender">The object which sent this event, i.e. this class</param>
        /// <param name="args">The parameters for the property</param>
        private void HandleSendSubmission( object sender, PropertyEventArgs args )
        {
            if (this.m_SendingLock) return;

            using (Synchronizer.Lock(SubmissionStatusModel.GetInstance().SyncRoot)) {
                SubmissionStatusModel.GetInstance().SubmissionStatus = SubmissionStatusModel.Status.NotReceived;
            }

            ///declare variables we will be using
            UW.ClassroomPresenter.Network.Messages.Message pres, deck, slide, sheet;
            // Construct the message to send
            using( this.m_Model.Workspace.Lock() ) {
                // Add the presentation
                if( this.m_Model.Workspace.CurrentPresentation == null )
                    return;
                ///the presentation message we will be sending
                pres = new PresentationInformationMessage( this.m_Model.Workspace.CurrentPresentation );
                pres.Group = Groups.Group.Submissions;

                //Add the current deck model that corresponds to this slide deck at the remote location
                if( (~this.m_Model.Workspace.CurrentDeckTraversal) == null )
                    return;
                using( Synchronizer.Lock( (~this.m_Model.Workspace.CurrentDeckTraversal).SyncRoot ) ) {
                    DeckModel dModel = (~this.m_Model.Workspace.CurrentDeckTraversal).Deck;
                    foreach( DeckPairModel match in this.m_Model.Workspace.DeckMatches ) {
                        ///check to see if the decks are the same
                        if( match.LocalDeckTraversal.Deck == (~this.m_Model.Workspace.CurrentDeckTraversal).Deck )
                            dModel = match.RemoteDeckTraversal.Deck;
                    }
                    ///create the deck message from this matched deck
                    deck = new DeckInformationMessage( dModel );
                    ///make the deck a submission type deck.
                    deck.Group = Groups.Group.Submissions;
                    ///tack this message onto the end.
                    pres.InsertChild( deck );

                    ///add the particular slide we're on the to message.
                    if( (~this.m_Model.Workspace.CurrentDeckTraversal).Current == null )
                        return;
                    using( Synchronizer.Lock( (~this.m_Model.Workspace.CurrentDeckTraversal).Current.Slide.SyncRoot ) ) {
                        // Add the Slide Message
                        slide = new StudentSubmissionSlideInformationMessage( (~this.m_Model.Workspace.CurrentDeckTraversal).Current.Slide, Guid.NewGuid(), Guid.NewGuid() );
                        slide.Group = Groups.Group.Submissions;
                        deck.InsertChild( slide );

                        // Find the correct user ink layer to send
                        RealTimeInkSheetModel m_Sheet = null;
                        int count = 0;
                        foreach( SheetModel s in (~this.m_Model.Workspace.CurrentDeckTraversal).Current.Slide.AnnotationSheets ) {
                            if( s is RealTimeInkSheetModel && (s.Disposition & SheetDisposition.Remote) == 0 ) {
                                m_Sheet = (RealTimeInkSheetModel)s;
                                count++;
                            }
                        }

                        // DEBUGGING
                        if( count > 1 )
                            Debug.Assert( true, "Bad Count", "Bad" );

                        // Find the existing ink on the slide
                        Ink extracted;
                        using( Synchronizer.Lock( m_Sheet.Ink.Strokes.SyncRoot ) ) {
                            // Ensure that each stroke has a Guid which will uniquely identify it on the remote side
                            foreach( Stroke stroke in m_Sheet.Ink.Strokes ) {
                                if( !stroke.ExtendedProperties.DoesPropertyExist( InkSheetMessage.StrokeIdExtendedProperty ) )
                                    stroke.ExtendedProperties.Add( InkSheetMessage.StrokeIdExtendedProperty, Guid.NewGuid().ToString() );
                            }
                            // Extract all of the strokes
                            extracted = m_Sheet.Ink.ExtractStrokes( m_Sheet.Ink.Strokes, ExtractFlags.CopyFromOriginal );
                        }

                        // Find the Realtime ink on the slide
                        RealTimeInkSheetModel newSheet = null;
                        using( Synchronizer.Lock( m_Sheet.SyncRoot ) ) {
                            newSheet = new RealTimeInkSheetModel( Guid.NewGuid(), m_Sheet.Disposition | SheetDisposition.Remote, m_Sheet.Bounds );
                            using( Synchronizer.Lock( newSheet.SyncRoot ) ) {
                                newSheet.CurrentDrawingAttributes = m_Sheet.CurrentDrawingAttributes;
                            }
                        }

                        // Add a message to *create* the student's RealTimeInkSheetModel on the instructor client (without any ink).
                        sheet = SheetMessage.ForSheet(newSheet, SheetMessage.SheetCollection.AnnotationSheets);
                        sheet.Group = Groups.Group.Submissions;
                        slide.InsertChild(sheet);

                        //Scale the ink if necessary
                        if (ViewerStateModel.NonStandardDpi) {
                            extracted.Strokes.Transform(ViewerStateModel.DpiNormalizationSendMatrix);
                        }

                        // Add a message to copy the ink from the student's RealTimeInkSheetModel to the just-created sheet on the instructor.
                        sheet = new InkSheetStrokesAddedMessage(newSheet, (Guid)slide.TargetId, SheetMessage.SheetCollection.AnnotationSheets, extracted);
                        sheet.Group = Groups.Group.Submissions;
                        slide.InsertChild( sheet );

                        ///Add each text and image sheet into the message as children of the ink sheet if it is public
                        foreach( SheetModel s in (~this.m_Model.Workspace.CurrentDeckTraversal).Current.Slide.AnnotationSheets ) {
                            if( s is TextSheetModel && !(s is StatusLabel) && (s.Disposition & SheetDisposition.Remote) == 0) {
                                TextSheetModel text_sheet = (TextSheetModel) s;
                                text_sheet = (TextSheetModel) text_sheet.Clone();
                                ///some ugly code here due to synchronization
                                bool sheet_is_public;
                                using (Synchronizer.Lock(text_sheet.SyncRoot)) {
                                    sheet_is_public = text_sheet.IsPublic;
                                }
                                if (sheet_is_public) {
                                    TextSheetMessage t_message = new TextSheetMessage(text_sheet, SheetMessage.SheetCollection.AnnotationSheets);
                                    t_message.Group = Groups.Group.Submissions;
                                    slide.InsertChild(t_message);
                                }
                            }
                            if (s is ImageSheetModel && !(s is StatusLabel) && (s.Disposition & SheetDisposition.Remote) == 0) {
                                ImageSheetModel image_sheet = (ImageSheetModel)s;
                                image_sheet = (ImageSheetModel) image_sheet.Clone();
                                ImageSheetMessage i_message = new ImageSheetMessage(image_sheet, SheetMessage.SheetCollection.AnnotationSheets);
                                i_message.Group = Groups.Group.Submissions;
                                slide.InsertChild(i_message);

                            }
                        }
                        //Lock the current sending.
                        this.LockSending();

                        // Send the message
                        this.m_Sender.Send(pres);
                    }
                }
            }
        }
        /// <summary>
        /// Interpret a subset of CP3 message types
        /// May return null, a single RTObject, or a List<object> containing multiple RTObjects
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        private object ProcessMessage(UW.ClassroomPresenter.Network.Messages.Message m)
        {
            if (m is CP3Msgs.InkSheetStrokesAddedMessage)
            {
                return(m_Cache.AddInkSheetStrokesAdded((CP3Msgs.InkSheetStrokesAddedMessage)m));
            }
            else if (m is CP3Msgs.TableOfContentsEntryMessage)
            {
                return(m_Cache.AddTableOfContentsEntry((CP3Msgs.TableOfContentsEntryMessage)m));
            }
            else if (m is CP3Msgs.InkSheetStrokesDeletingMessage)
            {
                return(m_Cache.AddInkSheetStrokesDeleting((CP3Msgs.InkSheetStrokesDeletingMessage)m));
            }
            else if (m is CP3Msgs.SlideDeckTraversalMessage)
            {
                return(m_Cache.AddSlideDeckTraversal((CP3Msgs.SlideDeckTraversalMessage)m));
            }
            else if (m is CP3.Network.Messages.Network.InstructorCurrentDeckTraversalChangedMessage)
            {
                return(m_Cache.AddInstructorCurrentDeckTraversalChanged((CP3.Network.Messages.Network.InstructorCurrentDeckTraversalChangedMessage)m));
            }
            else if (m is CP3Msgs.RealTimeInkSheetInformationMessage)
            {
                m_Cache.AddRealTimeInkSheetInformation((CP3Msgs.RealTimeInkSheetInformationMessage)m);
            }
            else if (m is CP3Msgs.RealTimeInkSheetPacketsMessage)
            {
                return(m_Cache.AddRealTimeInkSheetPackets((CP3Msgs.RealTimeInkSheetPacketsMessage)m));
            }
            else if (m is CP3Msgs.RealTimeInkSheetStylusDownMessage)
            {
                m_Cache.AddRealTimeInkSheetStylusDown((CP3Msgs.RealTimeInkSheetStylusDownMessage)m);
            }
            else if (m is CP3Msgs.RealTimeInkSheetStylusUpMessage)
            {
                return(m_Cache.AddRealTimeInkSheetStylusUp((CP3Msgs.RealTimeInkSheetStylusUpMessage)m));
            }
            else if (m is CP3Msgs.SlideInformationMessage)
            {
                return(m_Cache.UpdateSlideInformation((CP3Msgs.SlideInformationMessage)m));
            }
            else if (m is CP3Msgs.DeckInformationMessage)
            {
                return(m_Cache.UpdateDeckInformation((CP3Msgs.DeckInformationMessage)m));
            }
            else if (m is CP3Msgs.TextSheetMessage)
            {
                return(m_Cache.AddTextSheet((CP3Msgs.TextSheetMessage)m));
            }
            else if (m is CP3Msgs.SheetRemovedMessage)
            {
                return(m_Cache.AddSheetRemoved((CP3Msgs.SheetRemovedMessage)m));
            }
            else if (m is CP3Msgs.PresentationMessage)
            {
                return(m_Cache.UpdatePresentaton((CP3Msgs.PresentationMessage)m));
            }
            else if (m is CP3Msgs.QuickPollSheetMessage)
            {
                //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.
                return(m_Cache.AddQuickPollSheet((CP3Msgs.QuickPollSheetMessage)m));
            }
            else if (m is CP3Msgs.QuickPollResultInformationMessage)
            {
                //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.
                return(m_Cache.AddQuickResultInformation((CP3Msgs.QuickPollResultInformationMessage)m));
            }
            else if (m is CP3Msgs.ImageSheetMessage)
            {
                CP3Msgs.ImageSheetMessage ism = (CP3Msgs.ImageSheetMessage)m;
                if (ism.SheetCollectionSelector ==
                    UW.ClassroomPresenter.Network.Messages.Presentation.SheetMessage.SheetCollection.AnnotationSheets)
                {
                    //Annotation sheets are the ones added on-the-fly.
                    return(m_Cache.AddImageAnnotation(ism));
                }
                else
                {
                    //Content sheets: These are found in slide broadcasts.
                }
            }
            else if (m is CP3Msgs.QuickPollInformationMessage)
            {
                //We see one of these with a PresentationInformationMessage parent at various times.  It also appears in
                //the heirarchy along with some other messages.  It's not clear if we need to pay attention to this
                //because we have the QuickPollInformationMessage in the heirarchy with QuickPollSheetMessage.
                //Debug.WriteLine("QuickPollInformationMessage");
            }
            else if (m is CP3Msgs.QuickPollResultRemovedMessage)
            {
                //Appears to be unused?
                //Debug.WriteLine("QuickPollResultRemovedMessage");
            }
            else if (m is CP3Msgs.XPSPageSheetMessage)
            {
                //Debug.WriteLine("XPSPageSheetMessage");
            }

            return(null);
        }