Пример #1
0
        public static void Start(ClientOrchestrator orchestrator, TriviaViewModel viewModel, CommandBindingCollection bindings, TriviaClient window)
        {
            Contract.Requires(orchestrator != null);

            bindings.Add(new CommandBinding(TriviaCommands.WrongAnswer,
                (object source, ExecutedRoutedEventArgs e) =>
                {
                    AnswerViewModel a = (AnswerViewModel)e.Parameter;

                    a.WhoCalledIn = TriviaClient.PlayerName;

                    orchestrator.SendUpdateAnswerRequest(a.CreateModel(), false);
                }));

            bindings.Add(new CommandBinding(TriviaCommands.CorrectAnswer,
                (object source, ExecutedRoutedEventArgs e) =>
                {
                    CorrectAnswerData cad = (CorrectAnswerData)e.Parameter;

                    AnswerViewModel a = cad.Answer;

                    a.WhoCalledIn = TriviaClient.PlayerName;

                    orchestrator.SendUpdateAnswerRequest(a.CreateModel(), false);

                    QuestionChanges questionChanges = new QuestionChanges(new QuestionId(a.Hour, a.Number))
                    {
                        Open = false,
                        Correct = true,
                        Answer = a.Text,
                        PhoneBankOperator = cad.PhoneBankOperator
                    };

                    // Update the correct answer

                    orchestrator.SendUpdateQuestionRequest(questionChanges);
                }));

            bindings.Add(new CommandBinding(TriviaCommands.PartialAnswer,
                (object source, ExecutedRoutedEventArgs e) =>
                {
                    AnswerViewModel a = (AnswerViewModel)e.Parameter;

                    a.WhoCalledIn = TriviaClient.PlayerName;
                    a.Partial = true;

                    orchestrator.SendUpdateAnswerRequest(a.CreateModel(), true);  // Flag as a partial
                }));

            bindings.Add(new CommandBinding(TriviaCommands.EditAnswer,
                (object source, ExecutedRoutedEventArgs e) =>
                {
                    // An edited answer was already modified by the command sender, so just update it to the server

                    // Note: this is usually done by fixing the text of a partial answer

                    AnswerViewModel a = (AnswerViewModel)e.Parameter;

                    orchestrator.SendUpdateAnswerRequest(a.CreateModel(), false);  // Flag not "partial changed".  We don't know it to be the case, but that's the behavior we want
                }));

            bindings.Add(new CommandBinding(TriviaCommands.ShowSubmitAnswerDialog,
                 (object source, ExecutedRoutedEventArgs e) =>
                 {
                     QuestionViewModel question = (QuestionViewModel)e.Parameter;

                     SubmitAnswerDialog submitAnswerDialog = new SubmitAnswerDialog(question);
                     submitAnswerDialog.Owner = window;

                     bool? submitted = submitAnswerDialog.ShowDialog();

                     if (submitted == true)
                     {
                         // 2014 - don't think this is true, but preserving the comment... Must execute the command against "this" current window so the commandbindings are hit
                         TriviaCommands.SubmitAnswer.Execute(submitAnswerDialog.AnswerSubmitted, null);  // Using "null" as the target means we avoid weird bugs where the event actually doesn't fire
                     }
                 }));

            bindings.Add(new CommandBinding(TriviaCommands.SubmitAnswer,
                (object source, ExecutedRoutedEventArgs e) =>
                {
                    Answer a = (Answer)e.Parameter;

                    QuestionViewModel q;
                   
                    if (!(viewModel.TryGetQuestion(a, out q)))
                    {
                        throw new InvalidOperationException("Couldn't get question for answer submitted, that's really odd...");
                    }

                    if (ContestConfiguration.PreventDuplicateAnswerSubmission && 
                        CommandHandler.ShouldStopAnswerSubmission(a, q, window))
                    {
                        return;
                    }

                    orchestrator.SendSubmitAnswerRequest(a);  // Inform the server

                    // If you're submitting an answer, it's likely to assume you're researching the question
                    if (!(q.IsBeingResearched))
                    {
                        q.IsBeingResearched = true;

                        TriviaCommands.ResearcherChanged.Execute(q, null);
                    }
                }));

            bindings.Add(new CommandBinding(TriviaCommands.OpenQuestion,
                (object source, ExecutedRoutedEventArgs e) =>
                {
                    QuestionId questionId = (QuestionId)e.Parameter;

                    orchestrator.SendOpenQuestionRequest(questionId.Hour, questionId.Number);
                }));

            bindings.Add(new CommandBinding(TriviaCommands.EditQuestion,
                (object source, ExecutedRoutedEventArgs e) =>
                {
                    QuestionViewModel question = (QuestionViewModel)e.Parameter;

                    EditingQuestionData data = new EditingQuestionData() 
                    { 
                        HourNumber = question.Identifier.Hour, 
                        QuestionNumber = question.Identifier.Number, 
                        WhoEditing = TriviaClient.PlayerName 
                    };

                    // Tell everyone else that "I'm editing this question, so back off!"
                    orchestrator.SendEditingQuestionMessage(data);

                    EditQuestionDialog dialog = new EditQuestionDialog(orchestrator, question, false);
                    dialog.Owner = window;

                    bool? result = dialog.ShowDialog();

                    if (!(result.HasValue) || result.Value == false)
                    {
                        data.WhoEditing = null; // Signal that the edit was cancelled
                        
                        orchestrator.SendEditingQuestionMessage(data);

                        // If the user made a change to the question (in-memory viewmodel), should refresh from the server (it's just the easiest thing to do)
                        if (dialog.GetChangesMade().Changes != QuestionChanges.Change.None)
                        {
                            orchestrator.SendGetCompleteHourDataRequest(question.Identifier.Hour);
                        }

                        return;  // Cancelled / Closed without saving
                    }

                    QuestionChanges changes = dialog.GetChangesMade();

                    if (changes.Changes == QuestionChanges.Change.None)
                    {
                        return;  // No changes actually were made even though the user clicked the Save button
                    }

                    orchestrator.SendUpdateQuestionRequest(changes);
                }));

            bindings.Add(new CommandBinding(TriviaCommands.UpdateQuestionPoints,
                (object source, ExecutedRoutedEventArgs e) =>
                {
                    // Only Beerpigs use this
                    QuestionChanges changes = (QuestionChanges)e.Parameter;

                    orchestrator.SendUpdateQuestionRequest(changes);
                }));

            bindings.Add(new CommandBinding(TriviaCommands.PlayAudioTrivia,
                (object source, ExecutedRoutedEventArgs e) =>
                {
                    string audioTriviaFileName = (string)e.Parameter;

                    CommandHandler.PlayAudioTrivia(audioTriviaFileName, window);
                }));

            bindings.Add(new CommandBinding(TriviaCommands.ShowAddNoteDialog,
                (object source, ExecutedRoutedEventArgs e) =>
                {
                    QuestionViewModel question = (QuestionViewModel)e.Parameter;

                    Note note = new Note();
                    note.Submitter = TriviaClient.PlayerName;
                    note.HourNumber = question.Identifier.Hour;
                    note.QuestionNumber = question.Identifier.Number;

                    AddNoteDialog dialog = new AddNoteDialog(note);
                    dialog.Owner = window;

                    bool? result = dialog.ShowDialog();

                    if (!(result.HasValue) || result.Value == false)
                    {
                         return;  // Cancelled / Closed without saving
                    }

                    note.Text = note.Text.Trim();  // Trim it.

                    // Save the note
                    orchestrator.SendAddNoteMessage(note);

                    // If you're adding a note, it's likely to assume you're researching the question

                    if (!(question.IsBeingResearched))
                    {
                        question.IsBeingResearched = true;

                        TriviaCommands.ResearcherChanged.Execute(question, null);
                    }
                }));

            bindings.Add(new CommandBinding(TriviaCommands.CorrectAnswerWithoutSubmission,
                (object source, ExecutedRoutedEventArgs e) =>
                {
                    QuestionViewModel question = (QuestionViewModel)e.Parameter;

                    Answer answer = new Answer();
                    answer.Hour = question.Identifier.Hour;
                    answer.Number = question.Identifier.Number;
                    answer.WhoCalledIn = TriviaClient.PlayerName;

                    AnswerViewModel answerViewModel = new AnswerViewModel(answer);

                    // Show the "blank" dialog
                    CorrectAnswerDialog dialog = new CorrectAnswerDialog(answerViewModel);
                    dialog.Owner = window;

                    bool? result = dialog.ShowDialog();

                    if (!(result.HasValue) || result.Value == false)
                    {
                        return;  // Cancelled / Closed without saving
                    }

                    QuestionChanges changes = new QuestionChanges(question.Identifier)
                    {
                        Open = false,
                        Correct = true,
                        Answer = dialog.Answer.Text,
                        PhoneBankOperator = dialog.PhoneBankOperator
                    };

                    // Update the correct answer

                    orchestrator.SendUpdateQuestionRequest(changes);
                }));

            bindings.Add(new CommandBinding(TriviaCommands.ResearcherChanged,
                (object source, ExecutedRoutedEventArgs e) =>
                {
                    QuestionViewModel questionThatChanged = (QuestionViewModel)e.Parameter;

                    if (questionThatChanged.IsBeingResearched == false)
                    {
                        // User said they're not researching this question (and by extension, aren't researching *anything* anymore)
                        orchestrator.SendResearcherChangedRequest(
                            new ResearcherChange
                            {
                                HourNumber = 0,  // Signaling that current user is researching nothing
                                QuestionNumber = 0,  // Signaling that current user is researching nothing
                                Name = TriviaClient.PlayerName
                            });
                    }
                    else
                    {
                        orchestrator.SendResearcherChangedRequest(
                          new ResearcherChange
                          {
                              HourNumber = questionThatChanged.Identifier.Hour,
                              QuestionNumber = questionThatChanged.Identifier.Number,
                              Name = TriviaClient.PlayerName
                          });
                    }
                }));


            bindings.Add(new CommandBinding(TriviaCommands.ShowEditPartialsDialog,
                (object source, ExecutedRoutedEventArgs e) =>
                {
                    ListCollectionView partials = (ListCollectionView)e.Parameter;

                    EditPartialsDialog dialog = new EditPartialsDialog();
                    dialog.DataContext = partials;
                    dialog.Owner = window;

                    dialog.ShowDialog();
                }));

            bindings.Add(new CommandBinding(TriviaCommands.ShowCombinePartialsDialog,
                (object source, ExecutedRoutedEventArgs e) =>
                {
                    QuestionViewModel question = (QuestionViewModel)e.Parameter;

                    string combinationAnswer = string.Join("\n", question.PartialAnswers.Cast<AnswerViewModel>().Select(avm => avm.Text));

                    SubmitAnswerDialog submitAnswerDialog = new SubmitAnswerDialog(question, combinationAnswer);
                    submitAnswerDialog.Owner = window;

                    bool? submitted = submitAnswerDialog.ShowDialog();

                    if (submitted == true)
                    {
                        // 2014, don't think this is true, ubt pres3erving the original comment.....  Must execute the command against "this" current window so the commandbindings are hit
                        TriviaCommands.SubmitAnswer.Execute(submitAnswerDialog.AnswerSubmitted, null);  // Using "null" as the target means we avoid weird bugs where the event actually doesn't fire
                    }
                }));


            bindings.Add(new CommandBinding(TriviaCommands.ShowAddLinkDialog,
                (object source, ExecutedRoutedEventArgs e) =>
                {
                    AddLinkDialog dialog = new AddLinkDialog();
                    dialog.Owner = window;

                    bool? result = dialog.ShowDialog();

                    if (!(result.HasValue) || result.Value == false)
                    {
                        return;  // Cancelled / Closed without saving
                    }

                    dialog.Link.Description = dialog.Link.Description.Trim();
                    dialog.Link.Url = dialog.Link.Url.Trim();

                    // Save the link
                    orchestrator.SendAddLinkRequest(dialog.Link);
                }));            
            bindings.Add(new CommandBinding(TriviaCommands.DeleteNote,
                (object source, ExecutedRoutedEventArgs e) =>
                {
                    orchestrator.SendDeleteNoteMessage((Note)e.Parameter);
                }));
        }
