Initial checkin of typhoon UI project.
[typhoon-ui.git] / Form1.cs
1 /**\r
2  * Copyright (c) Lynn J. Gasch\r
3  * 7/17/04\r
4  * Redmond, Washington\r
5  * \r
6  * typhoonUI displays the contents of an xml file in a tree control.\r
7  * It was developed to support chess programming, but can really be\r
8  * used for any xml file you wish.\r
9  * \r
10  * Xml nodes that do not have children should be written within\r
11  * a single tag, as < tag anAttrib="value" /> so that they can \r
12  * be displayed on a single line (node) of the tree.\r
13  * \r
14  * Tags starting with "FEN" can be used to lauch chess board\r
15  * viewers with the board value (they must use the single-line\r
16  * formatting as described above to trigger this functionality). \r
17  * \r
18  * This app is pretty special purpose and I'm sure there are plenty\r
19  * of ways to break it with various inputs. I coded it up in a few\r
20  * hours and it meets our needs. \r
21  */\r
22 \r
23 using System;\r
24 using System.Drawing;\r
25 using System.Collections;\r
26 using System.ComponentModel;\r
27 using System.Windows.Forms;\r
28 using System.Data;\r
29 using System.Xml;\r
30 using System.IO;\r
31 using System.Diagnostics;\r
32 using System.Text;\r
33 \r
34 namespace typhoonUI\r
35 {\r
36         // TODO: write tag for eval; Write evals dumps to file\r
37         // and retrieve the text into a separate text block \r
38         // on selection of the node\r
39         // TODO: command line file launch\r
40         // TODO: speed up loading\r
41 \r
42         /// <summary>\r
43         /// Summary description for Form1.\r
44         /// </summary>\r
45         public class Form1 : System.Windows.Forms.Form\r
46         {\r
47                 #region Private Fields\r
48 \r
49                 /// <summary>\r
50                 /// inserted between tag names and their values for display\r
51                 /// in the tree. For tags that have no children other than their\r
52                 /// values, list them in the xml as \r
53                 /// &lt; tag attrib="something to display" /> and they will only\r
54                 /// take up one tree node and be listed as \r
55                 /// tag something to display.\r
56                 /// </summary>\r
57                 private const string VALUE_SEPARATOR = " ";\r
58 \r
59                 /// <summary>\r
60                 /// Use this tag name to identify the board configuration node\r
61                 /// in your xml. This will provide menu item and double-clicking\r
62                 /// to launch the position in a viewer that you can specify.\r
63                 /// </summary>\r
64                 private const string TAG_BOARD = "FEN";\r
65 \r
66                 /// <summary>\r
67                 /// name of a tag for which the first attrib should be added to its parent\r
68                 /// node text. If it's the only child, the node itself will be skipped\r
69                 /// </summary>\r
70                 private const string TAG_SCORE = "score";\r
71 \r
72                 /// <summary>\r
73                 /// Attributes named tip will be set as tool tip text for the node\r
74                 /// </summary>\r
75                 private const string ATTRIB_TIP = "tip";\r
76 \r
77                 /// <summary>\r
78                 /// an attrib name to denote request for boldfacing the text.\r
79                 /// In order for the text to be displayed, it must be the frst attrib.\r
80                 /// </summary>\r
81                 private const string ATTRIB_BOLD = "bold";\r
82 \r
83                 /// <summary>\r
84                 /// Name of a (reusable) file that is written with the board\r
85                 /// position and used as a command arg to the board viewer app.\r
86                 /// </summary>\r
87                 private const string POSITION_FILE_NAME = "boardConfigFile.txt";\r
88 \r
89                 /// <summary>\r
90                 /// Windows designer generated code\r
91                 /// </summary>\r
92                 private System.Windows.Forms.MainMenu mainMenu1;\r
93                 private System.Windows.Forms.MenuItem menuItem1;\r
94                 private System.Windows.Forms.MenuItem menuItem2;\r
95                 private System.Windows.Forms.TreeView _treeView1;\r
96 \r
97                 /// <summary>\r
98                 /// appears when right-clicking on a "board" node. See\r
99                 /// BOARD_TAG_NAME\r
100                 /// </summary>\r
101                 private System.Windows.Forms.ContextMenu _contextMenu;\r
102 \r
103                 /// <summary>\r
104                 /// Path to the viewer app, to be collected from the user.\r
105                 /// TODO: allow the user to enter it into the Path environment \r
106                 /// variable and just attempt to run it; only request path\r
107                 /// if fail to run.\r
108                 /// </summary>\r
109                 private string _boardViewerAppPath = null;\r
110 \r
111                 /// <summary>\r
112                 /// the view app process, so we can kill it if another board\r
113                 /// is to be viewed\r
114                 /// </summary>\r
115                 private Process _process = null;\r
116 \r
117                 /// <summary>\r
118                 /// local dir for writing the board position file for input \r
119                 /// into the viewer app.\r
120                 /// </summary>\r
121                 private string _applicationDir;\r
122 \r
123                 /// <summary>\r
124                 /// Required designer variable.\r
125                 /// </summary>\r
126                 private System.ComponentModel.Container components = null;\r
127 \r
128                 /// <summary>\r
129                 /// for displaying long or multi-line text\r
130                 /// </summary>\r
131                 private ToolTip _tip;\r
132 \r
133                 private string _tipText;\r
134 \r
135                 #endregion\r
136 \r
137                 #region Construction / Destruction\r
138 \r
139                 /// <summary>\r
140                 /// The main thing\r
141                 /// </summary>\r
142                 public Form1(string startFile)\r
143                 {\r
144                         //\r
145                         // Required for Windows Form Designer support\r
146                         //\r
147                         InitializeComponent();\r
148 \r
149                         // create the context menu that will be used for board nodes\r
150                         _contextMenu = new ContextMenu();\r
151                         MenuItem item = new MenuItem("View board");\r
152                         item.Click += new EventHandler(OnViewBoardMenuClicked);\r
153                         _contextMenu.MenuItems.Add(item);\r
154 \r
155                         _applicationDir = Path.GetDirectoryName(\r
156                                 Application.ExecutablePath);\r
157 \r
158                         _tip = new ToolTip();\r
159                         _tipText = string.Empty;\r
160                         _tip.SetToolTip(_treeView1, _tipText);\r
161 \r
162                         if ((startFile != null) && (startFile.Length > 0))\r
163                                 Display(startFile);\r
164                 }\r
165 \r
166                 /// <summary>\r
167                 /// Clean up any resources being used.\r
168                 /// </summary>\r
169                 protected override void Dispose( bool disposing )\r
170                 {\r
171                         if( disposing )\r
172                         {\r
173                                 KillProcess();\r
174                                 if (components != null) \r
175                                 {\r
176                                         components.Dispose();\r
177                                 }\r
178                         }\r
179                         base.Dispose( disposing );\r
180                 }\r
181 \r
182                 \r
183                 #endregion\r
184 \r
185                 #region Windows Form Designer generated code\r
186                 /// <summary>\r
187                 /// Required method for Designer support - do not modify\r
188                 /// the contents of this method with the code editor.\r
189                 /// </summary>\r
190                 private void InitializeComponent()\r
191                 {\r
192                         this.mainMenu1 = new System.Windows.Forms.MainMenu();\r
193                         this.menuItem1 = new System.Windows.Forms.MenuItem();\r
194                         this.menuItem2 = new System.Windows.Forms.MenuItem();\r
195                         this._treeView1 = new System.Windows.Forms.TreeView();\r
196                         this.SuspendLayout();\r
197                         // \r
198                         // mainMenu1\r
199                         // \r
200                         this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {\r
201                                                                                                                                                                           this.menuItem1});\r
202                         // \r
203                         // menuItem1\r
204                         // \r
205                         this.menuItem1.Index = 0;\r
206                         this.menuItem1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {\r
207                                                                                                                                                                           this.menuItem2});\r
208                         this.menuItem1.Text = "File";\r
209                         // \r
210                         // menuItem2\r
211                         // \r
212                         this.menuItem2.Index = 0;\r
213                         this.menuItem2.Text = "Open";\r
214                         this.menuItem2.Click += new System.EventHandler(this.OnOpenMenuClick);\r
215                         // \r
216                         // _treeView1\r
217                         // \r
218                         this._treeView1.Anchor = (((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) \r
219                                 | System.Windows.Forms.AnchorStyles.Left) \r
220                                 | System.Windows.Forms.AnchorStyles.Right);\r
221                         this._treeView1.HideSelection = false;\r
222                         this._treeView1.ImageIndex = -1;\r
223                         this._treeView1.Name = "_treeView1";\r
224                         this._treeView1.SelectedImageIndex = -1;\r
225                         this._treeView1.Size = new System.Drawing.Size(680, 600);\r
226                         this._treeView1.TabIndex = 0;\r
227                         this._treeView1.MouseDown += new System.Windows.Forms.MouseEventHandler(this.OnTreeViewMouseDown);\r
228                         this._treeView1.DoubleClick += new System.EventHandler(this.OnTreeViewDoubleClick);\r
229                         this._treeView1.MouseMove += new System.Windows.Forms.MouseEventHandler(this.OnTreeViewMouseMove);\r
230                         // \r
231                         // Form1\r
232                         // \r
233                         this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);\r
234                         this.ClientSize = new System.Drawing.Size(680, 598);\r
235                         this.Controls.AddRange(new System.Windows.Forms.Control[] {\r
236                                                                                                                                                   this._treeView1});\r
237                         this.Menu = this.mainMenu1;\r
238                         this.Name = "Form1";\r
239                         this.Text = "Scott\'s Move Tree";\r
240                         this.ResumeLayout(false);\r
241 \r
242                 }\r
243                 #endregion\r
244 \r
245                 /// <summary>\r
246                 /// The main entry point for the application.\r
247                 /// </summary>\r
248                 [STAThread]\r
249                 static void Main(string[] args) \r
250                 {\r
251                         string filename = string.Empty;\r
252                         if (args.Length > 0)\r
253                                 filename = args[0];\r
254 \r
255                         Application.Run(new Form1(filename));\r
256                 }\r
257 \r
258                 /// <summary>\r
259                 /// Use Xml support to load and parse the indicated xml doc.\r
260                 /// Populate tree control with it.\r
261                 /// </summary>\r
262                 /// <param name="filename">path to the xml file to load</param>\r
263                 private void Display(string filename)\r
264                 {\r
265                         _treeView1.BeginUpdate();\r
266                         try\r
267                         {\r
268                                 XmlDocument doc = new XmlDocument();\r
269                                 doc.Load(filename);\r
270                                 _treeView1.Nodes.Clear();\r
271                                 TreeNode root = new TreeNode(doc.DocumentElement.Name);\r
272                                 _treeView1.Nodes.Add(root);\r
273                                 // recursively add all\r
274                                 StringBuilder builder = new StringBuilder();\r
275                                 foreach(XmlNode node in doc.DocumentElement.ChildNodes)\r
276                                 {\r
277                                         AddNode(node, ref root, ref builder);\r
278                                 }\r
279                         }\r
280                         catch(Exception e)\r
281                         {\r
282                                 MessageBox.Show(this, e.Message + "\r\n" +\r
283                                         e.StackTrace, "Error", \r
284                                         MessageBoxButtons.OK, MessageBoxIcon.Error);\r
285                         }\r
286                         _treeView1.EndUpdate();\r
287                 }\r
288 \r
289                 /// <summary>\r
290                 /// Recursively add nodes to the tree. \r
291                 /// </summary>\r
292                 /// <param name="element">the current info to add; corresponds\r
293                 /// to the parent node so that we are adding its children.</param>\r
294                 /// <param name="parent">parent node in the tree</param>\r
295                 /// <param name="builder">on;y create one instance of string builder\r
296                 /// and use it for text manipulation</param>\r
297                 /// <remarks>Nodes that don't have any nested nodes (other than their\r
298                 /// own "value") should be written in the form \r
299                 /// &lt; tagName attrib="Attrib value" />\r
300                 /// In this case, the tag name and the first attrib value are\r
301                 /// shown in one line (in one node) in the tree. NOTE: any\r
302                 /// additional tags are ignored. (the attrib value is not required).\r
303                 /// </remarks>\r
304                 private void AddNode(XmlNode element, ref TreeNode parent,\r
305                         ref StringBuilder builder)\r
306                 {\r
307                         builder.Length = 0;\r
308                         string text = element.Name;\r
309 \r
310                         // special cases:\r
311                         //  - score: append parent text with score instead of adding \r
312                         //    it as a node. \r
313                         //  - any with attribs - append first attrib val to node text\r
314                         //              - bold - (must be first attrib) - boldface the node\r
315                         //              - tip - (any attrib) - set as the tag of the node for tooltip\r
316 \r
317                         if (text.Equals(TAG_SCORE))\r
318                         {\r
319                                 builder.Append(parent.Text);\r
320                                 builder.Append(VALUE_SEPARATOR);\r
321                                 builder.Append(TAG_SCORE);\r
322                                 builder.Append(" ");\r
323                                 builder.Append(element.Attributes[0].Value);\r
324                                 parent.Text = builder.ToString();\r
325                                 // done\r
326                         }\r
327                         else\r
328                         {\r
329                                 builder.Append(text);\r
330                                 XmlAttributeCollection coll = element.Attributes;\r
331                                 int numAttribs = coll.Count;\r
332                                 XmlAttribute attrib = null;\r
333                                 if (numAttribs > 0)\r
334                                 {\r
335                                         attrib = coll[0];\r
336                                         // append the tag name with the first attrib value\r
337                                         builder.Append(VALUE_SEPARATOR);\r
338                                         builder.Append(attrib.Value);\r
339                                 }\r
340                                 TreeNode node = new TreeNode(builder.ToString());\r
341 \r
342                                 if ((attrib != null) && attrib.Name.Equals(ATTRIB_BOLD))\r
343                                         node.ForeColor = Color.Red;\r
344                                 if (numAttribs > 1)\r
345                                 {                                                               \r
346                                         attrib = coll[1];\r
347                                         if (attrib.Name.Equals(ATTRIB_TIP))\r
348                                         {\r
349                                                 node.Tag = attrib.Value;\r
350                                         }\r
351                                 }\r
352 \r
353                                 parent.Nodes.Add(node); \r
354 \r
355                                 foreach (XmlElement child in element.ChildNodes)\r
356                                 {\r
357                                         AddNode(child, ref node, ref builder);\r
358                                 }\r
359                         }\r
360                 }\r
361 \r
362                 /// <summary>\r
363                 /// Gets and saves the path to the viewer app from the user.\r
364                 /// </summary>\r
365                 private DialogResult SetViewerAppPath()\r
366                 {\r
367                         DialogResult result = DialogResult.OK;\r
368                         if (_boardViewerAppPath == null)\r
369                         {\r
370                                 result = MessageBox.Show(this, \r
371                                         "Please set path to Winboard or XBoard viewer app",\r
372                                         "Input requested", MessageBoxButtons.OKCancel, \r
373                                         MessageBoxIcon.Question);\r
374                                 if ( result == DialogResult.OK)\r
375                                 {\r
376                                         OpenFileDialog browse = new OpenFileDialog();\r
377                                         browse.Filter = "Executable files (*.exe)|*.exe";\r
378                                         browse.Multiselect = false;\r
379                                         result = browse.ShowDialog(this);\r
380                                         if (result == DialogResult.OK)\r
381                                         {\r
382                                                 _boardViewerAppPath = browse.FileName;\r
383                                         }\r
384                                 }\r
385                         }\r
386                         return result;\r
387                 }\r
388 \r
389                 /// <summary>\r
390                 /// Closes the viewer app\r
391                 /// </summary>\r
392                 private void KillProcess()\r
393                 {\r
394                         if (_process != null)\r
395                         {\r
396                                 if (!_process.HasExited)\r
397                                 {\r
398                                         if (!_process.CloseMainWindow())\r
399                                                 _process.Kill();\r
400                                 }\r
401                                 _process = null;\r
402                         }\r
403                 }\r
404 \r
405                 /// <summary>\r
406                 /// Writes board position to file and calls the viewer app with it.\r
407                 /// </summary>\r
408                 /// <param name="text">text of the board node, starting with the \r
409                 /// "board" tag and separator</param>\r
410                 private void ViewBoard(string text)\r
411                 {\r
412                         string boardCode = text.Substring(TAG_BOARD.Length\r
413                                 + VALUE_SEPARATOR.Length);\r
414                         StreamWriter f = new StreamWriter(new FileStream(\r
415                                 Path.Combine(_applicationDir, POSITION_FILE_NAME),\r
416                                 FileMode.Create, FileAccess.Write));\r
417                         f.WriteLine(boardCode);\r
418                         f.Close();\r
419 \r
420                         if (SetViewerAppPath() == DialogResult.OK)\r
421                         {\r
422 \r
423                                 try\r
424                                 {\r
425                                         KillProcess();\r
426                                         ProcessStartInfo pInfo = \r
427                                                 new ProcessStartInfo(_boardViewerAppPath);\r
428                                         pInfo.Arguments = \r
429                                                 "/mode editposition /ncp /top /coords /size small /lpf " +\r
430                                                 Path.Combine(_applicationDir, POSITION_FILE_NAME);\r
431 \r
432                                         _process = new Process();\r
433                                         _process.StartInfo = pInfo;\r
434                                         _process.Start();\r
435                                 }\r
436                                 catch(Exception)\r
437                                 {\r
438                                         MessageBox.Show(this, "Unable to run viewer app", "Error", \r
439                                                 MessageBoxButtons.OK, MessageBoxIcon.Error);\r
440                                         _boardViewerAppPath = null;\r
441                                         _process = null;\r
442                                 }\r
443                         }\r
444                 }\r
445 \r
446 \r
447                 #region Event Handlers\r
448 \r
449                 /// <summary>\r
450                 /// Handler for File | Open menu. Get the xml file to load.\r
451                 /// </summary>\r
452                 /// <param name="sender">ignored</param>\r
453                 /// <param name="e">ignored</param>\r
454                 private void OnOpenMenuClick(object sender, System.EventArgs e)\r
455                 {\r
456                         OpenFileDialog dialog = new  OpenFileDialog();\r
457                         dialog.Multiselect = false;\r
458                         dialog.Filter = "XML files (*.xml)|*.xml|All files (*.*)|*.*";\r
459                         if (dialog.ShowDialog(this) == DialogResult.OK)\r
460                         {\r
461                                 Display(dialog.FileName);\r
462                         }\r
463                 }\r
464 \r
465                 /// <summary>\r
466                 /// Handles mouse down. if right-click on "board" node, \r
467                 /// displays board context menu.\r
468                 /// </summary>\r
469                 /// <param name="sender">ignored</param>\r
470                 /// <param name="e">select the tree node at the click\r
471                 /// coords</param>\r
472                 private void OnTreeViewMouseDown(object sender, MouseEventArgs e)\r
473                 {\r
474                         // this doesn't automatically happen\r
475                         _treeView1.SelectedNode = _treeView1.GetNodeAt(e.X, e.Y);\r
476                         if (e.Button.Equals(MouseButtons.Right))\r
477                         {\r
478                                 string text = _treeView1.SelectedNode.Text;\r
479                                 if (text.StartsWith(TAG_BOARD))\r
480                                 {\r
481                                         _treeView1.ContextMenu = _contextMenu;\r
482                                 }\r
483                         }\r
484                 }\r
485 \r
486                 /// <summary>\r
487                 /// when view board menu item chosen, writes the selected board \r
488                 /// position to a file and calls the viewer app.\r
489                 /// </summary>\r
490                 /// <param name="sender">ignored</param>\r
491                 /// <param name="e">ignored</param>\r
492                 private void OnViewBoardMenuClicked(object sender, EventArgs e)\r
493                 {\r
494                         string text = _treeView1.SelectedNode.Text;\r
495                         if (text.StartsWith(TAG_BOARD))\r
496                         {\r
497                                 ViewBoard(text);\r
498                         }\r
499                         _treeView1.ContextMenu = null;\r
500                 }\r
501 \r
502                 /// <summary>\r
503                 /// If double-click a "board" node, calls the viewer \r
504                 /// for the position\r
505                 /// </summary>\r
506                 /// <param name="sender"></param>\r
507                 /// <param name="e"></param>\r
508                 private void OnTreeViewDoubleClick(object sender, System.EventArgs e)\r
509                 {\r
510                         string text = _treeView1.SelectedNode.Text;\r
511                         if (text.StartsWith(TAG_BOARD))\r
512                         {\r
513                                 ViewBoard(text);\r
514                         }\r
515                 }\r
516 \r
517                 /// <summary>\r
518                 /// set tooltip if over a node with a displayable tag \r
519                 /// </summary>\r
520                 /// <param name="sender">ignored</param>\r
521                 /// <param name="e">holds mouse position</param>\r
522                 private void OnTreeViewMouseMove(object sender, MouseEventArgs e)\r
523                 {\r
524                         TreeNode node = _treeView1.GetNodeAt(e.X, e.Y);\r
525                         string tag = string.Empty;\r
526                         if ((node != null) && (node.Tag != null))\r
527                         {\r
528                                 tag = (string)node.Tag;\r
529                         }\r
530                         if (!tag.Equals(_tipText))\r
531                         {\r
532                                 _tipText = tag;\r
533                                 _tip.SetToolTip(_treeView1, _tipText);\r
534                         }\r
535                 }\r
536 \r
537 \r
538                 #endregion\r
539 \r
540 \r
541         }\r
542 }\r