ConsoleWindow.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. using BansheeEngine;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Text.RegularExpressions;
  5. namespace BansheeEditor
  6. {
  7. /// <summary>
  8. /// Displays a list of log messages.
  9. /// </summary>
  10. public class ConsoleWindow : EditorWindow
  11. {
  12. /// <summary>
  13. /// Filter type that determines what kind of messages are shown in the console.
  14. /// </summary>
  15. [Flags]
  16. private enum EntryFilter
  17. {
  18. Info = 0x01, Warning = 0x02, Error = 0x04, All = Info | Warning | Error
  19. }
  20. private const int ENTRY_HEIGHT = 33;
  21. private static int sSelectedElementIdx = -1;
  22. private GUIListView<ConsoleGUIEntry, ConsoleEntryData> listView;
  23. private List<ConsoleEntryData> entries = new List<ConsoleEntryData>();
  24. private EntryFilter filter = EntryFilter.All;
  25. /// <summary>
  26. /// Opens the console window.
  27. /// </summary>
  28. [MenuItem("Windows/Console", ButtonModifier.CtrlAlt, ButtonCode.C, 6000)]
  29. private static void OpenConsoleWindow()
  30. {
  31. OpenWindow<ConsoleWindow>();
  32. }
  33. /// <inheritdoc/>
  34. protected override LocString GetDisplayName()
  35. {
  36. return new LocEdString("Console");
  37. }
  38. private void OnInitialize()
  39. {
  40. Width = 300;
  41. GUILayoutY layout = GUI.AddLayoutY();
  42. listView = new GUIListView<ConsoleGUIEntry, ConsoleEntryData>(Width, Height, ENTRY_HEIGHT, layout);
  43. Debug.OnAdded += OnEntryAdded;
  44. // TODO - Add buttons to filter info/warning/error
  45. // TODO - Add button to clear console
  46. // TODO - Add button that splits the window vertically and displays details about an entry + callstack
  47. // TODO - On entry double-click open VS at that line
  48. // TODO - On callstack entry double-click open VS at that line
  49. }
  50. private void OnEditorUpdate()
  51. {
  52. listView.Update();
  53. }
  54. private void OnDestroy()
  55. {
  56. Debug.OnAdded -= OnEntryAdded;
  57. }
  58. /// <inheritdoc/>
  59. protected override void WindowResized(int width, int height)
  60. {
  61. listView.SetSize(width, height);
  62. base.WindowResized(width, height);
  63. }
  64. /// <summary>
  65. /// Triggered when a new entry is added in the debug log.
  66. /// </summary>
  67. /// <param name="type">Type of the message.</param>
  68. /// <param name="message">Message string.</param>
  69. private void OnEntryAdded(DebugMessageType type, string message)
  70. {
  71. ConsoleEntryData newEntry = new ConsoleEntryData();
  72. newEntry.type = type;
  73. int firstMatchIdx = -1;
  74. Regex regex = new Regex(@"\tat (.*) in (.*), line (\d*), column .*, namespace .*");
  75. var matches = regex.Matches(message);
  76. newEntry.callstack = new ConsoleEntryData.CallStackEntry[matches.Count];
  77. for(int i = 0; i < matches.Count; i++)
  78. {
  79. ConsoleEntryData.CallStackEntry callstackEntry = new ConsoleEntryData.CallStackEntry();
  80. callstackEntry.method = matches[i].Groups[1].Value;
  81. callstackEntry.file = matches[i].Groups[2].Value;
  82. int.TryParse(matches[i].Groups[3].Value, out callstackEntry.line);
  83. newEntry.callstack[i] = callstackEntry;
  84. if (firstMatchIdx == -1)
  85. firstMatchIdx = matches[i].Index;
  86. }
  87. if (firstMatchIdx != -1)
  88. newEntry.message = message.Substring(0, firstMatchIdx);
  89. else
  90. newEntry.message = message;
  91. entries.Add(newEntry);
  92. if (DoesFilterMatch(type))
  93. listView.AddEntry(newEntry);
  94. }
  95. /// <summary>
  96. /// Changes the filter that controls what type of messages are displayed in the console.
  97. /// </summary>
  98. /// <param name="filter">Flags that control which type of messages should be displayed.</param>
  99. private void SetFilter(EntryFilter filter)
  100. {
  101. if (this.filter == filter)
  102. return;
  103. listView.Clear();
  104. foreach (var entry in entries)
  105. {
  106. if (DoesFilterMatch(entry.type))
  107. listView.AddEntry(entry);
  108. }
  109. this.filter = filter;
  110. }
  111. /// <summary>
  112. /// Checks if the currently active entry filter matches the provided type (i.e. the entry with the type should be
  113. /// displayed).
  114. /// </summary>
  115. /// <param name="type">Type of the entry to check.</param>
  116. /// <returns>True if the entry with the specified type should be displayed in the console.</returns>
  117. private bool DoesFilterMatch(DebugMessageType type)
  118. {
  119. switch (type)
  120. {
  121. case DebugMessageType.Info:
  122. return filter.HasFlag(EntryFilter.Info);
  123. case DebugMessageType.Warning:
  124. return filter.HasFlag(EntryFilter.Warning);
  125. case DebugMessageType.Error:
  126. return filter.HasFlag(EntryFilter.Error);
  127. }
  128. return false;
  129. }
  130. /// <summary>
  131. /// Removes all entries from the console.
  132. /// </summary>
  133. private void Clear()
  134. {
  135. listView.Clear();
  136. entries.Clear();
  137. }
  138. /// <summary>
  139. /// Contains data for a single entry in the console.
  140. /// </summary>
  141. private class ConsoleEntryData : GUIListViewData
  142. {
  143. /// <summary>
  144. /// Contains data for a single entry in a call stack associated with a console entry.
  145. /// </summary>
  146. public class CallStackEntry
  147. {
  148. public string method;
  149. public string file;
  150. public int line;
  151. }
  152. public DebugMessageType type;
  153. public string message;
  154. public CallStackEntry[] callstack;
  155. }
  156. /// <summary>
  157. /// Contains GUI elements used for displaying a single entry in the console.
  158. /// </summary>
  159. private class ConsoleGUIEntry : GUIListViewEntry<ConsoleEntryData>
  160. {
  161. // TODO - Create two separate labels for text and first callstack entry
  162. // TODO - Add invisible button for overlay, toggle background selection when clicked
  163. // TODO - Don't use toggle group, instead manually track which element is selected and update
  164. // selection state in UpdateContents()
  165. // TODO - Remove ListView GUI states
  166. private const int CALLER_LABEL_HEIGHT = 11;
  167. private const int MESSAGE_HEIGHT = ENTRY_HEIGHT - CALLER_LABEL_HEIGHT;
  168. private static readonly Color BG_COLOR = Color.DarkGray;
  169. private static readonly Color SELECTION_COLOR = Color.DarkCyan;
  170. private GUIPanel overlay;
  171. private GUIPanel main;
  172. private GUIPanel underlay;
  173. private GUILabel messageLabel;
  174. private GUILabel functionLabel;
  175. private GUITexture background;
  176. private int entryIdx;
  177. /// <inheritdoc/>
  178. public override void BuildGUI()
  179. {
  180. main = Layout.AddPanel(0, 1, 1, GUIOption.FixedHeight(ENTRY_HEIGHT));
  181. overlay = main.AddPanel(-1, 0, 0, GUIOption.FixedHeight(ENTRY_HEIGHT));
  182. underlay = main.AddPanel(1, 0, 0, GUIOption.FixedHeight(ENTRY_HEIGHT));
  183. GUILayoutY mainLayout = main.AddLayoutY();
  184. GUILayoutY overlayLayout = overlay.AddLayoutY();
  185. GUILayoutY underlayLayout = underlay.AddLayoutY();
  186. messageLabel = new GUILabel(new LocEdString(""), EditorStyles.MultiLineLabel, GUIOption.FixedHeight(MESSAGE_HEIGHT));
  187. functionLabel = new GUILabel(new LocEdString(""), GUIOption.FixedHeight(CALLER_LABEL_HEIGHT));
  188. mainLayout.AddElement(messageLabel);
  189. mainLayout.AddElement(functionLabel);
  190. background = new GUITexture(Builtin.WhiteTexture, GUIOption.FixedHeight(ENTRY_HEIGHT));
  191. underlayLayout.AddElement(background);
  192. GUIButton button = new GUIButton(new LocEdString(""), EditorStyles.Blank, GUIOption.FixedHeight(ENTRY_HEIGHT));
  193. overlayLayout.AddElement(button);
  194. button.OnClick += OnClicked;
  195. button.OnDoubleClick += OnDoubleClicked;
  196. }
  197. /// <inheritdoc/>
  198. public override void UpdateContents(int index, ConsoleEntryData data)
  199. {
  200. if (index != sSelectedElementIdx)
  201. {
  202. if (index%2 != 0)
  203. {
  204. background.Visible = true;
  205. background.SetTint(BG_COLOR);
  206. }
  207. else
  208. {
  209. background.Visible = false;
  210. }
  211. }
  212. else
  213. {
  214. background.Visible = true;
  215. background.SetTint(SELECTION_COLOR);
  216. }
  217. messageLabel.SetContent(new LocEdString(data.message));
  218. string method = "";
  219. if (data.callstack != null && data.callstack.Length > 0)
  220. method = data.callstack[0].method + " at " + data.callstack[0].file + ":" + data.callstack[0].line;
  221. functionLabel.SetContent(new LocEdString(method));
  222. entryIdx = index;
  223. }
  224. /// <summary>
  225. /// Triggered when the entry is selected.
  226. /// </summary>
  227. private void OnClicked()
  228. {
  229. sSelectedElementIdx = entryIdx;
  230. // TODO - Refresh all entries (especially previously selected one and this one to update their graphic)
  231. }
  232. /// <summary>
  233. /// Triggered when the entry is double-clicked.
  234. /// </summary>
  235. private void OnDoubleClicked()
  236. {
  237. // TODO - Open code editor
  238. }
  239. }
  240. }
  241. }