/// <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); }