using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Xml; using System.Windows.Forms; using TradeIdeas.TIProData; using TradeIdeas.TIProData.Configuration; using TradeIdeas.XML; using System.Runtime.InteropServices; using Microsoft.Win32; using System.Diagnostics; using System.IO; using System.Speech; using System.Threading; using System.Threading.Tasks; using System.Text.RegularExpressions; namespace TradeIdeas.TIProGUI { public partial class ActionsForm : Form, ICultureListener { private List _phrases; private List _defaultSound; private FontManager _fontManager; private const float DEFAULT_FONT_SIZE = 9.75F; Actions _currentSettings; // What the dialog box is using. Actions _restoreSettings; // In case the user hits cancel. public ActionsForm(Actions savedSettings) { _currentSettings = savedSettings; _restoreSettings = new Actions(null); _restoreSettings.CopyFrom(_currentSettings); _phrases = GuiEnvironment.XmlConfig.Node("ACTIONS").Node("PHRASES"); _defaultSound = GuiEnvironment.XmlConfig.Node("SOUNDS"); this.StartPosition = FormStartPosition.CenterParent; InitializeComponent(); Icon = GuiEnvironment.Icon; CultureChanged(); chkSound.Checked = _currentSettings.PlaySound; chkShowAlert.Checked = _currentSettings.ShowWindow; populateTextBox(); _fontManager = new FontManager(this, DEFAULT_FONT_SIZE); _fontManager.selectTheFont(); if (GuiEnvironment.XmlConfig.Node("ACTIONS").Node("HIDE_SHOW_ALERT_WINDOW").PropertyForCulture("VALUE", "0") == "1") chkShowAlert.Visible = false; } public void HideShowAlert() { chkShowAlert.Visible = false; } public void CultureChanged() { this.Text = _phrases.Node("WINDOW_TITLE").PropertyForCulture("TEXT", "***"); grpOnEachAlert.Text = _phrases.Node("ON_EACH_ALERT").PropertyForCulture("TEXT", "***"); chkSound.Text = _phrases.Node("PLAY_SOUND").PropertyForCulture("TEXT", "***"); btnSound.Text = _phrases.Node("SETUP_SOUND").PropertyForCulture("TEXT", "***"); chkShowAlert.Text = _phrases.Node("SHOW_ALERT_WINDOW").PropertyForCulture("TEXT", "***"); btnOK.Text = _phrases.Node("OK").PropertyForCulture("TEXT", "***"); btnCancel.Text = _phrases.Node("CANCEL").PropertyForCulture("TEXT", "***"); } private void chkSound_CheckedChanged(object sender, EventArgs e) { if (chkSound.Checked) { btnSound.Enabled = true; } else { btnSound.Enabled = false; } _currentSettings.PlaySound = chkSound.Checked; } private void btnCancel_Click(object sender, EventArgs e) { _currentSettings.CopyFrom(_restoreSettings); DialogResult = System.Windows.Forms.DialogResult.Cancel; } private void btnOK_Click(object sender, EventArgs e) { DialogResult = System.Windows.Forms.DialogResult.OK; } private void btnSound_Click(object sender, EventArgs e) { using (SoundConfig soundConfig = new SoundConfig(_currentSettings)) { if (soundConfig.ShowDialog() == DialogResult.OK) { populateTextBox(); } } } public void populateTextBox() { int left = 0; int right = 0; string contents = ""; if (_currentSettings.SoundType == SoundType.Custom) { if (string.IsNullOrEmpty(_currentSettings.SoundFile)) { //The text box in an actions form will display only if that's what the //user initially saves. txtDirectory.Text = _defaultSound.Node("DEFAULT_BUILTIN").PropertyForCulture("TEXT", "***"); } else { contents = _currentSettings.SoundFile + "\r\n" + _defaultSound.Node("PHRASES").Node("VOLUME").PropertyForCulture("TEXT", "***") + ": "; string volumePercent = (_currentSettings.Volume).ToString(); contents += volumePercent + "%" + "\r\n" + _defaultSound.Node("LEFT_RIGHT").PropertyForCulture("TEXT", "***") + ": "; right = _currentSettings.Balance; left = 100 - right; contents += left.ToString() + "%/" + right.ToString() + "%"; txtDirectory.Text = contents; } } else if (_currentSettings.SoundType == SoundType.Asterisk) txtDirectory.Text = "Built In Sound: Asterisk"; else if (_currentSettings.SoundType == SoundType.Beep) txtDirectory.Text = "Built In Sound: Beep"; else if (_currentSettings.SoundType == SoundType.Exclamation) txtDirectory.Text = "Built In Sound: Exclamation"; else if (_currentSettings.SoundType == SoundType.Stop) txtDirectory.Text = "Built In Sound: Stop"; else if (_currentSettings.SoundType == SoundType.Question) txtDirectory.Text = "Built In Sound: Question"; else if (_currentSettings.SoundType == SoundType.TextToSpeech) txtDirectory.Text = $"Built In Sound: Text To Speech ({_currentSettings.VoiceGender.ToString()}){(_currentSettings.VerboseTextToSpeech ? " (Verbose)" : "")}"; } //private static Point InitialLocation; private void Actions_VisibleChanged(object sender, EventArgs e) { if (Visible) { //Location = InitialLocation; CultureChanged(); if (_currentSettings.SoundFile == "") { _currentSettings.Volume = Actions.DEFAULT_VOLUME; _currentSettings.Balance = Actions.DEFAULT_BALANCE; } } //else // Hiding / closing // Save the current location so we start there next time. //InitialLocation = Location; } private void chkShowAlert_CheckedChanged(object sender, EventArgs e) { _currentSettings.ShowWindow = chkShowAlert.Checked; } } // This is the heart of the idea of actions. // // This will contain the master copy of all configuration. You can save a copy of this // object and restore it later, if for example someone hits the cancel button. This // information might be temporarily loaded into a dialog box so you can edit it, but // when the dialog box is closed, you dispose of the dialog box. An alert window (or // a similar window) will own one of these objects, but it will not have any other // copies of the data inside. public interface IActionsForOwner { // For alerts and multistrategy. If you want to add actions to your window, // this is all you need to know! void Perform(List symbols); void CopyFrom(Actions source); void Load(XmlNode source); void Save(XmlNode destination); void ShowDialog(); bool InUse(); void SetOwner(Form form); string GetTextToSpeak(string template, Dictionary substitutionRules); } public enum SoundType { Asterisk, Beep, Exclamation, Stop, Question, Custom, TextToSpeech }; public class Actions : IActionsForOwner { private Form _owner; private bool _isAllowed = true; // for forms such as RBI/GBI or others dependent upon whether user has subscribed account (Oddsmaker) private TIProSpeechManager.SpeechManager _speechManager; public bool PlaySound { get; set; } public bool ShowWindow { get; set; } public int Volume { get; set; } public int Balance { get; set; } public string SoundFile { get; set; } public bool UsingCustomSound { get; set; } public SoundType SoundType { get; set; } public bool VerboseTextToSpeech { get; set; } public TIProSpeechManager.VoiceGender VoiceGender { get; set; } public bool HideShowAlert { get; set; } public const int DEFAULT_VOLUME = 75; public const int DEFAULT_BALANCE = 50; public Actions(Form owner) { _owner = owner; _speechManager = TIProSpeechManager.SpeechManager.Instance(); PlaySound = false; ShowWindow = false; SoundType = SoundType.Custom; Volume = DEFAULT_VOLUME; Balance = DEFAULT_BALANCE; } public void setAllowed(bool allowed) { _isAllowed = allowed; } public bool InUse() //for the checkbox menu item in Alert/Multistrat..etc { if (PlaySound == true || ShowWindow == true) { return true; } return false; } private void ShowWindowNow() { if (ShowWindow && (null != _owner)) { BringToFront(_owner); if (_owner.WindowState == FormWindowState.Minimized) { _owner.WindowState = FormWindowState.Normal; _owner.Visible = true; } } } private void BringToFront(Form f) { //We wish to check whether there are any TIPro windows that are modal //before bringing another window to the front. This prevents the annoying //situation where the user will struggle to keep a modal window on top //due to other activated alert windows popping up... IntPtr hwnd = f.Handle; if (isModalPresent()) { return; } else if(GuiEnvironment.ApplicationIsActivated()) { GuiEnvironment.SetForegroundWindow(hwnd); } } public Form getOwner() { return _owner; } private bool isModalPresent() { bool retVal = false; foreach (Form f in Application.OpenForms.Cast
().Where(x => !x.IsDisposed).ToList()) { if (f.Modal) { return true; } } if (nativeWindowTIProWindowPresent()) // for "Save As" dialogs within TI Pro { return true; } return retVal; } private bool nativeWindowTIProWindowPresent() { /*Most of the modals of TIPro are "taken care of" with the above method, isModalPresent However, some of TIPro's modals (i.e. "Save As" or "Open" are not modals that we've created but * modals that are called from (presumably) the Windows API. As such, they're not even * noted in Application.OpenForms. As a result, a user might struggle if he wishes to save something while very active alert windows come forward. Most of this code courtesy of http://www.pinvoke.net/default.aspx/user32/EnumDesktopWindows.html */ bool retVal = false; var collection = new List(); IntPtr hWndOwner; EnumDelegate filter = delegate(IntPtr hWnd, int lParam) { StringBuilder strbTitle = new StringBuilder(255); int nLength = GetWindowText(hWnd, strbTitle, strbTitle.Capacity + 1); string strTitle = strbTitle.ToString(); if (IsWindowVisible(hWnd) && string.IsNullOrEmpty(strTitle) == false) { // Following code based on the answer to this question: http://stackoverflow.com/questions/5784237/how-to-given-hwnd-discover-if-window-is-modal-or-not if ((GetParent(hWnd) == null) || (GetParent(hWnd) == GetWindow(hWnd, GW_OWNER))) { // nWnd is a top-level dialog and not a child window hWndOwner = GetWindow(hWnd, GW_OWNER); // Check that the owner is itself a top-level window and that the owner is disabled if (GetAncestor(hWndOwner, GW_PARENT) == GetDesktopWindow() && (!IsWindowEnabled(hWndOwner))) { // hWnd is modal! Let's add this to the collection collection.Add(strTitle); } } } return true; }; EnumDesktopWindows(IntPtr.Zero, filter, IntPtr.Zero); // Check if we found a system modal window if (collection.Count > 0) retVal = true; return retVal; } public const uint GW_OWNER = 4; public const uint GW_PARENT = 1; public delegate bool EnumDelegate(IntPtr hWnd, int lParam); //filter function [DllImport("user32")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool IsWindowVisible(IntPtr hWnd); //check if windows are visible [DllImport("user32", EntryPoint = "GetWindowText", ExactSpelling = false, CharSet = CharSet.Auto, SetLastError = true)] //return window text public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpWindowText, int nMaxCount); [DllImport("user32", EntryPoint = "EnumDesktopWindows", ExactSpelling = false, CharSet = CharSet.Auto, SetLastError = true)] //Enumerator of all desktop windows public static extern bool EnumDesktopWindows(IntPtr hDesktop, EnumDelegate lpEnumCallbackFunction, IntPtr lParam); [DllImport("User32.dll")] public static extern IntPtr GetParent(IntPtr hWnd); [DllImport("User32.dll")] public static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd); [DllImport("User32.dll")] static extern IntPtr GetAncestor(IntPtr hwnd, uint gaFlags); [DllImport("User32.dll")] static extern IntPtr GetDesktopWindow(); [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] static extern bool IsWindowEnabled(IntPtr hWnd); [DllImport("winmm.dll")] private static extern int waveOutSetVolume(IntPtr hwo, uint dwVolume); private void CalculateVolumeAndBalance() { //This member function is public...if the alert window(or multistrategy) is // saved, then upon restoration this method needs to be called to remedy a bug // where the layout is restored, yet the volume doesn't correspond with the // slider control's position. eg. the restored file had a slider at zero, yet // we still hear the sound... // // Not used anymore since setting the volume and balance with the waveOutSetVolume // method caused all instances of the sound device to get those settings. // Volume and balance for other windows using another custom sound alerts would be affected. // // Calculate the volume that's being set for this trackbar int newVolume = Volume; newVolume = newVolume * ushort.MaxValue / 100; // Change the scale. int rightVolume; int leftVolume; leftVolume = newVolume; rightVolume = newVolume; if (Balance <= 0) { // All sound to the left channel leftVolume = newVolume; rightVolume = 0; } else if (Balance <= 50) { //Sound moves to the left leftVolume = newVolume; rightVolume = (int)((double)newVolume / (100 - Balance)) * Balance; } else if (Balance <= 99) { //Sound moves to the right rightVolume = newVolume; leftVolume = (int)((double)newVolume / Balance) * (100 - Balance); } else //100 { rightVolume = newVolume; leftVolume = 0; } uint NewVolumeAllChannels = ((uint)rightVolume << 16) + (uint)leftVolume; // Set the new volumes of both channels... // Note, waveOutSetVolume applies to all instances of the sound device. int result = waveOutSetVolume(IntPtr.Zero, NewVolumeAllChannels); } public string GetDefaultSoundParameter() { List defaultSound = GuiEnvironment.XmlConfig.Node("SOUNDS"); string soundDirectory = Application.StartupPath + "\\Sounds"; string soundFile = Path.Combine(soundDirectory, defaultSound.Node("DEFAULT_SOUND").Property("TEXT")); return soundFile; } public string GetTextToSpeak(string template, Dictionary substitutionRules) { if (this.VerboseTextToSpeech) { var result = template; var outputString = template; var substitutionList = new List(); foreach (Match match in Regex.Matches(result, "{([^{]*)}")) { if (substitutionRules.TryGetValue(match.Groups[1].Value, out string resultValue)) { substitutionList.Add(resultValue); var index = substitutionList.IndexOf(resultValue); outputString = outputString.Replace(match.Groups[1].Value, index.ToString()); } else outputString = outputString.Replace($"{{{match.Groups[1].Value}}}", ""); } outputString = string.Format(outputString, substitutionList.ToArray()); return outputString; } else { if (substitutionRules.TryGetValue("SYMBOL", out string resultValue)) { return resultValue; } else { return template; } } } private NAudio.Wave.WaveFileReader wave = null; private NAudio.Wave.WaveOut output = null; private NAudio.Wave.WaveChannel32 volumeStream = null; private NAudio.Wave.WaveMixerStream32 mixer = null; public bool PlaySoundNow(string stringToSpeak) { if (GuiEnvironment.MuteSounds) return false; if (PlaySound && SoundType == TIProGUI.SoundType.Custom) { try { if (string.IsNullOrEmpty(SoundFile)) { SoundFile = GetDefaultSoundParameter(); } // if SoundFile doesn't exist let's try to use a system sound instead if (File.Exists(SoundFile)) { // Use NAdudio to play wav file. The previous way would cause the resetting // of the system volume for other sound playbacks. // Based on: http://mark-dot-net.blogspot.com/2008/06/naudio-wave-stream-architecture.html // // Wait for wav file to finish playing before playing the sound again. if (output != null) return false; DisposeWave(); wave = new NAudio.Wave.WaveFileReader(SoundFile); // Modify volume and balance NAudio.Wave.WaveChannel32 volumeStream = new NAudio.Wave.WaveChannel32(wave); volumeStream.Volume = (float)Volume / 100; volumeStream.Pan = (float)(Balance - 50) / 50; // Adding stream to mixer NAudio.Wave.WaveMixerStream32 mixer = new NAudio.Wave.WaveMixerStream32(); mixer.AddInputStream(volumeStream); // Send mixer to sound device, this allows for PlaybackStopped to work correctly. output = new NAudio.Wave.WaveOut(); output.Init(mixer); output.PlaybackStopped += new EventHandler(Output_PlaybackStopped); output.Play(); } else { System.Media.SystemSounds.Beep.Play(); } return true; } catch (Exception e) { string debugView = e.StackTrace; } } else if (PlaySound && SoundType != TIProGUI.SoundType.Custom) { try { switch (SoundType) { case TIProGUI.SoundType.Asterisk: System.Media.SystemSounds.Asterisk.Play(); break; case TIProGUI.SoundType.Exclamation: System.Media.SystemSounds.Exclamation.Play(); break; case TIProGUI.SoundType.Stop: System.Media.SystemSounds.Hand.Play(); break; case TIProGUI.SoundType.Question: System.Media.SystemSounds.Question.Play(); break; case TIProGUI.SoundType.TextToSpeech: if (this.VerboseTextToSpeech) { _speechManager.SendMessage(stringToSpeak, Volume, VoiceGender, _owner.Handle); } else { new Thread(delegate () { try { System.Speech.Synthesis.SpeechSynthesizer voice = new System.Speech.Synthesis.SpeechSynthesizer(); // Set voice volume voice.Volume = Volume; switch (VoiceGender) { case TIProSpeechManager.VoiceGender.Female: voice.SelectVoiceByHints(System.Speech.Synthesis.VoiceGender.Female); break; case TIProSpeechManager.VoiceGender.Male: voice.SelectVoiceByHints(System.Speech.Synthesis.VoiceGender.Male); break; case TIProSpeechManager.VoiceGender.Neutral: voice.SelectVoiceByHints(System.Speech.Synthesis.VoiceGender.Neutral); break; } voice.SpeakAsync(stringToSpeak); } catch { // if this text to speech fails for whatever reason, just play the beep System.Media.SystemSounds.Beep.Play(); } }).Start(); } //new Thread(delegate () //{ // try // { // System.Speech.Synthesis.SpeechSynthesizer voice = new System.Speech.Synthesis.SpeechSynthesizer(); // // Set voice volume // voice.Volume = Volume; // // Female voice selected as default // voice.SelectVoiceByHints(System.Speech.Synthesis.VoiceGender.Female); // if (GuiEnvironment.AppConfig.Node("GUI_LIB").Node("TTS_VOICE").Property("TYPE", "") == "MALE") // voice.SelectVoiceByHints(System.Speech.Synthesis.VoiceGender.Male); // else if (GuiEnvironment.AppConfig.Node("GUI_LIB").Node("TTS_VOICE").Property("TYPE", "") == "NEUTRAL") // voice.SelectVoiceByHints(System.Speech.Synthesis.VoiceGender.Neutral); // voice.SpeakAsync(stringToSpeak); // } // catch // { // if this text to speech fails for whatever reason, just play the beep // System.Media.SystemSounds.Beep.Play(); // } //}).Start(); break; default: System.Media.SystemSounds.Beep.Play(); break; } return true; } catch { } return false; } return false; } public bool PlaySoundNow(List symbols) { if (GuiEnvironment.MuteSounds) return false; if (PlaySound && SoundType == TIProGUI.SoundType.Custom) { try { if (string.IsNullOrEmpty(SoundFile)) { SoundFile = GetDefaultSoundParameter(); } // if SoundFile doesn't exist let's try to use a system sound instead if (File.Exists(SoundFile)) { // Use NAdudio to play wav file. The previous way would cause the resetting // of the system volume for other sound playbacks. // Based on: http://mark-dot-net.blogspot.com/2008/06/naudio-wave-stream-architecture.html // // Wait for wav file to finish playing before playing the sound again. if (output != null) return false; DisposeWave(); wave = new NAudio.Wave.WaveFileReader(SoundFile); // Modify volume and balance NAudio.Wave.WaveChannel32 volumeStream = new NAudio.Wave.WaveChannel32(wave); volumeStream.Volume = (float)Volume / 100; volumeStream.Pan = (float)(Balance - 50)/50; // Adding stream to mixer NAudio.Wave.WaveMixerStream32 mixer = new NAudio.Wave.WaveMixerStream32(); mixer.AddInputStream(volumeStream); // Send mixer to sound device, this allows for PlaybackStopped to work correctly. output = new NAudio.Wave.WaveOut(); output.Init(mixer); output.PlaybackStopped += new EventHandler(Output_PlaybackStopped); output.Play(); } else { System.Media.SystemSounds.Beep.Play(); } return true; } catch (Exception e) { string debugView = e.StackTrace; } } else if (PlaySound && SoundType != TIProGUI.SoundType.Custom) { try { switch (SoundType) { case TIProGUI.SoundType.Asterisk: System.Media.SystemSounds.Asterisk.Play(); break; case TIProGUI.SoundType.Exclamation: System.Media.SystemSounds.Exclamation.Play(); break; case TIProGUI.SoundType.Stop: System.Media.SystemSounds.Hand.Play(); break; case TIProGUI.SoundType.Question: System.Media.SystemSounds.Question.Play(); break; case TIProGUI.SoundType.TextToSpeech: string stringToSpeak = String.Join(", ", symbols.ToList().ToArray()); if (this.VerboseTextToSpeech) { _speechManager.SendMessage(stringToSpeak, Volume, VoiceGender, _owner.Handle); } else { new Thread(delegate () { try { System.Speech.Synthesis.SpeechSynthesizer voice = new System.Speech.Synthesis.SpeechSynthesizer(); // Set voice volume voice.Volume = Volume; switch (VoiceGender) { case TIProSpeechManager.VoiceGender.Female: voice.SelectVoiceByHints(System.Speech.Synthesis.VoiceGender.Female); break; case TIProSpeechManager.VoiceGender.Male: voice.SelectVoiceByHints(System.Speech.Synthesis.VoiceGender.Male); break; case TIProSpeechManager.VoiceGender.Neutral: voice.SelectVoiceByHints(System.Speech.Synthesis.VoiceGender.Neutral); break; } voice.SpeakAsync(stringToSpeak); } catch { // if this text to speech fails for whatever reason, just play the beep System.Media.SystemSounds.Beep.Play(); } }).Start(); } //new Thread(delegate() //{ // try // { // string stringToSpeak = String.Join(", ", symbols.ToList().ToArray()); // System.Speech.Synthesis.SpeechSynthesizer voice = new System.Speech.Synthesis.SpeechSynthesizer(); // // Set voice volume // voice.Volume = Volume; // // Female voice selected as default // voice.SelectVoiceByHints(System.Speech.Synthesis.VoiceGender.Female); // if (GuiEnvironment.AppConfig.Node("GUI_LIB").Node("TTS_VOICE").Property("TYPE", "") == "MALE") // voice.SelectVoiceByHints(System.Speech.Synthesis.VoiceGender.Male); // else if (GuiEnvironment.AppConfig.Node("GUI_LIB").Node("TTS_VOICE").Property("TYPE", "") == "NEUTRAL") // voice.SelectVoiceByHints(System.Speech.Synthesis.VoiceGender.Neutral); // voice.SpeakAsync(stringToSpeak); // } // catch // { // if this text to speech fails for whatever reason, just play the beep // System.Media.SystemSounds.Beep.Play(); // } //}).Start(); break; default: System.Media.SystemSounds.Beep.Play(); break; } return true; } catch { } return false; } return false; } private void Output_PlaybackStopped(object sender, NAudio.Wave.StoppedEventArgs e) { // Once the sound has finished, dispose of the NAudio objects. DisposeWave(); } private void DisposeWave() { if (output != null) { if (output.PlaybackState == NAudio.Wave.PlaybackState.Playing) output.Stop(); output.PlaybackStopped -= Output_PlaybackStopped; output.Dispose(); output = null; if (wave != null) { wave.Dispose(); wave = null; } if (mixer != null) { mixer.Dispose(); mixer = null; } if (volumeStream != null) { volumeStream.Dispose(); volumeStream = null; } } } public void Perform(List symbols) { if (_isAllowed) { ShowWindowNow(); foreach (var text in symbols) { PlaySoundNow(text); } } } public void CopyFrom(Actions source) { // Leave _owner alone. PlaySound = source.PlaySound; ShowWindow = source.ShowWindow; Volume =source. Volume; Balance =source. Balance; SoundFile = source.SoundFile; SoundType = source.SoundType; VerboseTextToSpeech = source.VerboseTextToSpeech; VoiceGender = source.VoiceGender; } public void Load(XmlNode source) { XmlNode actions = source.Node("ACTIONS"); PlaySound = actions.Property("PLAY_SOUND", false); ShowWindow = actions.Property("SHOW_WINDOW", false); SoundType = (SoundType)Enum.Parse(typeof(SoundType), actions.Property("SOUND_TYPE", "Beep")); // Only load the sound file if SoundType.Custom is used. if (SoundType == SoundType.Custom) SoundFile = actions.Property("SOUND_FILE", ""); // Set sound type to Beep if set to Custom and there is no sound file. if (SoundType == SoundType.Custom && string.IsNullOrEmpty(SoundFile)) SoundType = SoundType.Beep; Volume = actions.Property("VOLUME", DEFAULT_VOLUME); Balance = actions.Property("BALANCE", DEFAULT_BALANCE); VerboseTextToSpeech = actions.Property("VERBOSE_TEXT_TO_SPEECH", false); VoiceGender = (TIProSpeechManager.VoiceGender)Enum.Parse(typeof(TIProSpeechManager.VoiceGender), actions.Property("VOICE_GENDER", "Female")); } public void Save(XmlNode destination) { XmlNode act = destination.NewNode("ACTIONS"); act.SetProperty("PLAY_SOUND", PlaySound); act.SetProperty("SHOW_WINDOW", ShowWindow); act.SetProperty("VOLUME", Volume); act.SetProperty("BALANCE", Balance); // Only write the sound file location if SoundType.Custom is used and the sound file is not empty or null. if (SoundType == SoundType.Custom && string.IsNullOrEmpty(SoundFile) == false) act.SetProperty("SOUND_FILE", SoundFile); act.SetProperty("SOUND_TYPE", SoundType.ToString()); act.SetProperty("VERBOSE_TEXT_TO_SPEECH", VerboseTextToSpeech); act.SetProperty("VOICE_GENDER", VoiceGender.ToString()); } public void ShowDialog() { using (ActionsForm actionsForm = new ActionsForm(this)) { // Price alert windows do not require the show alert checkbox to be visible. if (HideShowAlert) actionsForm.HideShowAlert(); actionsForm.ShowDialog(); } } public void SetOwner(Form form) { _owner = form; } } }