Пример #2
0
        void TriviaClient_Loaded(object sender, RoutedEventArgs e)
        {
            ConnectToServerDialog connectDialog = new ConnectToServerDialog();
            var dialogResult = connectDialog.ShowDialog();

            if (dialogResult == null || dialogResult.Value == false)
            {
                Application.Current.Shutdown(0);
                return;
            }

            this.mOrchestrator = new ClientOrchestrator(connectDialog.ConnectedClient);

            this.mOrchestrator.RemoteDisconnect += mOrchestrator_RemoteDisconnect;

            this.mWindowOriginalTitle = this.Title;  // Cache it since Chats / Answers may change the title

            this.mViewModel = new TriviaViewModel(isContributorOnlyMode: true);

            // Set up class to handle data updates from the server (do this before authentication is done because the auth result will contain data we need to process)
            DataUpdateHandler.Start(this.mOrchestrator, this.mViewModel, this.Dispatcher, this);

            // Make the user authenticate
            string playerNameChosen;

            AuthenticationResponse authResult = this.AuthenticateWithUserAuthentication(out playerNameChosen);
            //AuthenticationResponse authResult = this.AuthenticateWithHardcodedInformation(out playerNameChosen);

            if (authResult == null)
            {
                return;  // AuthResult will be null if the user failed to authenticate successfully
            }

            if (string.IsNullOrWhiteSpace(playerNameChosen))
            {
                throw new InvalidOperationException("No name to set");
            }

            // At this point, the user has authenticated            

            bool isContributorOnlyMode = TriviaSettings.Default.IsContributorOnlyMode2016 && ContestConfiguration.IsContributorModeDefaultValue;
            this.mViewModel.IsContributorOnlyMode = isContributorOnlyMode;
            this.mViewModel.TeamNumber = authResult.TeamNumber;
            TriviaClient.AudioTriviaUrlFormat = authResult.AudioTriviaUrlFormat;  // Set it
            TriviaClient.PlayerName = playerNameChosen;
            TriviaClient.RadioStationStreamUrl = authResult.RadioStationStreamUrl;
            TriviaMessage.SetDefaultSenderName(playerNameChosen);

            this.DataContext = this.mViewModel;

            CommandHandler.Start(this.mOrchestrator, this.mViewModel, this.CommandBindings, this);

            // Active the Chat Control by giving it the orchestrator
            this.mChatControl.SetOrchestrator(this.mOrchestrator);

            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);


            // Go go go!!!

            Note.OnCanBeDeletedCheck += (noteSubmitter) => (isContributorOnlyMode == false) || TriviaClient.PlayerName == noteSubmitter;

            this.mOrchestrator.AlertPlayersNotificationReceived += new ClientOrchestrator.AlertPlayersNotificationEventHandler(mOrchestrator_AlertPlayersNotificationReceived);
            this.mOrchestrator.GetLatestHourResponseReceived += new ClientOrchestrator.GetLatestHourEventHandler(mOrchestrator_GetLatestHourResponseReceived);
            this.mOrchestrator.SubmitAnswerResponseReceived += new ClientOrchestrator.SubmitAnswerEventHandler(mOrchestrator_SubmitAnswerResponseReceived);
            this.mOrchestrator.ChatMessageReceived += mOrchestrator_ChatMessageReceived;

            this.mOrchestrator.SendGetLatestHourRequest();

            this.InitiatePeriodicConnectionCheck();  // Check with the server every so often to make sure the connection is good
        }
