/* * Copyright (c) 2010, Tess Snider Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using System; using System.IO; using System.Collections.Generic; using System.Text.RegularExpressions; using Gtk; using DesertPaintLab; public partial class MainWindow : Gtk.Window { const string APP_VERSION = "1.7.15"; bool unsavedData; bool shouldShutDown; List profileList = new List(); PlayerProfile profile; Gdk.Window rootWindow; Gdk.Pixbuf screenBuffer; // views RecipeGeneratorView generatorView; SimulatorView simulatorView; CaptureView captureView; private ReactionStatusWindow _reactionStatusWindow; public bool ShouldShutDown { get { return shouldShutDown; } } public MainWindow() : base(Gtk.WindowType.Toplevel) { string appDataPath = FileUtils.AppDataPath; if (!System.IO.Directory.Exists(appDataPath)) { System.IO.Directory.CreateDirectory(appDataPath); } DirectoryInfo di = new DirectoryInfo(appDataPath); DirectoryInfo[] dirs = di.GetDirectories(); foreach (DirectoryInfo dir in dirs) { if (dir.Name != "template") { profileList.Add(dir.Name); } } string colorsPath = FileUtils.FindApplicationResourceFile("colors.txt"); if (colorsPath == null) { // failed to find colors.txt file MessageDialog md = new MessageDialog(this, DialogFlags.DestroyWithParent, MessageType.Error, ButtonsType.Close, "Failed to find colors.txt file. Please check your installation."); md.Run(); md.Destroy(); Application.Quit(); } Palette.Load(colorsPath); string ingredientsPath = FileUtils.FindApplicationResourceFile("ingredients.txt"); if (ingredientsPath == null) { // failed to find ingredients.txt file MessageDialog md = new MessageDialog(this, DialogFlags.DestroyWithParent, MessageType.Error, ButtonsType.Close, "Failed to find ingredients.txt file. Please check your installation."); md.Run(); md.Destroy(); Application.Quit(); } ReagentManager.Load(ingredientsPath); Build(); // get the root window rootWindow = Gdk.Global.DefaultRootWindow; // get its width and height AppSettings.Load(); ShowPreferencesDialog(); bool enableDebugMenu; AppSettings.Get("EnableDebugMenu", out enableDebugMenu); this.DebugAction.Visible = enableDebugMenu; ReactionRecorder.Instance.OnReactionRecorded += OnReactionRecorded; if (!OpenProfile()) { shouldShutDown = true; } if (!shouldShutDown) { captureView = new CaptureView(profile, screenBuffer); generatorView = new RecipeGeneratorView(profile); generatorView.SetStatus += OnStatusUpdate; generatorView.Started += OnGeneratorRunning; generatorView.Stopped += OnGeneratorStopped; simulatorView = new SimulatorView(profile); contentContainer.Add(captureView); captureView.Show(); } } bool ConfirmedExit() { if (unsavedData) { MessageDialog md = new MessageDialog(this, DialogFlags.DestroyWithParent, MessageType.Warning, ButtonsType.OkCancel, "Your last reaction was unsaved." + "Are you sure you want to quit?"); ResponseType resp = (ResponseType)md.Run(); md.Destroy(); return (resp == ResponseType.Ok); } return true; } protected void OnStatusUpdate(object sender, EventArgs args) { StatusUpdateEventArgs statusArgs = (StatusUpdateEventArgs)args; statusBar.Push(0, statusArgs.Status); } protected void OnGeneratorRunning(object sender, EventArgs args) { NewProfileAction.Sensitive = false; OpenProfileAction.Sensitive = false; ImportProfileAction.Sensitive = false; if (captureView != null) { captureView.DisableRecord(); } } protected void OnGeneratorStopped(object sender, EventArgs args) { NewProfileAction.Sensitive = true; OpenProfileAction.Sensitive = true; ImportProfileAction.Sensitive = true; if (captureView != null) { captureView.EnableRecord(); } } void SetProfileName(string name) { profile = new PlayerProfile(name, System.IO.Path.Combine(FileUtils.AppDataPath, name)); } void ProfileChanged() { statusBar.Push(0, "Profile: " + profile.Name); Title = "Desert Paint Lab - " + profile.Name; if (generatorView != null) { generatorView.Profile = profile; } if (simulatorView != null) { simulatorView.Profile = profile; } if (captureView != null) { captureView.Profile = profile; } } bool NewProfile() { bool newProfileCreated = false; bool duplicateName = false; NewProfileDialog newProfileDialog = new NewProfileDialog(); ResponseType resp = (ResponseType)newProfileDialog.Run(); if (resp == ResponseType.Ok) { // Make sure profile doesn't already exist. foreach (string profileName in profileList) { if (profileName.Equals(newProfileDialog.ProfileName)) { MessageDialog md = new MessageDialog(this, DialogFlags.DestroyWithParent, MessageType.Error, ButtonsType.Ok, "That profile name already exists."); resp = (ResponseType)md.Run(); md.Destroy(); duplicateName = true; break; } } if (!duplicateName) { // Set profile name. SetProfileName(newProfileDialog.ProfileName); newProfileCreated = profile.Initialize(); if (!newProfileCreated) { MessageDialog md = new MessageDialog(this, DialogFlags.DestroyWithParent, MessageType.Error, ButtonsType.Ok, "Failed to initialize profile: " + profile.LastError); resp = (ResponseType)md.Run(); md.Destroy(); duplicateName = false; } ProfileChanged(); } } newProfileDialog.Destroy(); return newProfileCreated; } bool OpenProfile() { bool profileSelected = false; if (profileList.Count > 0) { SelectProfileDialog selectProfileDialog = new SelectProfileDialog { ProfileList = profileList }; ResponseType resp = (ResponseType)selectProfileDialog.Run(); selectProfileDialog.Destroy(); string selectedProfile = selectProfileDialog.SelectedProfile; if ((resp == ResponseType.Ok) && (selectedProfile.Length > 0)) // Selected a profile. { SetProfileName(selectedProfile); profileSelected = true; } else if (resp == ResponseType.Accept) // New profile. { profileSelected = NewProfile(); } } else { FirstRunDialog firstRunDialog = new FirstRunDialog(); ResponseType resp = (ResponseType)firstRunDialog.Run(); firstRunDialog.Destroy(); if (resp == ResponseType.Ok) // New profile { profileSelected = NewProfile(); } else if (resp == ResponseType.Accept) // Import { FileChooserDialog fileDialog = new FileChooserDialog("Select reactions.txt file.", this, FileChooserAction.Open, Gtk.Stock.Cancel, ResponseType.Cancel, Gtk.Stock.Open, ResponseType.Accept); resp = (ResponseType)fileDialog.Run(); if (resp == ResponseType.Accept) { string fileName = fileDialog.Filename; string directory = fileDialog.CurrentFolder; if (fileName == "reactions.txt") { profileSelected = NewProfile(); if (profileSelected) { profile.ImportFromPP(directory); } } } } } if (profileSelected) { bool ok = profile.Load(); if (ok) { ProfileChanged(); } else { MessageDialog md = new MessageDialog(this, DialogFlags.DestroyWithParent, MessageType.Error, ButtonsType.Ok, "Error loading profile: " + profile.LastError); md.Run(); md.Destroy(); profileSelected = false; } } return profileSelected; } protected void OnDeleteEvent(object sender, DeleteEventArgs a) { if (ConfirmedExit()) { a.RetVal = true; Application.Quit(); } else { a.RetVal = false; } } protected virtual void OnDebugScreenshot(object sender, System.EventArgs e) { int screenWidth, screenHeight; rootWindow.GetSize(out screenWidth, out screenHeight); 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); string screenshotDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); string filename; int i = 0; do { ++i; filename = System.IO.Path.Combine(screenshotDir, String.Format("DesertPaintLab_{0}.png", i)); } while (System.IO.File.Exists(filename)); screenBuffer.Save(filename, "png"); } protected virtual void OnNewProfile(object sender, System.EventArgs e) { if (unsavedData) { MessageDialog md = new MessageDialog(this, DialogFlags.DestroyWithParent, MessageType.Warning, ButtonsType.OkCancel, "Your last reaction was unsaved." + "Are you sure you want to lose your changes?"); ResponseType resp = (ResponseType)md.Run(); md.Destroy(); if (resp != ResponseType.Ok) { return; } } if (NewProfile()) { bool ok = profile.Load(); if (ok) { ProfileChanged(); } else { MessageDialog md = new MessageDialog(this, DialogFlags.DestroyWithParent, MessageType.Warning, ButtonsType.OkCancel, "Failed to load profile: " + profile.LastError); md.Run(); md.Destroy(); } } } protected virtual void OnOpenProfile(object sender, System.EventArgs e) { bool profileSelected = false; SelectProfileDialog selectProfileDialog = new SelectProfileDialog { ProfileList = profileList }; ResponseType resp = (ResponseType)selectProfileDialog.Run(); selectProfileDialog.Destroy(); if (resp == ResponseType.Ok) // Selected a profile. { SetProfileName(selectProfileDialog.SelectedProfile); profileSelected = true; } else if (resp == ResponseType.Accept) // New profile. { profileSelected = NewProfile(); } if (profileSelected) { bool ok = profile.Load(); if (ok) { ProfileChanged(); } else { MessageDialog md = new MessageDialog(this, DialogFlags.DestroyWithParent, MessageType.Warning, ButtonsType.OkCancel, "Failed to load profile: " + profile.LastError); md.Run(); md.Destroy(); } } } protected virtual void OnAbout(object sender, System.EventArgs e) { AboutDialog aboutDialog = new AboutDialog { ProgramName = "Desert Paint Lab", Version = APP_VERSION, Comments = "Desert Paint Lab paint reaction recorder for A Tale in the Desert", Authors = new string [] { "Tess Snider", "Jason Maltzen" } }; //aboutDialog.Website = "http://www.google.com/"; aboutDialog.Run(); aboutDialog.Destroy(); } protected virtual void OnMenuExit(object sender, System.EventArgs e) { if (ConfirmedExit()) { Application.Quit(); } } protected virtual void OnExportToPracticalPaint(object sender, System.EventArgs e) { FileChooserDialog fileDialog = new FileChooserDialog("Select destination file.", this, FileChooserAction.Save, Gtk.Stock.Cancel, ResponseType.Cancel, Gtk.Stock.Save, ResponseType.Accept); ResponseType resp = (ResponseType)fileDialog.Run(); if (resp == ResponseType.Accept) { string fileName = fileDialog.Filename; string directory = fileDialog.CurrentFolder; profile.SaveToPP(System.IO.Path.Combine(directory, fileName)); } fileDialog.Destroy(); } protected void OnReactionRecorded(Reagent reagent1, Reagent reagent2, Reaction reaction) { if (_reactionStatusWindow != null) { _reactionStatusWindow.RefreshReactions(); } } protected void OnReactionStatusDestroyed(object o, EventArgs args) { _reactionStatusWindow = null; } protected void OnShowReactionStatus(object sender, EventArgs e) { if (_reactionStatusWindow != null) return; // already open _reactionStatusWindow = new ReactionStatusWindow(profile); _reactionStatusWindow.Destroyed += new EventHandler(OnReactionStatusDestroyed); _reactionStatusWindow.Show(); } protected void OnExportProfile(object sender, EventArgs e) { FileChooserDialog fileDialog = new FileChooserDialog("Select destination file.", this, FileChooserAction.Save, Gtk.Stock.Cancel, ResponseType.Cancel, Gtk.Stock.Save, ResponseType.Accept); ResponseType resp = (ResponseType)fileDialog.Run(); if (resp == ResponseType.Accept) { string fileName = fileDialog.Filename; string directory = fileDialog.CurrentFolder; string targetFile = System.IO.Path.Combine(directory, fileName); if (File.Exists(targetFile)) { // prompt to overwrite MessageDialog md = new MessageDialog(this, DialogFlags.DestroyWithParent, MessageType.Warning, ButtonsType.OkCancel, "Overwrite file at " + targetFile + "?"); resp = (ResponseType)md.Run(); md.Destroy(); if (resp == ResponseType.Ok) { File.Delete(targetFile); profile.Export(targetFile); } } else { profile.Export(targetFile); } } fileDialog.Destroy(); } protected void OnImportProfile(object sender, EventArgs e) { FileChooserDialog fileDialog = new FileChooserDialog("Select file to import.", this, FileChooserAction.Open, Gtk.Stock.Cancel, ResponseType.Cancel, Gtk.Stock.Open, ResponseType.Accept); ResponseType resp = (ResponseType)fileDialog.Run(); if (resp == ResponseType.Accept) { string fileName = fileDialog.Filename; string directory = fileDialog.CurrentFolder; string targetFile = fileName; if (directory != null) { targetFile = System.IO.Path.Combine(directory, fileName); } if (Directory.Exists(profile.Directory)) { // prompt to overwrite MessageDialog md = new MessageDialog(this, DialogFlags.DestroyWithParent, MessageType.Warning, ButtonsType.OkCancel, "Overwrite profile '" + profile.Name + "'?"); resp = (ResponseType)md.Run(); md.Destroy(); if (resp == ResponseType.Ok) { Directory.Delete(profile.Directory, true); profile.Import(targetFile); } } else { profile.Import(targetFile); } bool ok = profile.Load(); if (ok) { ProfileChanged(); } else { MessageDialog md = new MessageDialog(this, DialogFlags.DestroyWithParent, MessageType.Warning, ButtonsType.OkCancel, "Failed to load imported profile: " + profile.LastError); md.Run(); md.Destroy(); } } fileDialog.Destroy(); } protected void OnExportRecipeListToWiki(object sender, EventArgs e) { Gtk.FileChooserDialog fileDialog = new Gtk.FileChooserDialog("Select destination file.", (Gtk.Window)Toplevel, Gtk.FileChooserAction.Save, Gtk.Stock.Cancel, Gtk.ResponseType.Cancel, Gtk.Stock.Save, Gtk.ResponseType.Accept); Gtk.ResponseType resp = (Gtk.ResponseType)fileDialog.Run(); if (resp == Gtk.ResponseType.Accept) { string fileName = fileDialog.Filename; string directory = fileDialog.CurrentFolder; profile.ExportWikiRecipes(System.IO.Path.Combine(directory, fileName)); } fileDialog.Destroy(); } protected void OnCopyRecipeListToClipboard(object sender, EventArgs e) { Gtk.Clipboard clipboard = this.GetClipboard(Gdk.Selection.Clipboard); StringWriter writer = new StringWriter(); profile.ExportWikiRecipes(writer); clipboard.Text = writer.ToString(); } protected void OnSelectCaptureView(object sender, EventArgs e) { if (generatorView != null && generatorView.Visible) { generatorView.Hide(); contentContainer.Remove(generatorView); } if (simulatorView != null && simulatorView.Visible) { simulatorView.Hide(); contentContainer.Remove(simulatorView); } if (captureView == null) { captureView = new CaptureView(profile, screenBuffer); } if (captureView.Visible == false) { captureView.ResizeChildren(); captureView.CheckResize(); contentContainer.Add(captureView); captureView.Show(); } CheckResize(); // TODO: enable/disable menu options based on view NewProfileAction.Sensitive = true; OpenProfileAction.Sensitive = true; ImportProfileAction.Sensitive = true; } protected void OnSelectRecipeGeneratorView(object sender, EventArgs e) { if (captureView != null && captureView.Visible) { captureView.Hide(); contentContainer.Remove(captureView); } if (simulatorView != null && simulatorView.Visible) { simulatorView.Hide(); contentContainer.Remove(simulatorView); } if (generatorView == null) { generatorView = new RecipeGeneratorView(profile); generatorView.SetStatus += OnStatusUpdate; generatorView.Started += OnGeneratorRunning; generatorView.Stopped += OnGeneratorStopped; } if (generatorView.Visible == false) { generatorView.ResizeChildren(); generatorView.CheckResize(); contentContainer.Add(generatorView); generatorView.Show(); } CheckResize(); // TODO: only when generator is running NewProfileAction.Sensitive = false; OpenProfileAction.Sensitive = false; ImportProfileAction.Sensitive = false; // TODO: enable/disable menu options based on view } protected void OnSelectSimulatorView(object sender, EventArgs e) { if (captureView != null && captureView.Visible) { captureView.Hide(); contentContainer.Remove(captureView); } if (generatorView != null && generatorView.Visible) { generatorView.Hide(); contentContainer.Remove(generatorView); } if (simulatorView == null) { simulatorView = new SimulatorView(profile); } if (simulatorView.Visible == false) { simulatorView.ResizeChildren(); simulatorView.CheckResize(); contentContainer.Add(simulatorView); simulatorView.Show(); } CheckResize(); // TODO: enable/disable menu options based on view NewProfileAction.Sensitive = true; OpenProfileAction.Sensitive = true; ImportProfileAction.Sensitive = true; } protected void ShowPreferencesDialog() { int detectedScreenWidth; int detectedScreenHeight; rootWindow.GetSize(out detectedScreenWidth, out detectedScreenHeight); int screenWidth = detectedScreenWidth; int screenHeight = detectedScreenHeight; int pixelMultiplier = 1; int interfaceSizeIndex = (int)(InterfaceSize.Small); AppSettings.Get("ScreenWidth", out screenWidth); AppSettings.Get("ScreenHeight", out screenHeight); AppSettings.Get("PixelMultiplier", out pixelMultiplier); AppSettings.Get("InterfaceSize", out interfaceSizeIndex); InterfaceSize interfaceSize = (InterfaceSize)interfaceSizeIndex; ScreenCheckDialog screenCheckDialog = new ScreenCheckDialog { DetectedScreenWidth = detectedScreenWidth, DetectedScreenHeight = detectedScreenHeight, ScreenWidth = screenWidth, ScreenHeight = screenHeight, GamePixelWidth = pixelMultiplier, InterfaceSize = interfaceSize }; ResponseType resp = (ResponseType)screenCheckDialog.Run(); screenWidth = Math.Max(640, screenCheckDialog.ScreenWidth); // don't support screen smaller than 640x480 no matter what the user says screenHeight = Math.Max(480, screenCheckDialog.ScreenHeight); pixelMultiplier = Math.Max(1, screenCheckDialog.GamePixelWidth); // Don't allow 0/negative pixel multiplier interfaceSize = screenCheckDialog.InterfaceSize; screenCheckDialog.Destroy(); AppSettings.Set("ScreenWidth", screenWidth); AppSettings.Set("ScreenHeight", screenHeight); AppSettings.Set("PixelMultiplier", pixelMultiplier); AppSettings.Set("InterfaceSize", (int)interfaceSize); AppSettings.Save(); screenBuffer = new Gdk.Pixbuf(Gdk.Colorspace.Rgb, false, 8, screenWidth, screenHeight); ReactionRecorder.Instance.SetPixelMultiplier(pixelMultiplier); ReactionRecorder.Instance.SetInterfaceSize(interfaceSize); } protected void OnPreferences(object sender, EventArgs e) { ShowPreferencesDialog(); int screenWidth = 1920; int screenHeight = 1080; int pixelMultiplier = 1; int interfaceSizeIndex = (int)(InterfaceSize.Small); AppSettings.Get("ScreenWidth", out screenWidth); AppSettings.Get("ScreenHeight", out screenHeight); AppSettings.Get("PixelMultiplier", out pixelMultiplier); AppSettings.Get("InterfaceSize", out interfaceSizeIndex); captureView.ScreenBuffer = screenBuffer; } }