Details | Last modification | View Log | RSS feed
| Rev | Author | Line No. | Line |
|---|---|---|---|
| 1452 | chris | 1 | #region License |
| 2 | // |
||
| 3 | // The Open Toolkit Library License |
||
| 4 | // |
||
| 5 | // Copyright (c) 2006 - 2009 the Open Toolkit library. |
||
| 6 | // |
||
| 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy |
||
| 8 | // of this software and associated documentation files (the "Software"), to deal |
||
| 9 | // in the Software without restriction, including without limitation the rights to |
||
| 10 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of |
||
| 11 | // the Software, and to permit persons to whom the Software is furnished to do |
||
| 12 | // so, subject to the following conditions: |
||
| 13 | // |
||
| 14 | // The above copyright notice and this permission notice shall be included in all |
||
| 15 | // copies or substantial portions of the Software. |
||
| 16 | // |
||
| 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||
| 18 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
||
| 19 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||
| 20 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
||
| 21 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
||
| 22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
||
| 23 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
||
| 24 | // OTHER DEALINGS IN THE SOFTWARE. |
||
| 25 | // |
||
| 26 | #endregion |
||
| 27 | |||
| 28 | using System; |
||
| 29 | using System.Diagnostics; |
||
| 30 | using System.Drawing; |
||
| 31 | using System.Drawing.Text; |
||
| 32 | using System.Reflection; |
||
| 33 | using System.Windows.Forms; |
||
| 34 | using OpenTK.Examples.Properties; |
||
| 35 | using System.Threading; |
||
| 36 | using System.IO; |
||
| 37 | |||
| 38 | namespace Examples |
||
| 39 | { |
||
| 40 | public partial class ExampleBrowser : Form |
||
| 41 | { |
||
| 42 | #region Fields |
||
| 43 | |||
| 44 | //PrivateFontCollection font_collection = new PrivateFontCollection(); |
||
| 45 | |||
| 46 | bool show_warning = true; |
||
| 47 | |||
| 48 | static readonly string SourcePath = FindSourcePath(); |
||
| 49 | |||
| 50 | #endregion |
||
| 51 | |||
| 52 | #region Constructors |
||
| 53 | |||
| 54 | public ExampleBrowser() |
||
| 55 | { |
||
| 56 | Font = SystemFonts.DialogFont; |
||
| 57 | |||
| 58 | InitializeComponent(); |
||
| 59 | Icon = Resources.App; |
||
| 60 | |||
| 61 | // Windows 6 (Vista) and higher come with Consolas, a high-quality monospace font. Use that or fallback to |
||
| 62 | // the generic monospace font on other systems. |
||
| 63 | if (System.Environment.OSVersion.Platform == PlatformID.Win32NT && |
||
| 64 | System.Environment.OSVersion.Version.Major >= 6) |
||
| 65 | { |
||
| 66 | textBoxOutput.Font = richTextBoxSource.Font = new Font("Consolas", 10.0f, FontStyle.Regular); |
||
| 67 | } |
||
| 68 | else |
||
| 69 | { |
||
| 70 | textBoxOutput.Font = richTextBoxSource.Font = |
||
| 71 | new Font(FontFamily.GenericMonospace, 10.0f, FontStyle.Regular); |
||
| 72 | } |
||
| 73 | } |
||
| 74 | |||
| 75 | #endregion |
||
| 76 | |||
| 77 | #region Protected Members |
||
| 78 | |||
| 79 | protected override void OnLoad(EventArgs e) |
||
| 80 | { |
||
| 81 | base.OnLoad(e); |
||
| 82 | |||
| 83 | // Add those by hand, because using the designer results in an empty |
||
| 84 | // image list when cross-compiling on Mono. |
||
| 85 | imageListSampleCategories.Images.Add("OpenAL", Resources.OpenAL); |
||
| 86 | imageListSampleCategories.Images.Add("OpenGL", Resources.OpenGL); |
||
| 87 | imageListSampleCategories.Images.Add("OpenGLES", Resources.OpenGLES); |
||
| 88 | imageListSampleCategories.Images.Add("OpenCL", Resources.OpenCL); |
||
| 89 | imageListSampleCategories.Images.Add("OpenTK", Resources.OpenTK); |
||
| 90 | imageListSampleCategories.Images.Add("1.x", Resources.v1x); |
||
| 91 | imageListSampleCategories.Images.Add("2.x", Resources.v2x); |
||
| 92 | imageListSampleCategories.Images.Add("3.x", Resources.v3x); |
||
| 93 | imageListSampleCategories.Images.Add("4.x", Resources.v4x); |
||
| 94 | |||
| 95 | Debug.Listeners.Add(new TextBoxTraceListener(textBoxOutput)); |
||
| 96 | treeViewSamples.TreeViewNodeSorter = new SamplesTreeViewSorter(); |
||
| 97 | |||
| 98 | LoadSamplesFromAssembly(Assembly.GetExecutingAssembly()); |
||
| 99 | } |
||
| 100 | |||
| 101 | protected override void OnShown(EventArgs e) |
||
| 102 | { |
||
| 103 | if (show_warning) |
||
| 104 | { |
||
| 105 | //MessageBox.Show("The new Sample Browser is not complete. Please report any issues at http://www.opentk.com/project/issues.", |
||
| 106 | // "Work in Progress", MessageBoxButtons.OK, MessageBoxIcon.Information); |
||
| 107 | show_warning = false; |
||
| 108 | } |
||
| 109 | } |
||
| 110 | |||
| 111 | #endregion |
||
| 112 | |||
| 113 | #region Private Members |
||
| 114 | |||
| 115 | #region Events |
||
| 116 | |||
| 117 | #region TreeView |
||
| 118 | |||
| 119 | private void treeViewSamples_AfterSelect(object sender, TreeViewEventArgs e) |
||
| 120 | { |
||
| 121 | const string no_docs = "Documentation has not been entered."; |
||
| 122 | const string no_source = "Source code has not been entered."; |
||
| 123 | |||
| 124 | if (e.Node.Tag != null && !String.IsNullOrEmpty(((ExampleInfo)e.Node.Tag).Attribute.Documentation)) |
||
| 125 | { |
||
| 126 | string docs = null; |
||
| 127 | string source = null; |
||
| 128 | |||
| 129 | ExampleInfo einfo = (ExampleInfo)e.Node.Tag; |
||
| 130 | string sample = einfo.Attribute.Documentation; |
||
| 131 | string category = einfo.Attribute.Category.ToString(); |
||
| 132 | string subcategory = einfo.Attribute.Subcategory; |
||
| 133 | |||
| 134 | string path = Path.Combine(Path.Combine(Path.Combine(SourcePath, category), subcategory), sample); |
||
| 135 | string sample_rtf = Path.ChangeExtension(path, "rtf"); |
||
| 136 | string sample_cs = Path.ChangeExtension(path, "cs"); |
||
| 137 | |||
| 138 | if (File.Exists(sample_rtf)) |
||
| 139 | { |
||
| 140 | docs = File.ReadAllText(sample_rtf); |
||
| 141 | } |
||
| 142 | |||
| 143 | if (File.Exists(sample_cs)) |
||
| 144 | { |
||
| 145 | source = File.ReadAllText(sample_cs); |
||
| 146 | } |
||
| 147 | |||
| 148 | if (String.IsNullOrEmpty(docs)) |
||
| 149 | richTextBoxDescription.Text = String.Format("File {0} not found.", sample_rtf); |
||
| 150 | else |
||
| 151 | richTextBoxDescription.Rtf = docs; |
||
| 152 | |||
| 153 | if (String.IsNullOrEmpty(source)) |
||
| 154 | richTextBoxSource.Text = String.Format("File {0} not found.", sample_cs); |
||
| 155 | else |
||
| 156 | richTextBoxSource.Text = source; |
||
| 157 | } |
||
| 158 | else |
||
| 159 | { |
||
| 160 | richTextBoxDescription.Text = no_docs; |
||
| 161 | richTextBoxSource.Text = no_source; |
||
| 162 | } |
||
| 163 | } |
||
| 164 | |||
| 165 | private void treeViewSamples_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e) |
||
| 166 | { |
||
| 167 | if (e.Node.Tag != null) |
||
| 168 | { |
||
| 169 | ActivateNode(e.Node); |
||
| 170 | } |
||
| 171 | } |
||
| 172 | |||
| 173 | private void treeViewSamples_KeyDown(object sender, KeyEventArgs e) |
||
| 174 | { |
||
| 175 | // The enter key activates a node (either expands/collapses or executes its sample). |
||
| 176 | switch (e.KeyCode) |
||
| 177 | { |
||
| 178 | case Keys.Enter: |
||
| 179 | ActivateNode(treeViewSamples.SelectedNode); |
||
| 180 | e.Handled = true; |
||
| 181 | e.SuppressKeyPress = true; |
||
| 182 | break; |
||
| 183 | } |
||
| 184 | } |
||
| 185 | |||
| 186 | private void treeViewSamples_MouseDown(object sender, MouseEventArgs e) |
||
| 187 | { |
||
| 188 | // Make sure that right-clicking a new node will select that node before displaying |
||
| 189 | // the context menu. Without this, right-clicking a node does not select it, which |
||
| 190 | // is completely disorienting. |
||
| 191 | // As a bonus, make any mouse button select the underlying node, |
||
| 192 | TreeNode node = treeViewSamples.HitTest(e.Location).Node; |
||
| 193 | if (node != null) |
||
| 194 | treeViewSamples.SelectedNode = node; |
||
| 195 | |||
| 196 | // Middle click selects and activates a node (either expands/collapses or executes its sample). |
||
| 197 | // Right button displays the context menu. |
||
| 198 | // All other mouse buttons simply select the underlying node. |
||
| 199 | switch (e.Button) |
||
| 200 | { |
||
| 201 | case MouseButtons.Middle: |
||
| 202 | ActivateNode(node); |
||
| 203 | break; |
||
| 204 | |||
| 205 | case MouseButtons.Right: |
||
| 206 | treeViewSamples.ContextMenuStrip.Show(sender as Control, e.Location); |
||
| 207 | break; |
||
| 208 | } |
||
| 209 | } |
||
| 210 | |||
| 211 | private void treeViewSamples_AfterExpand(object sender, TreeViewEventArgs e) |
||
| 212 | { |
||
| 213 | foreach (TreeNode child in e.Node.Nodes) |
||
| 214 | child.EnsureVisible(); |
||
| 215 | } |
||
| 216 | |||
| 217 | private void contextMenuStripSamples_ItemClicked(object sender, ToolStripItemClickedEventArgs e) |
||
| 218 | { |
||
| 219 | switch (e.ClickedItem.Text) |
||
| 220 | { |
||
| 221 | case "&Run Sample": RunSample(this, (ExampleInfo)treeViewSamples.SelectedNode.Tag); break; |
||
| 222 | case "View Description": tabControlSample.SelectedTab = tabDescription; break; |
||
| 223 | case "View Source Code": tabControlSample.SelectedTab = tabSource; break; |
||
| 224 | } |
||
| 225 | } |
||
| 226 | |||
| 227 | #endregion |
||
| 228 | |||
| 229 | #region Description |
||
| 230 | |||
| 231 | private void richTextBoxDescription_MouseDown(object sender, MouseEventArgs e) |
||
| 232 | { |
||
| 233 | if (e.Button == MouseButtons.Right) |
||
| 234 | { |
||
| 235 | richTextBoxDescription.ContextMenuStrip.Show(sender as Control, e.X, e.Y); |
||
| 236 | } |
||
| 237 | } |
||
| 238 | |||
| 239 | private void contextMenuStripDescription_ItemClicked(object sender, ToolStripItemClickedEventArgs e) |
||
| 240 | { |
||
| 241 | if (e.ClickedItem.Text == "&Copy") |
||
| 242 | { |
||
| 243 | Clipboard.SetText(richTextBoxDescription.SelectedRtf, TextDataFormat.Rtf); |
||
| 244 | } |
||
| 245 | } |
||
| 246 | |||
| 247 | #endregion |
||
| 248 | |||
| 249 | #region Source Code |
||
| 250 | |||
| 251 | private void richTextBoxSource_MouseDown(object sender, MouseEventArgs e) |
||
| 252 | { |
||
| 253 | if (e.Button == MouseButtons.Right) |
||
| 254 | { |
||
| 255 | richTextBoxSource.ContextMenuStrip.Show(sender as Control, e.X, e.Y); |
||
| 256 | } |
||
| 257 | } |
||
| 258 | |||
| 259 | private void contextMenuStripSource_ItemClicked(object sender, ToolStripItemClickedEventArgs e) |
||
| 260 | { |
||
| 261 | if (e.ClickedItem.Text == "&Copy") |
||
| 262 | { |
||
| 263 | Clipboard.SetText(richTextBoxSource.SelectedText, TextDataFormat.Text); |
||
| 264 | } |
||
| 265 | } |
||
| 266 | |||
| 267 | #endregion |
||
| 268 | |||
| 269 | #endregion |
||
| 270 | |||
| 271 | #region Actions |
||
| 272 | |||
| 273 | void LoadSamplesFromAssembly(Assembly assembly) |
||
| 274 | { |
||
| 275 | if (assembly == null) |
||
| 276 | throw new ArgumentNullException("assembly"); |
||
| 277 | |||
| 278 | Type[] types = assembly.GetTypes(); |
||
| 279 | foreach (Type type in types) |
||
| 280 | { |
||
| 281 | object[] attributes = type.GetCustomAttributes(false); |
||
| 282 | ExampleAttribute example = null; |
||
| 283 | foreach (object attr in attributes) |
||
| 284 | { |
||
| 285 | if (attr is ExampleAttribute) |
||
| 286 | { |
||
| 287 | example = (ExampleAttribute)attr; |
||
| 288 | |||
| 289 | if (example.Visible) |
||
| 290 | { |
||
| 291 | // Add this example to the sample TreeView. |
||
| 292 | // First check whether the ExampleCategory exists in the tree (and add it if it doesn't). |
||
| 293 | // Then add the example as a child node on this category. |
||
| 294 | |||
| 295 | if (!treeViewSamples.Nodes.ContainsKey(example.Category.ToString())) |
||
| 296 | { |
||
| 297 | int category_index = GetImageIndexForSample(imageListSampleCategories, example.Category.ToString(), String.Empty); |
||
| 298 | treeViewSamples.Nodes.Add(example.Category.ToString(), String.Format("{0} samples", example.Category), |
||
| 299 | category_index, category_index); |
||
| 300 | } |
||
| 301 | |||
| 302 | int image_index = GetImageIndexForSample(imageListSampleCategories, example.Category.ToString(), example.Subcategory); |
||
| 303 | TreeNode node = new TreeNode(example.Title, image_index, image_index); |
||
| 304 | node.Name = example.Title; |
||
| 305 | node.Tag = new ExampleInfo(type, example); |
||
| 306 | treeViewSamples.Nodes[example.Category.ToString()].Nodes.Add(node); |
||
| 307 | } |
||
| 308 | } |
||
| 309 | } |
||
| 310 | } |
||
| 311 | |||
| 312 | treeViewSamples.Sort(); |
||
| 313 | } |
||
| 314 | |||
| 315 | void ActivateNode(TreeNode node) |
||
| 316 | { |
||
| 317 | if (node == null) |
||
| 318 | return; |
||
| 319 | |||
| 320 | if (node.Tag == null) |
||
| 321 | { |
||
| 322 | if (node.IsExpanded) |
||
| 323 | node.Collapse(); |
||
| 324 | else |
||
| 325 | node.Expand(); |
||
| 326 | } |
||
| 327 | else |
||
| 328 | { |
||
| 329 | tabControlSample.SelectedTab = tabPageOutput; |
||
| 330 | textBoxOutput.Clear(); |
||
| 331 | RunSample(node.TreeView.TopLevelControl, (ExampleInfo)node.Tag); |
||
| 332 | } |
||
| 333 | } |
||
| 334 | |||
| 335 | static int GetImageIndexForSample(ImageList list, string category, string subcategory) |
||
| 336 | { |
||
| 337 | if (list == null) |
||
| 338 | throw new ArgumentNullException("list"); |
||
| 339 | |||
| 340 | foreach (string extension in new string[] { "", ".png", ".jpg" }) |
||
| 341 | { |
||
| 342 | string name = subcategory.ToString() + extension; |
||
| 343 | if (list.Images.ContainsKey(name)) |
||
| 344 | return list.Images.IndexOfKey(name); |
||
| 345 | |||
| 346 | name = category.ToString() + extension; |
||
| 347 | if (list.Images.ContainsKey(name)) |
||
| 348 | return list.Images.IndexOfKey(name); |
||
| 349 | } |
||
| 350 | |||
| 351 | return -1; |
||
| 352 | } |
||
| 353 | |||
| 354 | static void RunSample(Control parent, ExampleInfo e) |
||
| 355 | { |
||
| 356 | if (e == null) |
||
| 357 | return; |
||
| 358 | |||
| 359 | MethodInfo main = |
||
| 360 | e.Example.GetMethod("Main", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) ?? |
||
| 361 | e.Example.GetMethod("Main", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(object), typeof(object) }, null); |
||
| 362 | if (main != null) |
||
| 363 | { |
||
| 364 | try |
||
| 365 | { |
||
| 366 | if (parent != null) |
||
| 367 | { |
||
| 368 | parent.Visible = false; |
||
| 369 | Application.DoEvents(); |
||
| 370 | } |
||
| 371 | Trace.WriteLine(String.Format("Launching sample: \"{0}\"", e.Attribute.Title)); |
||
| 372 | Trace.WriteLine(String.Empty); |
||
| 373 | |||
| 374 | Thread thread = new Thread((ThreadStart)delegate |
||
| 375 | { |
||
| 376 | try |
||
| 377 | { |
||
| 378 | main.Invoke(null, null); |
||
| 379 | } |
||
| 380 | catch (TargetInvocationException expt) |
||
| 381 | { |
||
| 382 | string ex_info; |
||
| 383 | if (expt.InnerException != null) |
||
| 384 | ex_info = expt.InnerException.ToString(); |
||
| 385 | else |
||
| 386 | ex_info = expt.ToString(); |
||
| 387 | MessageBox.Show(ex_info, "An OpenTK example encountered an error.", MessageBoxButtons.OK, MessageBoxIcon.Warning); |
||
| 388 | |||
| 389 | Debug.Print(expt.ToString()); |
||
| 390 | } |
||
| 391 | catch (NullReferenceException expt) |
||
| 392 | { |
||
| 393 | MessageBox.Show(expt.ToString(), "The Example launcher failed to load the example.", MessageBoxButtons.OK, MessageBoxIcon.Warning); |
||
| 394 | } |
||
| 395 | }); |
||
| 396 | thread.IsBackground = true; |
||
| 397 | thread.Start(); |
||
| 398 | thread.Join(); |
||
| 399 | } |
||
| 400 | finally |
||
| 401 | { |
||
| 402 | if (parent != null) |
||
| 403 | { |
||
| 404 | parent.Visible = true; |
||
| 405 | Application.DoEvents(); |
||
| 406 | } |
||
| 407 | } |
||
| 408 | } |
||
| 409 | else |
||
| 410 | { |
||
| 411 | MessageBox.Show("The selected example does not define a Main method", "Entry point not found", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); |
||
| 412 | } |
||
| 413 | } |
||
| 414 | |||
| 415 | // Tries to detect the path that contains the source for the examples. |
||
| 416 | static string FindSourcePath() |
||
| 417 | { |
||
| 418 | string current_dir = Directory.GetCurrentDirectory(); |
||
| 419 | |||
| 420 | // Typically, our working directory is either "[opentk]/Binaries/OpenTK/[config]" or "[opentk]". |
||
| 421 | // The desired source path is "[opentk]/Source/Examples/[ExampleCategory]" |
||
| 422 | |||
| 423 | string guess = current_dir; |
||
| 424 | if (CheckPath(ref guess)) |
||
| 425 | return guess; // We were in [opentk] after all |
||
| 426 | |||
| 427 | guess = current_dir; |
||
| 428 | for (int i = 0; i < 3; i++) |
||
| 429 | { |
||
| 430 | DirectoryInfo dir = Directory.GetParent(guess); |
||
| 431 | if (!dir.Exists) |
||
| 432 | break; |
||
| 433 | guess = dir.FullName; |
||
| 434 | } |
||
| 435 | |||
| 436 | if (CheckPath(ref guess)) |
||
| 437 | return guess; // We were in [opentk]/Binaries/OpenTK/[config] after all |
||
| 438 | |||
| 439 | throw new DirectoryNotFoundException(); |
||
| 440 | } |
||
| 441 | |||
| 442 | static bool CheckPath(ref string path) |
||
| 443 | { |
||
| 444 | string guess = path; |
||
| 445 | if (Directory.Exists(guess)) |
||
| 446 | { |
||
| 447 | guess = Path.Combine(guess, "Source"); |
||
| 448 | if (Directory.Exists(guess)) |
||
| 449 | { |
||
| 450 | guess = Path.Combine(guess, "Examples"); |
||
| 451 | if (Directory.Exists(guess)) |
||
| 452 | { |
||
| 453 | // We are have found [opentk]/Source/Examples |
||
| 454 | path = guess; |
||
| 455 | return true; |
||
| 456 | } |
||
| 457 | } |
||
| 458 | } |
||
| 459 | return false; |
||
| 460 | } |
||
| 461 | |||
| 462 | #endregion |
||
| 463 | |||
| 464 | #endregion |
||
| 465 | } |
||
| 466 | } |