using System; using System.IO; using System.Threading; namespace DesertPaintLab { [System.ComponentModel.ToolboxItem(true)] public partial class CaptureView : Gtk.Bin { Reagent[] reagents = new Reagent[3]; PaintRecipe recipe = new PaintRecipe(); PaintColor expectedColor = new PaintColor(); PaintColor reactedColor = new PaintColor(); PlayerProfile profile = null; Gdk.Pixbuf screenBuffer = null; Gtk.ThreadNotify notifyCaptureProgress; Gtk.ThreadNotify notifyCaptureComplete; bool recordEnabled = true; bool isCaptured = false; public CaptureView(PlayerProfile profile, Gdk.Pixbuf screenBuffer) : base() { this.profile = profile; this.screenBuffer = screenBuffer; this.Build(); reagents[0] = null; reagents[1] = null; reagents[2] = null; if (unmodifiedSwatch != null) { unmodifiedSwatch.Clear(); } if (reactionSwatch != null) { reactionSwatch.Clear(); } PopulateDropDowns(); recipe.Reactions = profile.Reactions; } public Gdk.Pixbuf ScreenBuffer { get { return screenBuffer; } set { if (screenBuffer != null) { // TODO: free it up? } screenBuffer = value; } } public PlayerProfile Profile { set { profile = value; PopulateDropDowns(); recipe.Reactions = profile.Reactions; UpdateIngredients(); // TODO: reset views } } public void DisableRecord() { recordEnabled = false; recordButton.Sensitive = false; } public void EnableRecord() { recordEnabled = true; if (isCaptured) { recordEnabled = true; } UpdateIngredients(); } public void PopulateDropDowns() { ReagentManager.PopulateReagents(ref ingredient1ComboBox); ReagentManager.PopulateReagents(ref ingredient2ComboBox); ReagentManager.PopulateReagents(ref ingredient3ComboBox); ingredient2ComboBox.Sensitive = false; ingredient3ComboBox.Sensitive = false; Gtk.TreeIter iter; ingredient1ComboBox.Model.IterNthChild(out iter, 0); ingredient1ComboBox.SetActiveIter(iter); ingredient2ComboBox.Model.IterNthChild(out iter, 0); ingredient2ComboBox.SetActiveIter(iter); ingredient3ComboBox.Model.IterNthChild(out iter, 0); ingredient3ComboBox.SetActiveIter(iter); } protected void SetExpectedColor(byte red, byte green, byte blue) { expectedColor.Red = red; expectedColor.Green = green; expectedColor.Blue = blue; unmodifiedSwatch.Color = expectedColor; } protected void SetExpectedColor(PaintColor color) { SetExpectedColor(color.Red, color.Green, color.Blue); } protected string GetSelectedReagentName(int reagentNum) { Gtk.ComboBox[] comboBox = { ingredient1ComboBox, ingredient2ComboBox, ingredient3ComboBox }; Gtk.ComboBox desired = comboBox[reagentNum-1]; Gtk.TreeIter selectIter; string reagentName = null; if (desired.GetActiveIter(out selectIter)) { reagentName = (string)desired.Model.GetValue(selectIter, 0); } if (reagentName != null && reagentName.Length == 0) { reagentName = null; } return reagentName; } protected void UpdateIngredients() { Reaction reaction1, reaction2; string reagentName; reagents[0] = null; reagents[1] = null; reagents[2] = null; bool reactionKnown = true; isCaptured = false; recordButton.Sensitive = false; clearReactionButton.Sensitive = false; clearReactionButton.Visible = false; recipe.Clear(); if ((reagentName = GetSelectedReagentName(1)) == null) { // Nothing selected as reagent 1 ingredient2ComboBox.Sensitive = false; ingredient3ComboBox.Sensitive = false; unmodifiedSwatch.Clear(); reactionSwatch.Clear(); captureButton.Sensitive = false; return; } recipe.AddReagent(reagentName); reagents[0] = ReagentManager.GetReagent(reagentName); ingredient2ComboBox.Sensitive = true; if ((reagentName = GetSelectedReagentName(2)) == null) { ingredient3ComboBox.Sensitive = false; recordButton.Sensitive = false; reactionKnown = false; reactionSwatch.Clear(); return; } recipe.AddReagent(reagentName); reagents[1] = ReagentManager.GetReagent(reagentName); ingredient3ComboBox.Sensitive = true; captureButton.Sensitive = true; reaction1 = profile.FindReaction(reagents[0], reagents[1]); // TODO: really should handle reagent0==reagent1 better if ((reaction1 != null) || (reagents[0] == reagents[1])) { clearReactionButton.Sensitive = recordEnabled; clearReactionButton.Visible = true; ingredient3ComboBox.Sensitive = true; if ((reagentName = GetSelectedReagentName(3)) != null) { clearReactionButton.Sensitive = false; clearReactionButton.Visible = false; recipe.AddReagent(reagentName); reagents[2] = ReagentManager.GetReagent(reagentName); if (!reactionKnown) { Gtk.MessageDialog md = new Gtk.MessageDialog((Gtk.Window)Toplevel, Gtk.DialogFlags.DestroyWithParent, Gtk.MessageType.Error, Gtk.ButtonsType.Ok, "To do a three-ingredient reaction test, " + "you must first recored the reaction of " + "the first two ingredients."); md.Run(); md.Destroy(); captureButton.Sensitive = false; } reaction1 = profile.FindReaction(reagents[0], reagents[2]); reaction2 = profile.FindReaction(reagents[1], reagents[2]); if (reactionKnown && (reaction1 == null) && (reaction2 == null)) { Gtk.MessageDialog md = new Gtk.MessageDialog((Gtk.Window)Toplevel, Gtk.DialogFlags.DestroyWithParent, Gtk.MessageType.Error, Gtk.ButtonsType.Ok, "To do a three-ingredient reaction test, " + "you must first record the reaction of " + "either the first or second ingredient " + "with the third ingredient."); md.Run(); md.Destroy(); captureButton.Sensitive = false; } if ((reaction1 == null) && (reagents[0] != reagents[2])) { reactionKnown = false; } if ((reaction2 == null) && (reagents[1] != reagents[2])) { reactionKnown = false; } } } else { reactionKnown = false; ingredient3ComboBox.Sensitive = false; } expectedColor.Set(recipe.BaseColor); unmodifiedSwatch.Color = expectedColor; //SetExpectedColor(recipeColor.Red, recipeColor.Green, recipeColor.Blue); if (reactionKnown) { reactedColor.Set(recipe.ReactedColor); reactionSwatch.Color = reactedColor; } else { reactionSwatch.Clear(); } } unsafe void BeginCapture() { captureButton.Sensitive = false; ingredient1ComboBox.Sensitive = false; ingredient2ComboBox.Sensitive = false; ingredient3ComboBox.Sensitive = false; clearReactionButton.Sensitive = false; progressBar.Show(); recordButton.Hide(); bool enableDebugMenu = false; DesertPaintLab.AppSettings.Get("EnableDebugMenu", out enableDebugMenu); StreamWriter log = null; if (enableDebugMenu) { string logfile = FileUtils.FindNumberedFile("DesertPaintLab_Capture", "log", Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)); log = new StreamWriter(logfile); ReactionRecorder.Instance.Log = log; } if (notifyCaptureProgress == null) { notifyCaptureProgress = new Gtk.ThreadNotify(new Gtk.ReadyEvent(NotifyCaptureProgress)); notifyCaptureComplete = new Gtk.ThreadNotify(new Gtk.ReadyEvent(NotifyCaptureComplete)); } this.reactionSwatch.Clear(); Thread thr = new Thread(new ThreadStart(this.CaptureReactionThread)); thr.Priority = ThreadPriority.AboveNormal; thr.Start(); //OnCaptureComplete(isCaptured, reactedColor, screenWidth, screenHeight, stride, redPixelStart); } unsafe private void CaptureReactionThread() { byte* pixBytes = (byte*)screenBuffer.Pixels; ReactionRecorder.Instance.OnCaptureProgress += OnCaptureProgress; ReactionRecorder.Instance.CaptureReaction(pixBytes, screenBuffer.Width, screenBuffer.Height, screenBuffer.Rowstride); ReactionRecorder.Instance.OnCaptureProgress -= OnCaptureProgress; notifyCaptureComplete.WakeupMain(); } private void OnCaptureProgress(int x, int y) { notifyCaptureProgress.WakeupMain(); } private void NotifyCaptureProgress() { // TODO: progress bar //ReactionRecorder.Instance.X; //ReactionRecorder.Instance.Y; progressBar.Fraction = ((double)(ReactionRecorder.Instance.X) / (double)(ReactionRecorder.Instance.ScreenWidth - ReactionRecorder.Instance.ColorBarWidth)); } void OnCaptureComplete(bool isCaptured, PaintColor reactedColor, int swatchX, int swatchY) { int screenWidth = screenBuffer.Width; int screenHeight = screenBuffer.Height; int stride = screenBuffer.Rowstride; StreamWriter log = ReactionRecorder.Instance.Log; if (log != null) { log.Flush(); log.Close(); log = null; ReactionRecorder.Instance.Log = null; } bool enableDebugMenu = false; DesertPaintLab.AppSettings.Get("EnableDebugMenu", out enableDebugMenu); bool debugScreenshot = false; DesertPaintLab.AppSettings.Get("DebugScreenshot", out debugScreenshot); if (enableDebugMenu && debugScreenshot) { if (!isCaptured) { // write out the whole screenshot on a failure to capture string screenshotDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); string filename = FileUtils.FindNumberedFile("DesertPaintLab_Colormatch", "png", screenshotDir); screenBuffer.Save(filename, "png"); } else { // record the swatch that was captured // write out the screenshot string screenshotDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); string filename = FileUtils.FindNumberedFile("DesertPaintLab_Colormatch", "png", screenshotDir); int x = swatchX - 16; int captureAreaWidth = ReactionRecorder.Instance.ColorBarWidth + 32; captureAreaWidth = Math.Min(screenWidth - x, captureAreaWidth); int y = swatchY - 16; int captureAreaHeight = ReactionRecorder.Instance.BlueBarSpacing + 42; captureAreaHeight = Math.Min(screenHeight - y, captureAreaHeight); Gdk.Pixbuf outPixBuf = new Gdk.Pixbuf(screenBuffer, Math.Max(0, x), Math.Max(0, y), captureAreaWidth, captureAreaHeight); //Gdk.Pixbuf outPixBuf = new Gdk.Pixbuf(screenBuffer, Math.Max(0, redPixelStartX), Math.Max(0, redPixelStartY), // ReactionRecorder.Instance.ColorBarWidth, ReactionRecorder.Instance.GreenBarSpacing - ReactionRecorder.Instance.RedBarSpacing); //screenBuffer.Save(filename, "png"); outPixBuf.Save(filename, "png"); } } //screenBuffer.Save("screenshot.png", "png"); if (isCaptured) { string warning = ""; if (reactedColor.Red == 0) { warning = warning + "\nRed is too low."; } if (reactedColor.Green == 0) { warning = warning + "\nGreen is too low."; } if (reactedColor.Blue == 0) { warning = warning + "\nBlue is too low."; } if (reactedColor.Red == 255) { warning = warning + "\nRed is too high."; } if (reactedColor.Green == 255) { warning = warning + "\nGreen is too high."; } if (reactedColor.Blue == 255) { warning = warning + "\nBlue is too high."; } // Always update the reacted swatch this.reactionSwatch.Color = reactedColor; if (warning.Length != 0) { isCaptured = true; Gtk.MessageDialog md = new Gtk.MessageDialog((Gtk.Window)Toplevel, Gtk.DialogFlags.DestroyWithParent, Gtk.MessageType.Error, Gtk.ButtonsType.Ok, "Reaction clipped. You will need to do a " + "3-way reaction to test this pair. Details: " + warning); md.Run(); md.Destroy(); // reaction clipped - don't let them record recordButton.Sensitive = false; } else { recordButton.Sensitive = recordEnabled; } } else { Gtk.MessageDialog md = new Gtk.MessageDialog((Gtk.Window)Toplevel, Gtk.DialogFlags.DestroyWithParent, Gtk.MessageType.Error, Gtk.ButtonsType.Ok, "Pigment Lab dialog box NOT FOUND. Please ensure " + "that there is an unobstructed view of the dialog " + "and that your interface size is set to 'small' " + "when you press the Capture button."); md.Run(); md.Destroy(); } progressBar.Hide(); recordButton.Show(); ingredient1ComboBox.Sensitive = true; ingredient2ComboBox.Sensitive = true; ingredient3ComboBox.Sensitive = (GetSelectedReagentName(1) != null) && (GetSelectedReagentName(2) != null); } private void NotifyCaptureComplete() { OnCaptureComplete( ReactionRecorder.Instance.IsCaptured, ReactionRecorder.Instance.RecordedColor, ReactionRecorder.Instance.SwatchX, ReactionRecorder.Instance.SwatchY); } unsafe void CaptureReactionColor() { // Take a screenshot. int screenWidth, screenHeight; Gdk.Window rootWindow = Gdk.Global.DefaultRootWindow; DesertPaintLab.AppSettings.Get("ScreenWidth", out screenWidth); DesertPaintLab.AppSettings.Get("ScreenHeight", out screenHeight); Gdk.Image rootImage = rootWindow.GetImage(0, 0, screenWidth, screenHeight); screenBuffer.GetFromImage(rootImage, rootImage.Colormap, 0, 0, 0, 0, screenWidth, screenHeight); rootImage.Unref(); System.GC.Collect(); // really, clean up now BeginCapture(); } protected virtual void OnCapture(object sender, System.EventArgs e) { CaptureReactionColor(); } protected virtual void OnRecord(object sender, System.EventArgs e) { if (ReactionRecorder.Instance.RecordReaction(profile, expectedColor, reactedColor, reagents)) { recordButton.Sensitive = false; } } protected virtual void OnChangedIngredient1(object sender, System.EventArgs e) { UpdateIngredients(); } protected virtual void OnChangedIngredient2(object sender, System.EventArgs e) { UpdateIngredients(); } protected virtual void OnChangedIngredient3(object sender, System.EventArgs e) { UpdateIngredients(); } protected void OnClearReaction(object sender, EventArgs e) { string reagentName1 = GetSelectedReagentName(1); string reagentName2 = GetSelectedReagentName(2); string reagentName3 = GetSelectedReagentName(3); if ((reagentName1 != null) && (reagentName2 != null) && (reagentName3 == null)) { Reagent reagent1 = ReagentManager.GetReagent(reagentName1); Reagent reagent2 = ReagentManager.GetReagent(reagentName2); if (null != profile.FindReaction(reagent1, reagent2)) { Gtk.MessageDialog md = new Gtk.MessageDialog((Gtk.Window)Toplevel, Gtk.DialogFlags.DestroyWithParent, Gtk.MessageType.Warning, Gtk.ButtonsType.OkCancel, "This will delete the reaction status between " + // TODO: ingredient1Name + " and " + ingredient2Name + "\n\n" + "ARE YOU SURE?" ); Gtk.ResponseType response = (Gtk.ResponseType)md.Run(); if (response == Gtk.ResponseType.Ok) { // really delete it profile.ClearReaction(reagent1, reagent2); } md.Destroy(); } } } } }