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;
}
}
}
}