public static Brush GetBackground(QuestionViewModel q) { if (q == null) { // For some reason, this appears to happen infrequently, so handle it "gracefully" //throw new ArgumentNullException("q"); return Brushes.Plum; // An ugly color on purpose } ImageBrush result; if (QuestionControl.mBackgroundBrushes.TryGetValue(q.State, out result)) { return result; } // <ImageSource x:Key="CorrectQuestionPointsBackgroundImage">images/led_square_green.png</ImageSource> object value = QuestionControl.mImagesDictionary[q.State.ToString() + "QuestionPointsBackgroundImage"]; ImageSource source = (ImageSource)value; result = new ImageBrush(source); QuestionControl.mBackgroundBrushes[q.State] = result; return result; }
private void AddAnswersColumnControls(QuestionViewModel question) { for (int i = 0; i < 6; ++i) { AnswersColumnControl acc = new AnswersColumnControl(); acc.DataContext = question.BeerpigRankedAnswers[i]; Grid.SetRow(acc, 1); Grid.SetColumn(acc, i); this.mAnswersGrid.Children.Add(acc); } }
private void HandleDesignTimeView() { HourViewModel hour = new HourViewModel(3); List<QuestionViewModel> qs = new List<QuestionViewModel>(); QuestionViewModel q = new QuestionViewModel( new Question(69, 5) ); q.Text = "Design: How much wood could a woodchuck chuck?"; q.Points = 100; hour.Questions.Add(q); QuestionViewModel q2 = new QuestionViewModel( new Question(69, 6) ); q2.Text = "Design: What's my name?"; q2.Points = 50; hour.Questions.Add(q2); this.DataContext = qs; }
public static void ShowAppropriateToast(HourViewModel currentHour, QuestionViewModel oldQuestionState, Question currentQuestionState) { if (oldQuestionState.State == currentQuestionState.State) { Debug.WriteLine(string.Format("No toast displayed because oldQuestionState.{0} == currentQuestionState.{1}", oldQuestionState.State, currentQuestionState.State)); return; // If the state didn't change, don't show a toast } if (currentQuestionState.State == QuestionState.Open) { Debug.WriteLine(string.Format("No toast displayed because currentQuestionState.{0} == QuestionState.Open", currentQuestionState.State)); return; // no need to show toast } string imageKey; string text; ToastNotification.TriviaIndication indication; if (currentQuestionState.State == QuestionState.Wrong) { if (oldQuestionState.IsBeingResearched == false) { Debug.WriteLine(string.Format("No toast displayed because oldQuestionState.IsBeingResearched({0}) == false", oldQuestionState.IsBeingResearched)); return; // Don't show any toast if a question's closed that you weren't working on } // Just closed, didn't get an answer text = string.Format("Question #{0} Now Closed.", currentQuestionState.Identifier.Number); indication = ToastNotification.TriviaIndication.IncorrectQuestion; imageKey = GetImageForQuestion(currentQuestionState.Identifier.Hour, currentQuestionState.Identifier.Number, ToastFactory.WrongImages); // If it's a high-pointer, let's show a special meme instead if (currentQuestionState.Points >= 80) { imageKey = GetImageForQuestion(currentQuestionState.Identifier.Hour, currentQuestionState.Identifier.Number, ToastFactory.HighPointWrongImages); } } else if (currentQuestionState.State == QuestionState.Correct) { // Always show toast for correct questions (maybe in the future we should have a threshold) text = string.Format("Got Correct Answer for #{0} - {1} points!", currentQuestionState.Identifier.Number, currentQuestionState.Points); indication = ToastNotification.TriviaIndication.CorrectQuestion; imageKey = GetImageForQuestion(currentQuestionState.Identifier.Hour, currentQuestionState.Identifier.Number, ToastFactory.CorrectImages); } else { throw new InvalidOperationException("Toast in wrong place unexpectedly"); } ImageSource image = (ImageSource)ToastFactory.mImagesDictionary[imageKey]; bool shouldShowStopResearching = oldQuestionState.IsBeingResearched; ToastNotification toast = new ToastNotification(image); toast.Indication = indication; toast.BodyText = text; toast.Show(); // Extra toasts for special situations? if (currentQuestionState.Identifier.Number == 9 && currentHour.OpenQuestions.Count == 0 && currentHour.Questions.All(q => q.State == QuestionState.Correct)) { ToastNotification allofthethings = new ToastNotification((ImageSource)ToastFactory.mImagesDictionary["allofthethings"], false); allofthethings.Indication = indication; allofthethings.BodyText = "Perfect Hour"; allofthethings.Show(); } // If it's a low pointer that we missed, add an extra toast meme (only show the people researching though to avoid spam) if (currentQuestionState.State == QuestionState.Wrong && currentQuestionState.Points < 20) // Less than 20 points we'll consider small { ToastNotification fuckitbillmurray = new ToastNotification((ImageSource)ToastFactory.mImagesDictionary["fuckitbillmurray"], false); fuckitbillmurray.Indication = indication; fuckitbillmurray.BodyText = "Small Question Missed"; fuckitbillmurray.Show(); } // Always last/leftmost if (shouldShowStopResearching) { ToastNotification stopResearchingToast = new ToastNotification((ImageSource)ToastFactory.mImagesDictionary["StopResearching"], false); stopResearchingToast.Indication = indication; stopResearchingToast.BodyText = "Stop Researching This Question"; stopResearchingToast.Show(); } }
public EditQuestionViewModel(QuestionViewModel q, bool isOpeningQuestion) { Contract.Requires(q != null); this.Question = q; this.IsOpeningQuestion = isOpeningQuestion; }
private static bool ShouldStopAnswerSubmission(Answer submittedAnswer, QuestionViewModel q, TriviaClient window) { string normalizedSubmittedAnswer = AnswerViewModel.GetNormalizedText(submittedAnswer.Text); AnswerViewModel duplicateAnswer = q.SubmittedAnswers.AsParallel().FirstOrDefault(otherAnswer => AnswerViewModel.GetNormalizedText(otherAnswer.Text).Equals(normalizedSubmittedAnswer, StringComparison.OrdinalIgnoreCase)); if (duplicateAnswer == null) { return false; } string message; if (duplicateAnswer.HasBeenCalledIn) { if (duplicateAnswer.Text.Equals(q.Answer)) { message = string.Format("The answer '{0}' is already accepted as the correct answer.", duplicateAnswer.Text); } else if (duplicateAnswer.Partial) { message = string.Format("The answer '{0}' has been identified as a partial.", duplicateAnswer.Text); } else { message = string.Format("The answer '{0}' was already called in and was wrong.", duplicateAnswer.Text); } } else { message = string.Format("The answer '{0}' has already been submitted by {1} and is on the list to call in.", duplicateAnswer.Text, duplicateAnswer.Submitter); } MessageBox.Show(window, message, "Already Submitted", MessageBoxButton.OK); return true; }
public EditQuestionDialog(ClientOrchestrator orchestrator, QuestionViewModel question, bool isOpeningQuestion) : this() { Contract.Requires(orchestrator != null); Contract.Requires(question != null); this.mQuestion = question; this.mSnapshotOfQuestionPriorToEdits = question.CreateModel(); this.mViewModel = new EditQuestionViewModel(question, isOpeningQuestion); this.mOrchestrator = orchestrator; this.mOrchestrator.StartAudioTriviaRecordingResponseReceived += new ClientOrchestrator.StartAudioTriviaRecordingEventHandler(mOrchestrator_StartAudioTriviaRecordingResponseReceived); this.mOrchestrator.StopAudioTriviaRecordingResponseReceived += new ClientOrchestrator.StopAudioTriviaRecordingEventHandler(mOrchestrator_StopAudioTriviaRecordingResponseReceived); this.DataContext = this.mViewModel; this.mQuestionTextTextbox.Text = question.Text; // Because we don't want other people's updates to change the textbox, binding is OneWayToSource, so we need to set the initial value // If it starts with the magic text, manually set this flag isOpeningQuestion |= (question.Text != null && question.Text.StartsWith("Currently being opened")); // Set up bindings if (isOpeningQuestion) { // We can do this since IsOpeningQuestion will not change this.mAnswerPanel.Visibility = Visibility.Collapsed; this.mPhoneBankPanel.Visibility = Visibility.Collapsed; this.mStatePanel.Visibility = Visibility.Collapsed; question.Text = string.Empty; // Start with empty text if opening the question // Special labels for opening this.Title = "Open Question"; this.mHeaderActionLabel.Text = "Open Question"; this.mRecordAudioTriviaButton.Focus(); this.mRecordAudioTriviaButton.Click += new RoutedEventHandler(this.OnRecordingAudioTriviaClickedWhileOpeningQuestion); // Hack, because it's bound to a question RoutedEventHandler focusHandler = null; focusHandler = (sender, e) => { this.mQuestionTextTextbox.Clear(); this.mQuestionTextTextbox.GotFocus -= focusHandler; // only once! }; this.mQuestionTextTextbox.GotFocus += focusHandler; } else { this.mAnswerPanel.SetBinding(Panel.VisibilityProperty, new ConvertingBinding<QuestionState, Visibility>("Question.State", state => state == QuestionState.Open ? Visibility.Collapsed : Visibility.Visible)); this.mPhoneBankPanel.SetBinding(Panel.VisibilityProperty, new ConvertingBinding<QuestionState, Visibility>("Question.State", state => state == QuestionState.Correct ? Visibility.Visible : Visibility.Collapsed)); this.mQuestionTextTextbox.Focus(); } // Set the text for the Record button this.mRecordThisQuestionButtonText.SetBinding(TextBlock.TextProperty, new ConvertingBinding<string, string>("Question.AudioTriviaFilename", audioTriviaFileName => string.IsNullOrWhiteSpace(audioTriviaFileName) ? "Record This Question" : "Re-Record This Question")); }
private static void HighlightAppearanceOfPartialInThisAnswer(AnswerViewModel answerViewModel, QuestionViewModel question, HashSet<string> oneWordPartials) { IEnumerable<string> words = answerViewModel.Text.Split(new string[] { " " }, StringSplitOptions.RemoveEmptyEntries); IEnumerable<string> wordsThatArePartial = words.Where(word => oneWordPartials.Contains(AnswerViewModel.GetNormalizedText(word).ToLowerInvariant())); answerViewModel.RelatedPartials = string.Join(", ", wordsThatArePartial.Select(word => "'" + word + "'")); }
private static HashSet<string> CreateOneWordPartialsSet(QuestionViewModel question) { return new HashSet<string>( question.PartialAnswers .Cast<AnswerViewModel>() .Where(avm => IsOneWordAnswer(avm.Text)) .Select(avm => AnswerViewModel.GetNormalizedText(avm.Text)) .Select(text => text.ToLowerInvariant())); }
/// <summary> /// A question may not always exist, for example, if someone submits an answer for an hour that's not loaded from the server yet (i.e. a past hour). So try to get the hour if it exists. /// </summary> public bool TryGetQuestion(int hourNumber, int questionNumber, out QuestionViewModel question) { Contract.Requires(hourNumber >= 1); Contract.Requires(questionNumber >= 1); // SingleOrDefault is intentional, we might not find it... question = this.GetHour(hourNumber).Questions.SingleOrDefault(q => q.Identifier.Number == questionNumber); return question != null; }
/// <summary> /// A question may not always exist, for example, if someone submits an answer for an hour that's not loaded from the server yet (i.e. a past hour). So try to get the hour if it exists. /// </summary> public bool TryGetQuestion(QuestionId id, out QuestionViewModel question) { Contract.Requires(id != null); return this.TryGetQuestion(id.Hour, id.Number, out question); }
/// <summary> /// A question may not always exist, for example, if someone submits an answer for an hour that's not loaded from the server yet (i.e. a past hour). So try to get the hour if it exists. /// </summary> public bool TryGetQuestion(Answer answerToFindQuestionFor, out QuestionViewModel question) { Contract.Requires(answerToFindQuestionFor != null); return this.TryGetQuestion(answerToFindQuestionFor.Hour, answerToFindQuestionFor.Number, out question); }
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); }; }