Пример #3
0
        private static void LoadStatsIntoViewModel(Stats stats, TriviaViewModel viewModel)
        {
            if (stats.PointsMaximumByHour == null ||
                                stats.PointsMaximumByHour.Length == 0 ||
                                stats.PointsEarnedByHour == null ||
                                stats.PointsEarnedByHour.Length == 0)
            {
                return;  // No data yet... no questions opened
            }

            int maxPointsPossibleInAnyHour = stats.PointsMaximumByHour.Max();

            for (int i = 0; i < stats.PointsEarnedByHour.Length; ++i)
            {
                int hourNumber = i + 1;

                HourViewModel hour = viewModel.GetHour(hourNumber);

                hour.Stats.MaximumTotalPointsPossibleInAnyHour = maxPointsPossibleInAnyHour;
                hour.Stats.TotalPointsEarned = stats.PointsEarnedByHour[i];
                hour.Stats.TotalPointsPossible = stats.PointsMaximumByHour[i];
            }
        }
Пример #4
0
        private static void LoadLinksIntoViewModel(Link[] links, TriviaViewModel viewModel)
        {
            if (links == null)
            {
                return;
            }

            if (viewModel.Links.Count > 0)
            {
                return;  // We've already got them
            }

            foreach (Link link in links)
            {
                viewModel.Links.Add(link);
            }
        }
