using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Windows.Forms; using TradeIdeas.TIProData; using TradeIdeas.TIProGUI; // TODO override the way we save to the layout. namespace TradeIdeas.Dashboard { public partial class WatchList : TopListForm { private readonly int _listId; private readonly IConnectionMaster _connectionMaster; private readonly SymbolListsCacheManager _symbolListsCacheManager; /// /// This data is just a sample. For real we'd want the colors. Possibly more, like the secondary sort. /// However, keeping it simple would be a good idea, too. /// We could start from the saved format, and work backwards to see what /// we don't need (like position and size of window). In face, instead of this, we /// should be starting from an XML file, maybe even a saved layout. /// private static readonly Dictionary COLUMN_SETTINGS = new Dictionary { { "Common", "form=1&show0=D_Symbol&show1=Price&show2=FCP&show3=TV&show4=RD&col_ver=1&SL=X1o5&sort=MinFCP&X_NYSE=on&X_ARCA=on&X_AMEX=on&XN=on&X_OTC=on&X_PINK=on&X_CAT=on&X_CAV=on&XX=on&count=1000" }, { "Fundamentals", "form=1&show0=D_Symbol&show1=Price&show2=FCP&show3=MCap&show4=PERatio&col_ver=1&SL=X1o5&sort=MinFCP&X_NYSE=on&X_ARCA=on&X_AMEX=on&XN=on&X_OTC=on&X_PINK=on&X_CAT=on&X_CAV=on&XX=on&count=1000" }, { "Day Trader", "form=1&show0=D_Symbol&show1=Price&show2=FCP&show3=Vol5&show4=Range5P&show5=DUp5&col_ver=1&SL=X1o5&sort=MinFCP&X_NYSE=on&X_ARCA=on&X_AMEX=on&XN=on&X_OTC=on&X_PINK=on&X_CAT=on&X_CAV=on&XX=on&count=1000" }, { "Swing Trader", "form=1&show0=D_Symbol&show1=Price&show2=FCP&show3=MA200P&show4=GUD&show5=RV&col_ver=1&SL=X1o5&sort=MinFCP&X_NYSE=on&X_ARCA=on&X_AMEX=on&XN=on&X_OTC=on&X_PINK=on&X_CAT=on&X_CAV=on&XX=on&count=1000" } }; private static string DefautColumnListName { get { foreach (var kvp in COLUMN_SETTINGS) return kvp.Key; // There must be at least one setting! // But keep the compiler happy. throw new Exception(); } } private string _columnListName = null; /// /// This is an index into a table. /// If you specify an unknown or invalid name, it will be ignored. /// Note that this is public. /// The main program might want to change all of these at once. /// Currently there is no need for the main program to read or set this, /// but it brings up interesting possibilities. /// public string ColumnListName { get { return _columnListName; } set { if ((value != _columnListName) && (COLUMN_SETTINGS.ContainsKey(value))) { _columnListName = value; _symbolListsCacheManager.RenameList(_listId, "Watchlists|" + ColumnListName + "|" + UserVisibleName()); RequestData(); foreach (ToolStripMenuItem item in columnsToolStripMenuItem.DropDown.Items) item.Checked = item.Text == value; } } } private void RequestData() { Config = COLUMN_SETTINGS[_columnListName] + "&SL=" + _listId + "&WN=" + System.Web.HttpUtility.UrlEncode(UserVisibleName()); } /// /// The list id should never be reused. /// Use or to create a new window. /// These will set the list id appropriately. /// /// /// private WatchList(IConnectionMaster connectionMaster, int listId) : base(connectionMaster, null) { if (listId <= 0) throw new ArgumentOutOfRangeException("listId", listId, "Should be positive."); _listId = listId; _connectionMaster = connectionMaster; _symbolListsCacheManager = GuiEnvironment.GetSymbolListsCacheManager(_connectionMaster); InitializeComponent(); ParsedName parsedName = new ParsedName(_symbolListsCacheManager.GetListName(listId)); if (parsedName.OneOfOurs) ColumnListName = parsedName.ColumnListName; // It doesn't matter if we never set this, or if the setting failed. if (null == ColumnListName) ColumnListName = DefautColumnListName; } /// /// Look for an existing window that follows this list. /// You will probably use instead of this, but this allows for some interesting /// GUI possibilities. For example, the main program might show open windows differently than /// new windows. /// /// /// /// The window, if it exists, or null if no such window exists. public static WatchList Find(IConnectionMaster connectionMaster, int listId) { foreach (Form form in Application.OpenForms.Cast
().Where(x => !x.IsDisposed).ToList()) { WatchList result = form as WatchList; if ((null != result) && (result._connectionMaster == connectionMaster) && (result._listId == listId)) return result; } return null; } /// /// Find a window for this list and make the window visible. /// This will create a new window if required. /// /// /// public static void Show(IConnectionMaster connectionMaster, int listId) { WatchList watchList = Find(connectionMaster, listId); if (null != watchList) { watchList.Visible = true; watchList.WindowState = FormWindowState.Normal; watchList.BringToFront(); } else { watchList = new WatchList(connectionMaster, listId); watchList.Show(); } } /// /// Create a new window following a new symbol list. /// Before displaying the new window, this will prompt the user for a name and a list of symbols. /// /// public static void CreateNew(IConnectionMaster connectionMaster) { SymbolListsCacheManager cache = GuiEnvironment.GetSymbolListsCacheManager(connectionMaster); WatchList watchList = new WatchList(connectionMaster, cache.CreateList("Watchlists|Common|New List")); if (watchList.EditSymbolList()) if (watchList.Rename("List Created " + DateTime.Now.ToString(), "Name This Watchlist")) { watchList.Show(); return; } watchList.DeleteTheList(); watchList.Dispose(); } /// /// We store a lot of information in the symbol list. /// This is in a format that is easy us to read, but will not look too strange to the user /// if he uses a different program to browse the symbol lists. /// public class ParsedName { private static readonly char[] SPLIT = new char[] { '|' }; /// /// True if this appears to be a symbol list that is part of a watch list. /// public bool OneOfOurs { get; private set; } /// /// This is a user friendly string. /// This will be null if is false. /// public string WindowName { get; private set; } /// /// This is a value appropriate for /// This will be null if is false. /// public string ColumnListName { get; private set; } /// /// Parse a name and store the pieces in the newly created object. /// /// /// This is the standard list name returned by the standard symbol list manager. /// public ParsedName(string listName) { // I chose this simple way of parsing in part becuase GWT doesn't have full regular expressions. // But it should be reasonable, even if this account is shared with TI Pro. string[] pieces = listName.Split(SPLIT, 3); if (pieces.Length != 3) return; if (pieces[0] != "Watchlists") return; OneOfOurs = true; ColumnListName = pieces[1]; WindowName = pieces[2]; } } private void SetColumnList(object sender, EventArgs e) { ColumnListName = (sender as ToolStripMenuItem).Text; } private void symbolsToolStripMenuItem_Click(object sender, EventArgs e) { if (EditSymbolList()) RequestDataSoon(); } private object _currentWaitToken; private void RequestDataSoon() { object waitToken = new Object(); _currentWaitToken = waitToken; // Request a copy of the symbol list. This will prove that the symbol list got to the master database. // It does not prove that it got to the slaves, so there could still be a problem! Ideally we want the // next set of toplist data to include exactly the new symbol list. This is a decent approximation // of that. _connectionMaster.ListManager.Find(_listId).RequestFromServer((ListManager.OnListReturned)delegate { this.InvokeIfRequired((MethodInvoker)delegate { if (waitToken != _currentWaitToken) // Ignore old requests. return; RequestData(); }); }); } private bool EditSymbolList() { using (EditSymbolListDialog dialog = new EditSymbolListDialog(_symbolListsCacheManager.GetSymbols(_listId))) { if (dialog.ShowDialog() == DialogResult.OK) { HashSet allSymbols = new HashSet(); foreach (string line in dialog.Lines) if (line.Length > 0) allSymbols.Add(line.Trim().ToUpperInvariant()); _symbolListsCacheManager.SetAllSymbols(_listId, allSymbols); return true; } } return false; } private string UserVisibleName() { // Would it make sense to store this somewhere, rather than constantly recomputing it? ParsedName parsedName = new ParsedName(_symbolListsCacheManager.GetListName(_listId)); if (parsedName.OneOfOurs) return parsedName.WindowName; else return ""; } private void renameToolStripMenuItem_Click(object sender, EventArgs e) { Rename(UserVisibleName(), "Rename List"); } private bool Rename(string defaultName, string windowTitle) { using (TextResultDialog dialog = new TextResultDialog(windowTitle, "List name:")) { dialog.Answer = defaultName; if ((dialog.ShowDialog() == DialogResult.OK) && (dialog.Answer != "")) { // TODO "Watchlists" appears in a few places. It should be a constant. _symbolListsCacheManager.RenameList(_listId, "Watchlists|" + ColumnListName + "|" + dialog.Answer); RequestData(); return true; } } return false; } private void deleteThisWatchlistToolStripMenuItem_Click(object sender, EventArgs e) { if (MessageBox.Show("Are you sure you want to delete this watchlist?", "Delete This Watchlist", MessageBoxButtons.YesNo) == DialogResult.Yes) { DeleteTheList(); Dispose(); } } private void DeleteTheList() { _symbolListsCacheManager.DeleteList(_listId); } } }