text: //========================================================================
  // EngineDisplayForm.cs - the user interface for the C# Editor
  //
  // Part of the GameCode4 Application
  //
  // GameCode4 is the sample application that encapsulates much of the source code
  // discussed in "Game Coding Complete - 4th Edition" by Mike McShaffry and David
  // "Rez" Graham, published by Charles River Media. 
  // ISBN-10: 1133776574 | ISBN-13: 978-1133776574
  //
  // If this source code has found it's way to you, and you think it has helped you
  // in any way, do the authors a favor and buy a new copy of the book - there are 
  // detailed explanations in it that compliment this code well. Buy a copy at Amazon.com
  // by clicking here: 
  //    http://www.amazon.com/gp/product/1133776574/ref=olp_product_details?ie=UTF8&me=&seller=
  //
  // There's a companion web site at http://www.mcshaffry.com/GameCode/
  // 
  // The source code is managed and maintained through Google Code: 
  //    http://code.google.com/p/gamecode4/
  //
  // (c) Copyright 2012 Michael L. McShaffry and David Graham
  //
  // This program is free software; you can redistribute it and/or
  // modify it under the terms of the GNU Lesser GPL v3
  // as published by the Free Software Foundation.
  //
  // This program is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See 
  // http://www.gnu.org/licenses/lgpl-3.0.txt for more details.
  //
  // You should have received a copy of the GNU Lesser GPL v3
  // along with this program; if not, write to the Free Software
  // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  //
  //========================================================================
  
  using System;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Data;
  using System.Diagnostics;
  using System.Drawing;
  using System.IO;
  using System.Collections;
  using System.Windows.Forms;
  using System.Runtime.InteropServices;
  using System.Xml;
  
  namespace EditorApp
  {
      //========================================================================
      //
      // class EditorForm                      - Chapter 22, page 772
      //
      // The EditorForm handles the rendering for the editor, and as such,
      // is the main class in the EditorApp that interacts with the unmanaged dll.
      //
      //========================================================================
  
      public partial class EditorForm : Form
      {
          private const int INVALID_ACTOR_ID = 0;
  
          private string m_ProjectDirectory;
          private string m_AssetsDirectory;
          private string m_CurrentLevelFile;
  
          private List<XmlNode> m_ActorsXmlNodes = new List<XmlNode>();
          private int m_SelectedActorId = INVALID_ACTOR_ID;
  
          private MessageHandler m_messageFilter;
          private ActorComponentEditor m_ActorComponentEditor;
  
  
          public EditorForm()
          {
  
              var currentDirectory = Directory.GetCurrentDirectory();
              var parentDirectory = Directory.GetParent(currentDirectory);
              m_ProjectDirectory = parentDirectory.FullName;
              m_AssetsDirectory = m_ProjectDirectory + "\\Assets";
  
              InitializeComponent();
              try
              {
                  // This is how we get the instance handle for our C# app.
                  System.IntPtr hInstance = Marshal.GetHINSTANCE(this.GetType().Module);
                  
                  // This is how we get the window handle for the panel we'll be rendering into.
                  IntPtr hwnd = this.DisplayPanel.Handle;
  
                  // Call into our Dll main function, which will set up an instance of the EditorApp project.
                  NativeMethods.EditorMain(hInstance, IntPtr.Zero, hwnd, 1, this.DisplayPanel.Width, this.DisplayPanel.Height);
  
                  InitializeAssetTree();
  
                  m_messageFilter = new MessageHandler(this.DisplayPanel, this);
                  m_ActorComponentEditor = new ActorComponentEditor(Panel_ActorComponents, m_ProjectDirectory);
              }
              catch (Exception e)
              {
                  MessageBox.Show("Error: " + e.ToString());
              }
          }
  
  
          public EditorApp.MessageHandler GetMessageHandler()
          {
              return m_messageFilter;
          }
  
          private void Shutdown()
          {
              // FUTURE WORK - Detect if there are unsaved changes, and ask if the level should be saved first.
              NativeMethods.Shutdown();
              Application.Exit();
          }
  
   
          //----------------------------------------------------------------
          // Asset Tree Methods               - Chapter 22, page 775
          //----------------------------------------------------------------
  
          private void InitializeAssetTree()
          {
              TreeView_Assets.Nodes.Clear();
  
              var stack = new Stack<TreeNode>();
              var rootDirectory = new DirectoryInfo(m_AssetsDirectory);
              var node = new TreeNode(rootDirectory.Name) { Tag = rootDirectory };
              stack.Push(node);
  
              while (stack.Count > 0)
              {
                  var currentNode = stack.Pop();
                  var directoryInfo = (DirectoryInfo)currentNode.Tag;
                  foreach (var directory in directoryInfo.GetDirectories())
                  {
                      FileAttributes attributes = File.GetAttributes(directory.FullName);
                      if ((attributes & FileAttributes.Hidden) == 0 )
                      {
                          var childDirectoryNode = new TreeNode(directory.Name);
                          childDirectoryNode.Tag = directory;
                          currentNode.Nodes.Add(childDirectoryNode);
                          stack.Push(childDirectoryNode);
                      }
                  }
                  foreach (var file in directoryInfo.GetFiles())
                  {
                      FileAttributes attributes = File.GetAttributes(file.FullName);
                      if ((attributes & FileAttributes.Hidden) ==  0 )
                      {
                          var childNode = new TreeNode(file.Name);
                          childNode.Tag = file.FullName;
                          currentNode.Nodes.Add(childNode);
                      }
                  }
              }
  
              TreeView_Assets.Nodes.Add(node);
          }
  
  
          private void TreeView_Assets_MouseDoubleClick(object sender, MouseEventArgs e)
          {
              TreeNode node = TreeView_Assets.SelectedNode;
              if (node != null && node.Nodes.Count == 0)
              {
                  string file = node.Tag.ToString();
                  if (File.Exists(file))
                  {
                      Process.Start(file);
                  }
              }
          }
  
  
          //----------------------------------------------------------------
          // Actor List Methods               - Chapter 22, page 778
          //----------------------------------------------------------------
  
          private XmlElement GetActorXml(uint actorId)
          {
              int xmlSize = NativeMethods.GetActorXmlSize(actorId);
              if (xmlSize == 0)
                  return null;
  
              IntPtr tempArray = Marshal.AllocCoTaskMem((xmlSize + 1) * sizeof(char));
              NativeMethods.GetActorXml(tempArray, actorId);
              string actorXml = Marshal.PtrToStringAnsi(tempArray);
              Console.WriteLine(actorXml);
              Marshal.FreeCoTaskMem(tempArray);
  
              XmlDocument actorDoc = new XmlDocument();
              actorDoc.Load(new StringReader(actorXml));
              return actorDoc.DocumentElement;
          }
  
          private void InitializeActors()
          {
              TreeView_Actors.Nodes.Clear();
  
              int[] actorList = GetActorList();
  
              m_ActorsXmlNodes.Add(null);             // Game starts actors at Id=1, so we'll make a space for a null actor here.      
  
              // Add each actor as its own node in the treeview.
              for (int i = 0; i < actorList.GetLength(0); i++)
              {
                  uint actorId = Convert.ToUInt32(actorList[i]);
  
                  TreeNode node = new TreeNode();
  
                  XmlElement actorXml = GetActorXml(actorId);
                  if (actorXml != null)
                  {
                      node.Name = actorList[i].ToString();
                      m_ActorsXmlNodes.Add(actorXml);
                      node.Text = actorXml.GetAttribute("type");
                  }
                  else
                  {
                      node.Text = "<undefined actor - no xml>";
                  }
                  TreeView_Actors.Nodes.Add(node);
              }
          }
  
          private int[] GetActorList()
          {
              // We need to know how many actors there are,
              // in order to find out how much memory to allocate
              int numActors = NativeMethods.GetNumActors();
  
              IntPtr tempArray = Marshal.AllocCoTaskMem(numActors * sizeof(uint));
              NativeMethods.GetActorList(tempArray, numActors);
  
              // Copy the memory into an array of ints and dispose of our
              // our memory.
              int[] actorList = new int[numActors];
              Marshal.Copy(tempArray, actorList, 0, numActors);
              Marshal.FreeCoTaskMem(tempArray);
  
              return actorList;
          }
  
          public void PickActor()
          {
              IntPtr hWnd = this.DisplayPanel.Handle;
              int actorId = NativeMethods.PickActor(hWnd);
              if (actorId != INVALID_ACTOR_ID)
              {
                  TreeView_Actors.SelectedNode = TreeView_Actors.Nodes[actorId - 1];
              }
          }
  
          public void SetSelectedActor(int actorId)
          {
              if (actorId != INVALID_ACTOR_ID)
              {
                  if (m_SelectedActorId != INVALID_ACTOR_ID)
                  {
                      TreeView_Actors.Nodes[m_SelectedActorId-1].BackColor = Color.White;
                      TreeView_Actors.Nodes[m_SelectedActorId-1].ForeColor = Color.Black;
                  }
  
                  m_SelectedActorId = actorId;                 // Game starts Actor Ids at 1
  
                  TreeView_Actors.Nodes[m_SelectedActorId-1].BackColor = Color.Blue;
                  TreeView_Actors.Nodes[m_SelectedActorId-1].ForeColor = Color.White;
  
                  m_ActorComponentEditor.ShowActorComponents(m_SelectedActorId, m_ActorsXmlNodes[m_SelectedActorId]);
              }
          }
  
          private void TreeView_Actors_AfterSelect(object sender, TreeViewEventArgs e)
          {
              TreeNode node = TreeView_Actors.SelectedNode;
              if (node != null)
              {
                  SetSelectedActor(node.Index + 1);                     // [mrmike] I refactored this post press to fix a bug with editor actor selection
                  //
                  //m_SelectedActorId = node.Index + 1;                 // Game starts Actor Ids at 1
                  //m_ActorComponentEditor.ShowActorComponents(m_SelectedActorId, m_ActorsXmlNodes[m_SelectedActorId]);
              }
          }
  
          //----------------------------------------------------------------
          // Main Menu Methods               - Chapter 22, page 780
          //----------------------------------------------------------------
  
          private void openLevelToolStripMenuItem_Click(object sender, EventArgs e)
          {
              OpenFileDialog dialog = new OpenFileDialog();
  
              dialog.InitialDirectory = m_AssetsDirectory + "\\World";
              dialog.Filter = "XML files (*.xml)|*.xml";
              dialog.FilterIndex = 1;
              dialog.RestoreDirectory = true;
  
              if (dialog.ShowDialog() == DialogResult.OK)
              {
                  m_CurrentLevelFile = dialog.FileName;
                  NativeMethods.OpenLevel(m_CurrentLevelFile);
                  InitializeActors();
              }
          }
  
  
          private void saveLevelToolStripMenuItem_Click(object sender, EventArgs e)
          {
              try
              {
                  XmlDocument levelXml = new XmlDocument();
                  XmlElement world = levelXml.CreateElement("World");
                  levelXml.AppendChild(world);
                  XmlElement staticActors = levelXml.CreateElement("StaticActors");
                  world.AppendChild(staticActors);
  
                  int[] actorList = GetActorList();
  
                  for (int i = 0; i < actorList.GetLength(0); i++)
                  {
                      uint actorId = Convert.ToUInt32(actorList[i]);
                      XmlElement actorXml = GetActorXml(actorId);
                      if (actorXml != null)
                      {
                          staticActors.AppendChild(staticActors.OwnerDocument.ImportNode(actorXml, true));
                      }
                  }
  
                  XmlElement editorOptionsScriptAdditions = GetLevelScriptAdditionsXml();
                  foreach (XmlNode child in editorOptionsScriptAdditions.ChildNodes)
                  {
                      XmlNode node = levelXml.ImportNode(child, true); 
                      world.AppendChild(node);
                  }
  
                  // Save the document to a file and auto-indent the output.
                  XmlTextWriter writer = new XmlTextWriter(m_CurrentLevelFile, null);
                  writer.Formatting = Formatting.Indented;
                  levelXml.Save(writer);
  
              }
              catch (Exception ex)
              {
                  Debug.WriteLine(ex);
              }
          }
  
          private XmlElement GetLevelScriptAdditionsXml()
          {
              int xmlSize = NativeMethods.GetLevelScriptAdditionsXmlSize();
              if (xmlSize == 0)
                  return null;
  
              IntPtr tempArray = Marshal.AllocCoTaskMem((xmlSize + 1) * sizeof(char));
              NativeMethods.GetLevelScriptAdditionsXml(tempArray);
              string xml = Marshal.PtrToStringAnsi(tempArray);
              Console.WriteLine(xml);
              Marshal.FreeCoTaskMem(tempArray);
  
              XmlDocument scriptAdditionsDoc = new XmlDocument();
              scriptAdditionsDoc.Load(new StringReader(xml));
              return scriptAdditionsDoc.DocumentElement;
          }
  
  
  
  
          private void closeProjectToolStripMenuItem_Click(object sender, EventArgs e)
          {
              // FUTURE WORK - Switching from one project to another isn't supported yet!
          }
  
          private void buildProjectToolStripMenuItem_MouseUp(object sender, MouseEventArgs e)
          {
              ZipFileUtility.Create(m_AssetsDirectory, m_ProjectDirectory + "\\Game\\Assets.zip");
          }
  
          private void buildProjectToolStripMenuItem_Click(object sender, EventArgs e)
          {
              ZipFileUtility.Create(m_AssetsDirectory, m_ProjectDirectory + "\\Game\\Assets.zip");
          }
     }
  }