/**
* Copyright (c) Lynn J. Gasch
* 7/17/04
* Redmond, Washington
*
* typhoonUI displays the contents of an xml file in a tree control.
* It was developed to support chess programming, but can really be
* used for any xml file you wish.
*
* Xml nodes that do not have children should be written within
* a single tag, as < tag anAttrib="value" /> so that they can
* be displayed on a single line (node) of the tree.
*
* Tags starting with "FEN" can be used to lauch chess board
* viewers with the board value (they must use the single-line
* formatting as described above to trigger this functionality).
*
* This app is pretty special purpose and I'm sure there are plenty
* of ways to break it with various inputs. I coded it up in a few
* hours and it meets our needs.
*/
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Xml;
using System.IO;
using System.Diagnostics;
using System.Text;
namespace typhoonUI
{
// TODO: write tag for eval; Write evals dumps to file
// and retrieve the text into a separate text block
// on selection of the node
// TODO: command line file launch
// TODO: speed up loading
///
/// Summary description for Form1.
///
public class Form1 : System.Windows.Forms.Form
{
#region Private Fields
///
/// inserted between tag names and their values for display
/// in the tree. For tags that have no children other than their
/// values, list them in the xml as
/// < tag attrib="something to display" /> and they will only
/// take up one tree node and be listed as
/// tag something to display.
///
private const string VALUE_SEPARATOR = " ";
///
/// Use this tag name to identify the board configuration node
/// in your xml. This will provide menu item and double-clicking
/// to launch the position in a viewer that you can specify.
///
private const string TAG_BOARD = "FEN";
///
/// name of a tag for which the first attrib should be added to its parent
/// node text. If it's the only child, the node itself will be skipped
///
private const string TAG_SCORE = "score";
///
/// Attributes named tip will be set as tool tip text for the node
///
private const string ATTRIB_TIP = "tip";
///
/// an attrib name to denote request for boldfacing the text.
/// In order for the text to be displayed, it must be the frst attrib.
///
private const string ATTRIB_BOLD = "bold";
///
/// Name of a (reusable) file that is written with the board
/// position and used as a command arg to the board viewer app.
///
private const string POSITION_FILE_NAME = "boardConfigFile.txt";
///
/// Windows designer generated code
///
private System.Windows.Forms.MainMenu mainMenu1;
private System.Windows.Forms.MenuItem menuItem1;
private System.Windows.Forms.MenuItem menuItem2;
private System.Windows.Forms.TreeView _treeView1;
///
/// appears when right-clicking on a "board" node. See
/// BOARD_TAG_NAME
///
private System.Windows.Forms.ContextMenu _contextMenu;
///
/// Path to the viewer app, to be collected from the user.
/// TODO: allow the user to enter it into the Path environment
/// variable and just attempt to run it; only request path
/// if fail to run.
///
private string _boardViewerAppPath = null;
///
/// the view app process, so we can kill it if another board
/// is to be viewed
///
private Process _process = null;
///
/// local dir for writing the board position file for input
/// into the viewer app.
///
private string _applicationDir;
///
/// Required designer variable.
///
private System.ComponentModel.Container components = null;
///
/// for displaying long or multi-line text
///
private ToolTip _tip;
private string _tipText;
#endregion
#region Construction / Destruction
///
/// The main thing
///
public Form1(string startFile)
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
// create the context menu that will be used for board nodes
_contextMenu = new ContextMenu();
MenuItem item = new MenuItem("View board");
item.Click += new EventHandler(OnViewBoardMenuClicked);
_contextMenu.MenuItems.Add(item);
_applicationDir = Path.GetDirectoryName(
Application.ExecutablePath);
_tip = new ToolTip();
_tipText = string.Empty;
_tip.SetToolTip(_treeView1, _tipText);
if ((startFile != null) && (startFile.Length > 0))
Display(startFile);
}
///
/// Clean up any resources being used.
///
protected override void Dispose( bool disposing )
{
if( disposing )
{
KillProcess();
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#endregion
#region Windows Form Designer generated code
///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
private void InitializeComponent()
{
this.mainMenu1 = new System.Windows.Forms.MainMenu();
this.menuItem1 = new System.Windows.Forms.MenuItem();
this.menuItem2 = new System.Windows.Forms.MenuItem();
this._treeView1 = new System.Windows.Forms.TreeView();
this.SuspendLayout();
//
// mainMenu1
//
this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
this.menuItem1});
//
// menuItem1
//
this.menuItem1.Index = 0;
this.menuItem1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
this.menuItem2});
this.menuItem1.Text = "File";
//
// menuItem2
//
this.menuItem2.Index = 0;
this.menuItem2.Text = "Open";
this.menuItem2.Click += new System.EventHandler(this.OnOpenMenuClick);
//
// _treeView1
//
this._treeView1.Anchor = (((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right);
this._treeView1.HideSelection = false;
this._treeView1.ImageIndex = -1;
this._treeView1.Name = "_treeView1";
this._treeView1.SelectedImageIndex = -1;
this._treeView1.Size = new System.Drawing.Size(680, 600);
this._treeView1.TabIndex = 0;
this._treeView1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.OnTreeViewMouseDown);
this._treeView1.DoubleClick += new System.EventHandler(this.OnTreeViewDoubleClick);
this._treeView1.MouseMove += new System.Windows.Forms.MouseEventHandler(this.OnTreeViewMouseMove);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(680, 598);
this.Controls.AddRange(new System.Windows.Forms.Control[] {
this._treeView1});
this.Menu = this.mainMenu1;
this.Name = "Form1";
this.Text = "Scott\'s Move Tree";
this.ResumeLayout(false);
}
#endregion
///
/// The main entry point for the application.
///
[STAThread]
static void Main(string[] args)
{
string filename = string.Empty;
if (args.Length > 0)
filename = args[0];
Application.Run(new Form1(filename));
}
///
/// Use Xml support to load and parse the indicated xml doc.
/// Populate tree control with it.
///
/// path to the xml file to load
private void Display(string filename)
{
_treeView1.BeginUpdate();
try
{
XmlDocument doc = new XmlDocument();
doc.Load(filename);
_treeView1.Nodes.Clear();
TreeNode root = new TreeNode(doc.DocumentElement.Name);
_treeView1.Nodes.Add(root);
// recursively add all
StringBuilder builder = new StringBuilder();
foreach(XmlNode node in doc.DocumentElement.ChildNodes)
{
AddNode(node, ref root, ref builder);
}
}
catch(Exception e)
{
MessageBox.Show(this, e.Message + "\r\n" +
e.StackTrace, "Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
_treeView1.EndUpdate();
}
///
/// Recursively add nodes to the tree.
///
/// the current info to add; corresponds
/// to the parent node so that we are adding its children.
/// parent node in the tree
/// on;y create one instance of string builder
/// and use it for text manipulation
/// Nodes that don't have any nested nodes (other than their
/// own "value") should be written in the form
/// < tagName attrib="Attrib value" />
/// In this case, the tag name and the first attrib value are
/// shown in one line (in one node) in the tree. NOTE: any
/// additional tags are ignored. (the attrib value is not required).
///
private void AddNode(XmlNode element, ref TreeNode parent,
ref StringBuilder builder)
{
builder.Length = 0;
string text = element.Name;
// special cases:
// - score: append parent text with score instead of adding
// it as a node.
// - any with attribs - append first attrib val to node text
// - bold - (must be first attrib) - boldface the node
// - tip - (any attrib) - set as the tag of the node for tooltip
if (text.Equals(TAG_SCORE))
{
builder.Append(parent.Text);
builder.Append(VALUE_SEPARATOR);
builder.Append(TAG_SCORE);
builder.Append(" ");
builder.Append(element.Attributes[0].Value);
parent.Text = builder.ToString();
// done
}
else
{
builder.Append(text);
XmlAttributeCollection coll = element.Attributes;
int numAttribs = coll.Count;
XmlAttribute attrib = null;
if (numAttribs > 0)
{
attrib = coll[0];
// append the tag name with the first attrib value
builder.Append(VALUE_SEPARATOR);
builder.Append(attrib.Value);
}
TreeNode node = new TreeNode(builder.ToString());
if ((attrib != null) && attrib.Name.Equals(ATTRIB_BOLD))
node.ForeColor = Color.Red;
if (numAttribs > 1)
{
attrib = coll[1];
if (attrib.Name.Equals(ATTRIB_TIP))
{
node.Tag = attrib.Value;
}
}
parent.Nodes.Add(node);
foreach (XmlElement child in element.ChildNodes)
{
AddNode(child, ref node, ref builder);
}
}
}
///
/// Gets and saves the path to the viewer app from the user.
///
private DialogResult SetViewerAppPath()
{
DialogResult result = DialogResult.OK;
if (_boardViewerAppPath == null)
{
result = MessageBox.Show(this,
"Please set path to Winboard or XBoard viewer app",
"Input requested", MessageBoxButtons.OKCancel,
MessageBoxIcon.Question);
if ( result == DialogResult.OK)
{
OpenFileDialog browse = new OpenFileDialog();
browse.Filter = "Executable files (*.exe)|*.exe";
browse.Multiselect = false;
result = browse.ShowDialog(this);
if (result == DialogResult.OK)
{
_boardViewerAppPath = browse.FileName;
}
}
}
return result;
}
///
/// Closes the viewer app
///
private void KillProcess()
{
if (_process != null)
{
if (!_process.HasExited)
{
if (!_process.CloseMainWindow())
_process.Kill();
}
_process = null;
}
}
///
/// Writes board position to file and calls the viewer app with it.
///
/// text of the board node, starting with the
/// "board" tag and separator
private void ViewBoard(string text)
{
string boardCode = text.Substring(TAG_BOARD.Length
+ VALUE_SEPARATOR.Length);
StreamWriter f = new StreamWriter(new FileStream(
Path.Combine(_applicationDir, POSITION_FILE_NAME),
FileMode.Create, FileAccess.Write));
f.WriteLine(boardCode);
f.Close();
if (SetViewerAppPath() == DialogResult.OK)
{
try
{
KillProcess();
ProcessStartInfo pInfo =
new ProcessStartInfo(_boardViewerAppPath);
pInfo.Arguments =
"/mode editposition /ncp /top /coords /size small /lpf " +
Path.Combine(_applicationDir, POSITION_FILE_NAME);
_process = new Process();
_process.StartInfo = pInfo;
_process.Start();
}
catch(Exception)
{
MessageBox.Show(this, "Unable to run viewer app", "Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
_boardViewerAppPath = null;
_process = null;
}
}
}
#region Event Handlers
///
/// Handler for File | Open menu. Get the xml file to load.
///
/// ignored
/// ignored
private void OnOpenMenuClick(object sender, System.EventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
dialog.Multiselect = false;
dialog.Filter = "XML files (*.xml)|*.xml|All files (*.*)|*.*";
if (dialog.ShowDialog(this) == DialogResult.OK)
{
Display(dialog.FileName);
}
}
///
/// Handles mouse down. if right-click on "board" node,
/// displays board context menu.
///
/// ignored
/// select the tree node at the click
/// coords
private void OnTreeViewMouseDown(object sender, MouseEventArgs e)
{
// this doesn't automatically happen
_treeView1.SelectedNode = _treeView1.GetNodeAt(e.X, e.Y);
if (e.Button.Equals(MouseButtons.Right))
{
string text = _treeView1.SelectedNode.Text;
if (text.StartsWith(TAG_BOARD))
{
_treeView1.ContextMenu = _contextMenu;
}
}
}
///
/// when view board menu item chosen, writes the selected board
/// position to a file and calls the viewer app.
///
/// ignored
/// ignored
private void OnViewBoardMenuClicked(object sender, EventArgs e)
{
string text = _treeView1.SelectedNode.Text;
if (text.StartsWith(TAG_BOARD))
{
ViewBoard(text);
}
_treeView1.ContextMenu = null;
}
///
/// If double-click a "board" node, calls the viewer
/// for the position
///
///
///
private void OnTreeViewDoubleClick(object sender, System.EventArgs e)
{
string text = _treeView1.SelectedNode.Text;
if (text.StartsWith(TAG_BOARD))
{
ViewBoard(text);
}
}
///
/// set tooltip if over a node with a displayable tag
///
/// ignored
/// holds mouse position
private void OnTreeViewMouseMove(object sender, MouseEventArgs e)
{
TreeNode node = _treeView1.GetNodeAt(e.X, e.Y);
string tag = string.Empty;
if ((node != null) && (node.Tag != null))
{
tag = (string)node.Tag;
}
if (!tag.Equals(_tipText))
{
_tipText = tag;
_tip.SetToolTip(_treeView1, _tipText);
}
}
#endregion
}
}