Пример #5
0
        private static void LoadGlobalDataIntoViewModel(GlobalData globalData, TriviaViewModel viewModel)
        {
            /* 
             * comment below is not true - we load global data unnecesarily repeatedly every time we GetCompleteHourData.  Optimized?  No.  But that's ok.
             * 
            // It really ought to be sufficient to load Global Data once at startup.  Detect duplicate load because that's probably a subtle bug
            if (viewModel.Links.Count > 0)
            {
                throw new InvalidOperationException("Got GlobalData response when we already have Links");
            }
             */

            LoadStatsIntoViewModel(globalData.Stats, viewModel);
            LoadLinksIntoViewModel(globalData.Links, viewModel);
        }
Пример #6
0
        public static void Start(ClientOrchestrator orchestrator, TriviaViewModel viewModel, Dispatcher dispatcher, TriviaClient window)
        {
            Contract.Requires(orchestrator != null);
            Contract.Requires(viewModel != null);

            orchestrator.AuthenticateResponseReceived +=
                delegate (AuthenticationResponse authResponse)
                {
                    // Since as of 2016, this response contains hour/player data, need to process this
                    if (authResponse.CurrentHourData != null)  // Will be null if authentication was unsuccessful
                    {
                        orchestrator.TriggerGetCompleteHourDataResponseReceivedEvent(authResponse.CurrentHourData);
                    }

                    if (authResponse.Players != null)
                    {
                        orchestrator.TriggerPlayersListUpdateReceivedEvent(authResponse.Players);
                    }

                    dispatcher.Invoke(new Action(
                        delegate
                        {
                            // HourViewModel requires being on the UI thread
                            LoadGlobalDataIntoViewModel(authResponse.GlobalData, viewModel);
                        }));
                };
                      
            // Complete Hour
            orchestrator.GetCompleteHourDataResponseReceived +=
                delegate(CompleteHourData chd)
                {
                    dispatcher.Invoke(new Action(
                        delegate
                        {
                            Debug.WriteLine("CompletHourDataResponse for hour " + chd.HourNumber.ToString());

                            HourViewModel hour = viewModel.GetHour(chd.HourNumber);

                            hour.IsNoLongerCurrentHour = chd.IsNoLongerCurrentHour;

                            hour.Questions.Clear();

                            hour.HasRetrievedQuestionsFromServer = true;  // Flag that we've got the latest & greatest

                            ILookup<int, Answer> answerLookup = chd.Answers.ToLookup(a => a.Number);
                            ILookup<int, Note> noteLookup = chd.Notes.ToLookup(n => n.QuestionNumber);

                            // Obviously this could be fancier w/ LINQ, but eh, good enough
                            foreach (Question questionModel in chd.Questions)
                            {
                                var answers = answerLookup[questionModel.Number];
                                var notes = new DispatchableObservableCollection<Note>(noteLookup[questionModel.Number]);

                                QuestionViewModel questionViewModel = new QuestionViewModel(questionModel, answers, notes);

                                hour.Questions.Add(questionViewModel);
                            }
                        }));
                };
            
            // Latest Hour (for informing the player that old hours are no longer active)
            orchestrator.GetLatestHourResponseReceived +=
                delegate(int latestHour)
                {
                    dispatcher.Invoke(new Action(
                        delegate
                        {
                            foreach (HourViewModel hour in viewModel.Hours)
                            {
                                hour.IsNoLongerCurrentHour = hour.Number < latestHour;
                            }
                        }));
                };

            // Questions

            orchestrator.OpenQuestionResponseReceived += delegate (int hourNumber, int questionNumber, bool successful, string failureMessage)
            {
                // BeginInvoke is absolutely key here, otherwise the UI thread will be tied up and messages (such as Start Audio Trivia Recording) won't
                // be able to be processed as long as the UI thread is in use.  Regular old Invoke() will cause this problem.
                dispatcher.BeginInvoke(new Action(
                        delegate
                        {
                            if (!(successful))
                            {
                                MessageBox.Show(failureMessage, string.Format("Question #{0} Was Not Opened", questionNumber), MessageBoxButton.OK, MessageBoxImage.Exclamation);
                                return;  // Nothing to do
                            }

                            HourViewModel hour = viewModel.GetHour(hourNumber);

                            // Boy, I hope that the Update Question response (which creates the question) is received before this is...
                            QuestionViewModel question;

                            if (!(viewModel.TryGetQuestion(hourNumber, questionNumber, out question)))
                            {
                                // Fake it out since we didn't get a real one from the server
                                question = new QuestionViewModel(new Question(hourNumber, questionNumber));

                                hour.Questions.Add(question);
                            }

                            EditQuestionDialog dialog = new EditQuestionDialog(orchestrator, question, true);
                            dialog.Owner = window;

                            bool? result = dialog.ShowDialog();

                            if (!(result.HasValue) || result.Value == false)
                            {
                                return;  // Cancelled / Closed without saving
                            }

                            QuestionChanges changes = dialog.GetChangesMade();

                            if (changes.Changes == QuestionChanges.Change.None)
                            {
                                return;  // No changes actually were made even though the user clicked the Save button
                            }

                            orchestrator.SendUpdateQuestionRequest(changes);
                        }));
            };

            orchestrator.UpdateQuestionResponseReceived +=
                delegate(Question updatedQuestion, bool playerInitatedUpdate)
                {
                    dispatcher.Invoke(new Action(
                        delegate
                        {
                            HourViewModel hour = viewModel.GetHour(updatedQuestion.Hour);

                            // If the question is just being opened, the first we hear of this question is the Update message

                            QuestionViewModel wrapperToUpdate;

                            if (!(viewModel.TryGetQuestion(updatedQuestion.Identifier, out wrapperToUpdate)))
                            {
                                // A brand new opened question
                                QuestionViewModel questionToAdd = new QuestionViewModel(updatedQuestion);

                                hour.Questions.Add(questionToAdd);
                                return;
                            }

                            ToastFactory.ShowAppropriateToast(viewModel.GetHour(updatedQuestion.Hour), wrapperToUpdate, updatedQuestion);

                            if (playerInitatedUpdate)  // If an automatic process updated this question (such as Predicted Duration change), a person could still be editing
                            {
                                wrapperToUpdate.PersonCurrentlyEditingThisQuestion = null;  // Flag that there's (likely) nobody else Editing this question
                            }

                            wrapperToUpdate.Update(updatedQuestion, playerInitatedUpdate);
                        }));
                };

            orchestrator.EditingQuestionMessageReceived +=
                delegate(EditingQuestionData data)
                {
                    dispatcher.Invoke(new Action(
                        delegate
                        {
                            HourViewModel hour = viewModel.GetHour(data.HourNumber);

                            // If the question is just being opened, the first we hear of this question is the Update message
                            QuestionViewModel questionBeingUpdated;

                            if (!(viewModel.TryGetQuestion(data.HourNumber, data.QuestionNumber, out questionBeingUpdated)))
                            {
                                return;  // Odd that we can't find it, but this message isn't super-important so just move on
                            }

                            questionBeingUpdated.PersonCurrentlyEditingThisQuestion = data.WhoEditing;
                        }));
                };

            // Answers

            orchestrator.UpdateAnswerResponseReceived +=
                delegate(Answer answer, bool partialChanged)
                {
                    dispatcher.Invoke(new Action(
                        delegate
                        {
                            //Debug.WriteLine(string.Format("UpdateAnswer.  Number:{0}.  IsPartial:{1}.  PartialChanged:{2}.  HasBeenCalledIn:{3}", answer.Number, answer.Partial, partialChanged, answer.HasBeenCalledIn));

                            QuestionViewModel question;

                            if (!(viewModel.TryGetQuestion(answer, out question)))
                            {
                                // If we can't find it - it's assumed to be for an hour that's not retrievd from the server yet.
                                return;  
                            }

                            if (partialChanged && answer.Partial)
                            {
                                // Toasty!
                                ToastFactory.ShowToastForPartial(answer);
                            }

                            AnswerViewModel storedAnswer = question.SubmittedAnswers.SingleOrDefault(a => a.ID == answer.ID);

                            if (storedAnswer != null)  // Wouldn't expect it to be null, but in 2014 it was null in the wild one random time
                            {
                                // Make the update
                                storedAnswer.Update(answer);
                            }

                            // If it changed partial status, or it's already a partial (and edited to change the text)
                            if (partialChanged || answer.Partial)
                            {
                                if (IsOneWordAnswer(answer.Text))
                                {
                                    // The fact that this answer changed partial status doesn't affect anything else, ignore
                                    // because we only care about one-word answers
                                    question.HighlightAppearanceOfPartialsInAnswers();
                                }
                            }
                        }));
                };

            orchestrator.SubmitAnswerResponseReceived +=
                delegate(Answer answer)
                {
                    //  viewModel.TryGetQuestion() eventually could trigger a new Hour to be created, which relies on the UI thread (Hours view)
                    dispatcher.Invoke(new Action(
                        delegate
                        {
                            QuestionViewModel question;

                            if (!(viewModel.TryGetQuestion(answer, out question)))
                            {
                                // If we can't find it - it's assumed to be for an hour that's not retrievd from the server yet.
                                return;
                            }

                            AnswerViewModel answerViewModel = new AnswerViewModel(answer);

                            question.SubmittedAnswers.Add(answerViewModel);

                            question.HighlightAppearanceOfPartialInThisAnswer(answerViewModel);

                            /*  // couldn't finish this in 2011.
                            // To keep track of how many of your personal answers are in the queue
                            viewModel.GetHour(question.Identifier.Hour).AddAnswerSubmittedByCurrentUser(answerViewModel);
                             */
                        }));
                };

            orchestrator.AddNoteMessageReceived +=
                delegate(Note note)
                {
                    QuestionViewModel question;

                    if (!(viewModel.TryGetQuestion(note.HourNumber, note.QuestionNumber, out question)))
                    {
                        // If we can't find it - it's assumed to be for an hour that's not retrievd rom the server yet.
                        return;
                    }

                    question.Notes.Add(note);
                };
            orchestrator.DeleteNoteMessageReceived +=
                delegate (int noteId)
                {
                    var allHoursStartingWithCurrent = new HourViewModel[] { viewModel.ActiveHour }.Concat(viewModel.Hours.Cast<HourViewModel>());
                    var allQuestionsStartingWithCurrentHour = allHoursStartingWithCurrent.SelectMany(hour => hour.Questions);
                    var firstMatchingNote = allQuestionsStartingWithCurrentHour.SelectMany(q => q.Notes).Where(note => note.ID == noteId).FirstOrDefault();

                    if (firstMatchingNote == null)
                    {
                        // Someone must have deleted a note that's not in the Client's cache of questions yet...
                        return;
                    }

                    QuestionViewModel question;

                    bool found = viewModel.TryGetQuestion(firstMatchingNote.HourNumber, firstMatchingNote.QuestionNumber, out question);

                    if (!(found))
                    {
                        return; // Question not currently loaded by the client yet?  No biggie, it'll get the latest values later when it is loaded
                    }

                    question.Notes.Remove(firstMatchingNote);
                };
            /* dead code?
            orchestrator.GetGlobalDataResponseReceived += (globalData) =>
                {
                    dispatcher.Invoke(new Action(
                        delegate
                        {
                            LoadGlobalDataIntoViewModel(globalData, viewModel);
                        }));
                };
                */
            orchestrator.CurrentCallerChangedMessageReceived += (currentCaller) =>
                {
                    viewModel.CurrentCaller = currentCaller;
                };

            orchestrator.ResearcherChangedResponseReceived += (changes) =>
            {
                dispatcher.Invoke(new Action(
                    delegate
                    {
                        Debug.WriteLine("ResearcherChanged response received");

                        // First, remove this researcher from any (if applicable) questions they were researching previously
                        var allHoursStartingWithCurrent = new HourViewModel[] { viewModel.ActiveHour }.Concat(viewModel.Hours.Cast<HourViewModel>());
                        var allQuestionsStartingWithCurrentHour = allHoursStartingWithCurrent.SelectMany(hour => hour.Questions);
                        var previouslyResearchedQuestion = allQuestionsStartingWithCurrentHour.Where(q => q.Researchers.Contains(changes.Name)).FirstOrDefault();

                        Debug.WriteLine(string.Format("PreviouslyRsearchedQuestion: {0}", previouslyResearchedQuestion == null ? "null" : previouslyResearchedQuestion.Identifier.Number.ToString()));

                        if (previouslyResearchedQuestion != null)
                        {
                            // They are no longer researching this question
                            previouslyResearchedQuestion.Researchers.Remove(changes.Name);

                            // If the "person" is the current user, need to uncheck the checkbox
                            if (changes.Name == TriviaClient.PlayerName)
                            {
                                previouslyResearchedQuestion.IsBeingResearched = false;
Debug.WriteLine(string.Format("Unchecking checkbox for: {0}", previouslyResearchedQuestion.Identifier.Number.ToString()));
                            }
                        }

                        QuestionViewModel question;

                        bool found = viewModel.TryGetQuestion(changes.HourNumber, changes.QuestionNumber, out question);

                        if (!(found))
                        {
                            return; // Question not currently loaded by the client yet?  No biggie, it'll get the latest values later when it is loaded
                        }

                        question.Researchers.Add(changes.Name);  // Add to end of list

                        Debug.WriteLine(string.Format("{0} added to researchers of question: {1}", changes.Name, question.Identifier.Number));

                    }), DispatcherPriority.Background); // ResearcherChanged tends to trigger some slow things, so make it a low priority since it's nonessential
            };

            // Links

            orchestrator.AddLinkResponseReceived += (link) =>
            {
                dispatcher.Invoke(new Action(
                    delegate
                    {
                        viewModel.Links.Add(link);
                    }));
            };

            // Players
            orchestrator.PlayersListUpdateReceived += (IEnumerable<Player> players) =>
            {
                dispatcher.Invoke(new Action(
                    delegate
                    {
                        
                        viewModel.ConnectedPlayers.Clear();
                        viewModel.RecentlyJoinedPlayers.Clear();
                        viewModel.RecentlyDepartedPlayers.Clear();

                        foreach (Player player in players)
                        {
                            if (player.RecentlyDeparted)
                            {
                                viewModel.RecentlyDepartedPlayers.Add(player);
                            }
                            else if (player.RecentlyJoined)
                            {
                                viewModel.RecentlyJoinedPlayers.Add(player);
                            }
                            else
                            {
                                viewModel.ConnectedPlayers.Add(player);
                            }
                        }
                    }), DispatcherPriority.Background);
            };
        }