using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Xml; using TradeIdeas.XML; using TradeIdeas.TIProData; using TradeIdeas.ServerConnection; // TODO: Better testing of the event handlers. If we select a lot of items up top, and we change the import // text, what happens? Does the callback for a change to the selected items come at the end, after all items // have been unseleted, or does it call the callback after each item has been unseleted? // Does the selection callback happen often enough? What if we go from selecting 5 items to 6 items? Does it // get called? Does it need to get called? Does this matter? // What if we go from two items to none? Will the selected item be -1 in both cases? Does this matter? namespace TradeIdeas.TIProGUI.CloudLayout { public partial class LoadFromCloud : Form { public static readonly WindowIconCache WindowIconCache = new WindowIconCache("LOAD_FROM_CLOUD"); private readonly SendManager _sendManager; private volatile bool _aborted; private string _baseWindowName; public LoadFromCloud(SendManager sendManager) { _sendManager = sendManager; InitializeComponent(); WindowIconCache.SetIcon(this); _baseWindowName = Text; Text = _baseWindowName + " (Loading)"; DescribeSelectedItems(); if (!IsHandleCreated) CreateHandle(); RequestList(); //Set default list view listView1.View = View.Details; detailsToolStripMenuItem.Checked = true; //Allow scrollbars to longDescriptionTextBox longDescriptionTextBox.ScrollBars = ScrollBars.Vertical; Font = GuiEnvironment.FontSettings; } private void RequestList() { _sendManager.SendMessage(TalkWithServer.CreateMessage("command", "cloud_list"), RequestListResponse); } /// /// Called directly from communications library. Probably not in GUI thread. /// /// From server, or null if there is a communications error. /// Unused. private void RequestListResponse(byte[] body, object unused) { if (_aborted) return; if (null == body) RequestList(); else this.InvokeIfRequired(delegate { RequestListResponse(body); }); } /// /// Called in GUI thread. /// /// Received from server. This will not be null. private void RequestListResponse(byte[] body) { if (_aborted) return; XmlDocument document = XmlHelper.Get(body); foreach (XmlNode node in document.Node(0).Node("ROWS").Enum()) { CloudLayoutItem dataItem = new CloudLayoutItem(node); ListViewItem guiItem = new ListViewItem(); guiItem.Text = dataItem.ShortDescription; guiItem.SubItems.Add(dataItem.FileType); guiItem.SubItems.Add(dataItem.WindowCount); guiItem.ImageIndex = GetIconId(dataItem.Icon); guiItem.Tag = dataItem; listView1.Items.Add(guiItem); } if (listView1.Items.Count < 1) Text = _baseWindowName + " (No Data)"; else Text = _baseWindowName; DescribeSelectedItems(); } private int GetIconId(string iconName) { WindowIconCache source = new WindowIconCache(iconName); Icon icon = source.Icon; try { // This is required to get a high quality version of the icon. If you // add the icon directly to the image list, the icon will be resized // automatically, but it won't look as good. using (Icon smallIcon = new Icon(icon, smallImageList.ImageSize)) { smallImageList.Images.Add(smallIcon); } using (Icon largeIcon = new Icon(icon, largeImageList.ImageSize)) { largeImageList.Images.Add(largeIcon); } } catch { // Is it possible that the image was added to one list but not the other? while (smallImageList.Images.Count > largeImageList.Images.Count) smallImageList.Images.RemoveAt(largeImageList.Images.Count); return -1; } return smallImageList.Images.Count - 1; } private void largeIconToolStripMenuItem_Click(object sender, EventArgs e) { listView1.View = View.LargeIcon; largeIconToolStripMenuItem.Checked = true; detailsToolStripMenuItem.Checked = smallIconToolStripMenuItem.Checked = listToolStripMenuItem.Checked = tileToolStripMenuItem.Checked = false; } private void detailsToolStripMenuItem_Click(object sender, EventArgs e) { listView1.View = View.Details; detailsToolStripMenuItem.Checked = true; largeIconToolStripMenuItem.Checked = smallIconToolStripMenuItem.Checked = listToolStripMenuItem.Checked = tileToolStripMenuItem.Checked = false; } private void smallIconToolStripMenuItem_Click(object sender, EventArgs e) { listView1.View = View.SmallIcon; smallIconToolStripMenuItem.Checked = true; largeIconToolStripMenuItem.Checked = detailsToolStripMenuItem.Checked = listToolStripMenuItem.Checked = tileToolStripMenuItem.Checked = false; } private void listToolStripMenuItem_Click(object sender, EventArgs e) { listView1.View = View.List; listToolStripMenuItem.Checked = true; largeIconToolStripMenuItem.Checked = detailsToolStripMenuItem.Checked = smallIconToolStripMenuItem.Checked = tileToolStripMenuItem.Checked = false; } private void tileToolStripMenuItem_Click(object sender, EventArgs e) { listView1.View = View.Tile; tileToolStripMenuItem.Checked = true; largeIconToolStripMenuItem.Checked = detailsToolStripMenuItem.Checked = smallIconToolStripMenuItem.Checked = listToolStripMenuItem.Checked = false; } private void listView1_ItemActivate(object sender, EventArgs e) { loadButton.PerformClick(); } /// /// If we are in the process of handing a user change, and we make another change to the GUI, /// we don't want to keep firing events. We only want to fire events when the user changes /// the GUI. We use thise flag to mask secondary changes. /// private bool _ignoreChanges; private void listView1_SelectedIndexChanged(object sender, EventArgs e) { if (_ignoreChanges) return; if (listView1.SelectedItems.Count > 0) { _ignoreChanges = true; importFileTextBox.Clear(); _ignoreChanges = false; } DescribeSelectedItems(); } private void DescribeSelectedItems() { if (_loadInProgress) { // Disable everything but cancel. Usually we are in this state for a very short // amount of time, and you can't even see anything. But if it is slow, I want to // make sure it looks good. It should be obvous that you hit something. You // shouldn't be able to load a second layout as that might be confusing. And // starting anything else seems like a bad idea because as soon as the layout is // loaded, the other operations will be aborted. listView1.Enabled = false; importFileTextBox.ReadOnly = true; loadButton.Enabled = false; deleteButton.Enabled = false; shareButton.Enabled = false; revokeShartingButton.Enabled = false; return; } int selectedCount = listView1.SelectedItems.Count; if (importFileTextBox.Text != "") longDescriptionTextBox.Text = "Import layout from another user."; else if (selectedCount == 1) longDescriptionTextBox.Text = (listView1.SelectedItems[0].Tag as CloudLayoutItem).LongDescription; else longDescriptionTextBox.Text = "Select a file."; loadButton.Enabled = (selectedCount == 1) || (importFileTextBox.Text != ""); deleteButton.Enabled = selectedCount > 0; shareButton.Enabled = (selectedCount == 1) && (null != (listView1.SelectedItems[0].Tag as CloudLayoutItem).Code); // I want to say revokable = listView1.SelectedItems.Any(item => null != (current.Tag as CloudLayoutItem).Code) // But Any() doesn't work with this type of list. bool revokable = false; foreach (ListViewItem current in listView1.SelectedItems) { if (null != (current.Tag as CloudLayoutItem).Code) { revokable = true; break; } } revokeShartingButton.Enabled = revokable; } public static void DoIt(SendManager sendManager) { using (LoadFromCloud dialog = new LoadFromCloud(sendManager)) { dialog.ShowDialog(); } } private void importFileTextBox_TextChanged(object sender, EventArgs e) { if (_ignoreChanges) return; if (importFileTextBox.Text != "") { _ignoreChanges = true; listView1.SelectedItems.Clear(); _ignoreChanges = false; } DescribeSelectedItems(); } private void LoadFromCloud_FormClosed(object sender, FormClosedEventArgs e) { _aborted = true; } private void deleteButton_Click(object sender, EventArgs e) { // Create a copy of the list. As soon as we start deleting things from the GUI, the // original list will change and the iterators will probably be invalidated. List toDelete = new List(listView1.SelectedItems.Count); foreach (ListViewItem item in listView1.SelectedItems) { SendDeleteCommand((item.Tag as CloudLayoutItem).ID); toDelete.Add(item); } foreach (ListViewItem item in toDelete) listView1.Items.Remove(item); } private void SendDeleteCommand(string id) { var command = TalkWithServer.CreateMessage("command", "cloud_delete", "id", id); _sendManager.SendMessage(command, DeleteCommandResponse, false, command); } private void DeleteCommandResponse(byte[] response, object originalCommand) { if (_aborted) // For simplicity we stop retrying anything when the user closes the window. That style // is helpful with other commands because they have a GUI component to their reaction. // Here I'm just repeating that style. It is tempting to create a seperate object that // remembers these requests seperate from the GUI, like the symbol list manager and several // other objects, but that might be overkill. In the worst case the user just has to // click the delete button again. return; if (null == response) // On communications failure resend the same command. _sendManager.SendMessage(originalCommand as Dictionary, DeleteCommandResponse, false, originalCommand); // else... report success? That seems painful and confusing at best. We actually have a // seperate window to say please wait and to eventually report success or failure when // saving a strategy. That is currently ugly to say the least. } private void shareButton_Click(object sender, EventArgs e) { if (listView1.SelectedItems.Count < 1) // This should not happen! return; string code = (listView1.SelectedItems[0].Tag as CloudLayoutItem).Code; if (null == code) // This should not happen! return; try { Clipboard.SetText(code); } catch { } //MessageBox.Show("A link to these settings has been copied to the clipboard.\r\n" // + "You can paste that link into an email or IM.", // "Share from Cloud"); //Open collaboarte form CollaborateForm form = new CollaborateForm(); form.ConfigString = code; form.Text = "Load from Cloud"; form.SetCloudOptions(); form.ShowDialog(this); form.Dispose(); } private void revokeShartingButton_Click(object sender, EventArgs e) { foreach (ListViewItem listViewItem in listView1.SelectedItems) { CloudLayoutItem cloudLayoutItem = listViewItem.Tag as CloudLayoutItem; if (null != cloudLayoutItem.Code) { cloudLayoutItem.InvalidateCode(); SendRevokeCommand(cloudLayoutItem.ID); } } DescribeSelectedItems(); } private void SendRevokeCommand(string id) { var message = TalkWithServer.CreateMessage("command", "cloud_revoke", "id", id); _sendManager.SendMessage(message, RevokeCommandResponse, false, id); } private void RevokeCommandResponse(byte[] body, object clientId) { if (_aborted) // See the notes in DeleteCommandResponse. We stop trying when someone closes the window. return; if (null == body) // Communications error. Retry. SendRevokeCommand((string)clientId); } private bool _loadInProgress; private void loadButton_Click(object sender, EventArgs e) { Text = _baseWindowName + " (Loading)"; _loadInProgress = true; DescribeSelectedItems(); Dictionary command; if (listView1.SelectedItems.Count > 0) command = TalkWithServer.CreateMessage("command", "cloud_load", "id", (listView1.SelectedItems[0].Tag as CloudLayoutItem).ID); else command = TalkWithServer.CreateMessage("command", "cloud_import", "code", importFileTextBox.Text); SendLoadImportCommand(command); } private void SendLoadImportCommand(object command) { _sendManager.SendMessage((Dictionary)command, LoadImportCommandResponse, false, command); } private void LoadImportCommandResponse(byte[] body, object clientId) { if (_aborted) return; if (null == body) SendLoadImportCommand(clientId); else this.InvokeIfRequired(delegate { LoadImportCommandResponse(body); }); } /// /// This is always called in the GUI thread. /// /// Never null. private void LoadImportCommandResponse(byte[] body) { XmlNode wrapper = XmlHelper.Get(body).Node(0).Node("LAYOUT"); bool clearPrevious = wrapper.Property("CLEAR_PREVIOUS", false); XmlDocument layout = XmlHelper.Get(Encoding.UTF8.GetBytes(wrapper.Text())); if (null == layout) MessageBox.Show("Unable to load this item.", "Load from Cloud"); else LayoutManager.Instance().Restore(layout.DocumentElement, clearPrevious); DialogResult = System.Windows.Forms.DialogResult.OK; } } public class CloudLayoutItem { /// /// This says we should load the file like a traditional layout. I.e. we should start /// by closing old windows before opening the new ones described in here. This particular /// copy is only to display on the screen for the end user's information. We will get /// another copy when we ask for more details about the specific strategy. /// public bool ClearPreviousLayout { get; private set; } /// /// This is the code that you can give to other people so they can use your settings. /// /// This can be null to say that we don't know the code. /// public string Code { get; private set; } /// /// Mark the code as unknown or unavailable. This goes with the revoke button. Revoke /// actually just changes the value, rather than removing it. But only the server knows /// the new code. For simplicity we don't automtically grab the new one from the server. /// The user would have to close and reopen the window to see the new code. /// public void InvalidateCode() { Code = null; } /// /// A string suitable for submition to /// public string Icon { get; private set; } /// /// This is a unique id which never changes and is never reused. Currently it's an integer /// but there's no reason it couldn't change to a long integer or something stranger in the /// future. No reason to try to interpret it here. /// public string ID { get; private set; } /// /// To display in the big text box. /// public string LongDescription { get; private set; } /// /// To display in the list of items, next to the icon. /// public string ShortDescription { get; private set; } /// /// For the user's info, only. No real value. Display it for him. /// public string WindowCount { get; private set; } /// /// A user friendly string to display for the user. This takes and /// into account. /// public string FileType { get; private set; } /// /// Load this from the server. /// /// public CloudLayoutItem(XmlNode source) { ClearPreviousLayout = source.Property("CLEAR_PREVIOUS_LAYOUT") == "1"; Code = source.Property("CODE"); Icon = source.Property("ICON"); ID = source.Property("ID"); LongDescription = source.Property("LONG_DESCRIPTION"); ShortDescription = source.Property("SHORT_DESCRIPTION"); WindowCount = source.Property("WINDOW_COUNT"); if (WindowCount == "0") WindowCount = ""; if (ClearPreviousLayout) FileType = "Layout"; else switch (Icon) { case "ALERTS": FileType = "Alert Window"; break; case "TOP_LIST": FileType = "Top List Window"; break; case "MULTI_STRATEGY": FileType = "Multistrategy Window"; break; default: FileType = ""; break; } } } }