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