/** * 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 } }