Opentk/Source/Examples/ExampleBrowser.cs

466 lines
17 KiB
C#

#region License
//
// The Open Toolkit Library License
//
// Copyright (c) 2006 - 2009 the Open Toolkit library.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#endregion
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Text;
using System.Reflection;
using System.Windows.Forms;
using OpenTK.Examples.Properties;
using System.Threading;
using System.IO;
namespace Examples
{
public partial class ExampleBrowser : Form
{
#region Fields
//PrivateFontCollection font_collection = new PrivateFontCollection();
bool show_warning = true;
static readonly string SourcePath = FindSourcePath();
#endregion
#region Constructors
public ExampleBrowser()
{
Font = SystemFonts.DialogFont;
InitializeComponent();
Icon = Resources.App;
// Windows 6 (Vista) and higher come with Consolas, a high-quality monospace font. Use that or fallback to
// the generic monospace font on other systems.
if (System.Environment.OSVersion.Platform == PlatformID.Win32NT &&
System.Environment.OSVersion.Version.Major >= 6)
{
textBoxOutput.Font = richTextBoxSource.Font = new Font("Consolas", 10.0f, FontStyle.Regular);
}
else
{
textBoxOutput.Font = richTextBoxSource.Font =
new Font(FontFamily.GenericMonospace, 10.0f, FontStyle.Regular);
}
}
#endregion
#region Protected Members
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
// Add those by hand, because using the designer results in an empty
// image list when cross-compiling on Mono.
imageListSampleCategories.Images.Add("OpenAL", Resources.OpenAL);
imageListSampleCategories.Images.Add("OpenGL", Resources.OpenGL);
imageListSampleCategories.Images.Add("OpenGLES", Resources.OpenGLES);
imageListSampleCategories.Images.Add("OpenCL", Resources.OpenCL);
imageListSampleCategories.Images.Add("OpenTK", Resources.OpenTK);
imageListSampleCategories.Images.Add("1.x", Resources.v1x);
imageListSampleCategories.Images.Add("2.x", Resources.v2x);
imageListSampleCategories.Images.Add("3.x", Resources.v3x);
imageListSampleCategories.Images.Add("4.x", Resources.v4x);
Debug.Listeners.Add(new TextBoxTraceListener(textBoxOutput));
treeViewSamples.TreeViewNodeSorter = new SamplesTreeViewSorter();
LoadSamplesFromAssembly(Assembly.GetExecutingAssembly());
}
protected override void OnShown(EventArgs e)
{
if (show_warning)
{
//MessageBox.Show("The new Sample Browser is not complete. Please report any issues at http://www.opentk.com/project/issues.",
// "Work in Progress", MessageBoxButtons.OK, MessageBoxIcon.Information);
show_warning = false;
}
}
#endregion
#region Private Members
#region Events
#region TreeView
private void treeViewSamples_AfterSelect(object sender, TreeViewEventArgs e)
{
const string no_docs = "Documentation has not been entered.";
const string no_source = "Source code has not been entered.";
if (e.Node.Tag != null && !String.IsNullOrEmpty(((ExampleInfo)e.Node.Tag).Attribute.Documentation))
{
string docs = null;
string source = null;
ExampleInfo einfo = (ExampleInfo)e.Node.Tag;
string sample = einfo.Attribute.Documentation;
string category = einfo.Attribute.Category.ToString();
string subcategory = einfo.Attribute.Subcategory;
string path = Path.Combine(Path.Combine(Path.Combine(SourcePath, category), subcategory), sample);
string sample_rtf = Path.ChangeExtension(path, "rtf");
string sample_cs = Path.ChangeExtension(path, "cs");
if (File.Exists(sample_rtf))
{
docs = File.ReadAllText(sample_rtf);
}
if (File.Exists(sample_cs))
{
source = File.ReadAllText(sample_cs);
}
if (String.IsNullOrEmpty(docs))
richTextBoxDescription.Text = String.Format("File {0} not found.", sample_rtf);
else
richTextBoxDescription.Rtf = docs;
if (String.IsNullOrEmpty(source))
richTextBoxSource.Text = String.Format("File {0} not found.", sample_cs);
else
richTextBoxSource.Text = source;
}
else
{
richTextBoxDescription.Text = no_docs;
richTextBoxSource.Text = no_source;
}
}
private void treeViewSamples_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
{
if (e.Node.Tag != null)
{
ActivateNode(e.Node);
}
}
private void treeViewSamples_KeyDown(object sender, KeyEventArgs e)
{
// The enter key activates a node (either expands/collapses or executes its sample).
switch (e.KeyCode)
{
case Keys.Enter:
ActivateNode(treeViewSamples.SelectedNode);
e.Handled = true;
e.SuppressKeyPress = true;
break;
}
}
private void treeViewSamples_MouseDown(object sender, MouseEventArgs e)
{
// Make sure that right-clicking a new node will select that node before displaying
// the context menu. Without this, right-clicking a node does not select it, which
// is completely disorienting.
// As a bonus, make any mouse button select the underlying node,
TreeNode node = treeViewSamples.HitTest(e.Location).Node;
if (node != null)
treeViewSamples.SelectedNode = node;
// Middle click selects and activates a node (either expands/collapses or executes its sample).
// Right button displays the context menu.
// All other mouse buttons simply select the underlying node.
switch (e.Button)
{
case MouseButtons.Middle:
ActivateNode(node);
break;
case MouseButtons.Right:
treeViewSamples.ContextMenuStrip.Show(sender as Control, e.Location);
break;
}
}
private void treeViewSamples_AfterExpand(object sender, TreeViewEventArgs e)
{
foreach (TreeNode child in e.Node.Nodes)
child.EnsureVisible();
}
private void contextMenuStripSamples_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
switch (e.ClickedItem.Text)
{
case "&Run Sample": RunSample(this, (ExampleInfo)treeViewSamples.SelectedNode.Tag); break;
case "View Description": tabControlSample.SelectedTab = tabDescription; break;
case "View Source Code": tabControlSample.SelectedTab = tabSource; break;
}
}
#endregion
#region Description
private void richTextBoxDescription_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
richTextBoxDescription.ContextMenuStrip.Show(sender as Control, e.X, e.Y);
}
}
private void contextMenuStripDescription_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
if (e.ClickedItem.Text == "&Copy")
{
Clipboard.SetText(richTextBoxDescription.SelectedRtf, TextDataFormat.Rtf);
}
}
#endregion
#region Source Code
private void richTextBoxSource_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
richTextBoxSource.ContextMenuStrip.Show(sender as Control, e.X, e.Y);
}
}
private void contextMenuStripSource_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
if (e.ClickedItem.Text == "&Copy")
{
Clipboard.SetText(richTextBoxSource.SelectedText, TextDataFormat.Text);
}
}
#endregion
#endregion
#region Actions
void LoadSamplesFromAssembly(Assembly assembly)
{
if (assembly == null)
throw new ArgumentNullException("assembly");
Type[] types = assembly.GetTypes();
foreach (Type type in types)
{
object[] attributes = type.GetCustomAttributes(false);
ExampleAttribute example = null;
foreach (object attr in attributes)
{
if (attr is ExampleAttribute)
{
example = (ExampleAttribute)attr;
if (example.Visible)
{
// Add this example to the sample TreeView.
// First check whether the ExampleCategory exists in the tree (and add it if it doesn't).
// Then add the example as a child node on this category.
if (!treeViewSamples.Nodes.ContainsKey(example.Category.ToString()))
{
int category_index = GetImageIndexForSample(imageListSampleCategories, example.Category.ToString(), String.Empty);
treeViewSamples.Nodes.Add(example.Category.ToString(), String.Format("{0} samples", example.Category),
category_index, category_index);
}
int image_index = GetImageIndexForSample(imageListSampleCategories, example.Category.ToString(), example.Subcategory);
TreeNode node = new TreeNode(example.Title, image_index, image_index);
node.Name = example.Title;
node.Tag = new ExampleInfo(type, example);
treeViewSamples.Nodes[example.Category.ToString()].Nodes.Add(node);
}
}
}
}
treeViewSamples.Sort();
}
void ActivateNode(TreeNode node)
{
if (node == null)
return;
if (node.Tag == null)
{
if (node.IsExpanded)
node.Collapse();
else
node.Expand();
}
else
{
tabControlSample.SelectedTab = tabPageOutput;
textBoxOutput.Clear();
RunSample(node.TreeView.TopLevelControl, (ExampleInfo)node.Tag);
}
}
static int GetImageIndexForSample(ImageList list, string category, string subcategory)
{
if (list == null)
throw new ArgumentNullException("list");
foreach (string extension in new string[] { "", ".png", ".jpg" })
{
string name = subcategory.ToString() + extension;
if (list.Images.ContainsKey(name))
return list.Images.IndexOfKey(name);
name = category.ToString() + extension;
if (list.Images.ContainsKey(name))
return list.Images.IndexOfKey(name);
}
return -1;
}
static void RunSample(Control parent, ExampleInfo e)
{
if (e == null)
return;
MethodInfo main =
e.Example.GetMethod("Main", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ??
e.Example.GetMethod("Main", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(object), typeof(object) }, null);
if (main != null)
{
try
{
if (parent != null)
{
parent.Visible = false;
Application.DoEvents();
}
Trace.WriteLine(String.Format("Launching sample: \"{0}\"", e.Attribute.Title));
Trace.WriteLine(String.Empty);
Thread thread = new Thread((ThreadStart)delegate
{
try
{
main.Invoke(null, null);
}
catch (TargetInvocationException expt)
{
string ex_info;
if (expt.InnerException != null)
ex_info = expt.InnerException.ToString();
else
ex_info = expt.ToString();
MessageBox.Show(ex_info, "An OpenTK example encountered an error.", MessageBoxButtons.OK, MessageBoxIcon.Warning);
Debug.Print(expt.ToString());
}
catch (NullReferenceException expt)
{
MessageBox.Show(expt.ToString(), "The Example launcher failed to load the example.", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
});
thread.IsBackground = true;
thread.Start();
thread.Join();
}
finally
{
if (parent != null)
{
parent.Visible = true;
Application.DoEvents();
}
}
}
else
{
MessageBox.Show("The selected example does not define a Main method", "Entry point not found", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
// Tries to detect the path that contains the source for the examples.
static string FindSourcePath()
{
string current_dir = Directory.GetCurrentDirectory();
// Typically, our working directory is either "[opentk]/Binaries/OpenTK/[config]" or "[opentk]".
// The desired source path is "[opentk]/Source/Examples/[ExampleCategory]"
string guess = current_dir;
if (CheckPath(ref guess))
return guess; // We were in [opentk] after all
guess = current_dir;
for (int i = 0; i < 3; i++)
{
DirectoryInfo dir = Directory.GetParent(guess);
if (!dir.Exists)
break;
guess = dir.FullName;
}
if (CheckPath(ref guess))
return guess; // We were in [opentk]/Binaries/OpenTK/[config] after all
throw new DirectoryNotFoundException();
}
static bool CheckPath(ref string path)
{
string guess = path;
if (Directory.Exists(guess))
{
guess = Path.Combine(guess, "Source");
if (Directory.Exists(guess))
{
guess = Path.Combine(guess, "Examples");
if (Directory.Exists(guess))
{
// We are have found [opentk]/Source/Examples
path = guess;
return true;
}
}
}
return false;
}
#endregion
#endregion
}
}