using BansheeEngine; using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; namespace BansheeEditor { /// /// Displays a list of log messages. /// public class ConsoleWindow : EditorWindow { /// /// Filter type that determines what kind of messages are shown in the console. /// [Flags] private enum EntryFilter { Info = 0x01, Warning = 0x02, Error = 0x04, All = Info | Warning | Error } private const int TITLE_HEIGHT = 21; private const int ENTRY_HEIGHT = 39; private static int sSelectedElementIdx = -1; private GUIListView listView; private List entries = new List(); private List filteredEntries = new List(); private EntryFilter filter = EntryFilter.All; private GUIScrollArea detailsArea; private int dbg_disable = 0; /// /// Opens the console window. /// [MenuItem("Windows/Console", ButtonModifier.CtrlAlt, ButtonCode.C, 6000)] private static void OpenConsoleWindow() { OpenWindow(); } /// protected override LocString GetDisplayName() { return new LocEdString("Console"); } private void OnInitialize() { Width = 300; GUILayoutY layout = GUI.AddLayoutY(); GUILayoutX titleLayout = layout.AddLayoutX(); GUIToggle infoBtn = new GUIToggle(new GUIContent(EditorBuiltin.GetLogIcon(LogIcon.Info, 16)), EditorStyles.Button); GUIToggle warningBtn = new GUIToggle(new GUIContent(EditorBuiltin.GetLogIcon(LogIcon.Warning, 16)), EditorStyles.Button); GUIToggle errorBtn = new GUIToggle(new GUIContent(EditorBuiltin.GetLogIcon(LogIcon.Error, 16)), EditorStyles.Button); GUIToggle detailsBtn = new GUIToggle(new LocEdString("Show details"), EditorStyles.Button); GUIButton clearBtn = new GUIButton(new LocEdString("Clear")); titleLayout.AddElement(infoBtn); titleLayout.AddElement(warningBtn); titleLayout.AddElement(errorBtn); titleLayout.AddFlexibleSpace(); titleLayout.AddElement(detailsBtn); titleLayout.AddElement(clearBtn); infoBtn.Value = filter.HasFlag(EntryFilter.Info); warningBtn.Value = filter.HasFlag(EntryFilter.Warning); errorBtn.Value = filter.HasFlag(EntryFilter.Error); infoBtn.OnToggled += x => { if (x) SetFilter(filter | EntryFilter.Info); else SetFilter(filter & ~EntryFilter.Info); }; warningBtn.OnToggled += x => { if (x) SetFilter(filter | EntryFilter.Warning); else SetFilter(filter & ~EntryFilter.Warning); }; errorBtn.OnToggled += x => { if (x) SetFilter(filter | EntryFilter.Error); else SetFilter(filter & ~EntryFilter.Error); }; detailsBtn.OnToggled += ToggleDetailsPanel; clearBtn.OnClick += Clear; GUILayoutX mainLayout = layout.AddLayoutX(); listView = new GUIListView(Width, Height - TITLE_HEIGHT, ENTRY_HEIGHT, mainLayout); detailsArea = new GUIScrollArea(); mainLayout.AddElement(detailsArea); detailsArea.Active = false; Debug.OnAdded += OnEntryAdded; // DEBUG ONLY for (int i = 0; i < 10; i++) Debug.Log("DUMMY ENTRY #" + i); } private void OnEditorUpdate() { listView.Update(); dbg_disable++; } private void OnDestroy() { Debug.OnAdded -= OnEntryAdded; } /// protected override void WindowResized(int width, int height) { listView.SetSize(width, height); base.WindowResized(width, height); } /// /// Triggered when a new entry is added in the debug log. /// /// Type of the message. /// Message string. private void OnEntryAdded(DebugMessageType type, string message) { if (dbg_disable > 1) return; // Check if compiler message, otherwise parse it normally LogEntryData logEntry = ScriptCodeManager.ParseCompilerMessage(message); if (logEntry == null) logEntry = Debug.ParseLogMessage(message); ConsoleEntryData newEntry = new ConsoleEntryData(); newEntry.type = type; newEntry.callstack = logEntry.callstack; newEntry.message = logEntry.message; entries.Add(newEntry); if (DoesFilterMatch(type)) { listView.AddEntry(newEntry); filteredEntries.Add(newEntry); } } /// /// Changes the filter that controls what type of messages are displayed in the console. /// /// Flags that control which type of messages should be displayed. private void SetFilter(EntryFilter filter) { if (this.filter == filter) return; this.filter = filter; listView.Clear(); filteredEntries.Clear(); foreach (var entry in entries) { if (DoesFilterMatch(entry.type)) { listView.AddEntry(entry); filteredEntries.Add(entry); } } sSelectedElementIdx = -1; } /// /// Checks if the currently active entry filter matches the provided type (i.e. the entry with the type should be /// displayed). /// /// Type of the entry to check. /// True if the entry with the specified type should be displayed in the console. private bool DoesFilterMatch(DebugMessageType type) { switch (type) { case DebugMessageType.Info: return filter.HasFlag(EntryFilter.Info); case DebugMessageType.Warning: return filter.HasFlag(EntryFilter.Warning); case DebugMessageType.Error: return filter.HasFlag(EntryFilter.Error); } return false; } /// /// Removes all entries from the console. /// private void Clear() { listView.Clear(); entries.Clear(); filteredEntries.Clear(); sSelectedElementIdx = -1; } /// /// Shows or hides the details panel. /// /// True to show, false to hide. private void ToggleDetailsPanel(bool show) { detailsArea.Active = show; if (show) RefreshDetailsPanel(); } /// /// Updates the contents of the details panel according to the currently selected element. /// private void RefreshDetailsPanel() { detailsArea.Layout.Clear(); if (sSelectedElementIdx != -1) { ConsoleEntryData entry = filteredEntries[sSelectedElementIdx]; LocString message = new LocEdString(entry.message); GUILabel messageLabel = new GUILabel(message); detailsArea.Layout.AddElement(messageLabel); detailsArea.Layout.AddSpace(10); if (entry.callstack != null) { foreach (var call in entry.callstack) { string callMessage; if (string.IsNullOrEmpty(call.method)) callMessage = "\tat " + call.file + ":" + call.line; else callMessage = "\t" + call.method + " at " + call.file + ":" + call.line; GUIButton callBtn = new GUIButton(new LocEdString(callMessage)); detailsArea.Layout.AddElement(callBtn); CallStackEntry hoistedCall = call; callBtn.OnClick += () => { CodeEditor.OpenFile(hoistedCall.file, hoistedCall.line); }; } } } } /// /// Contains data for a single entry in the console. /// private class ConsoleEntryData : GUIListViewData { public DebugMessageType type; public string message; public CallStackEntry[] callstack; } /// /// Contains GUI elements used for displaying a single entry in the console. /// private class ConsoleGUIEntry : GUIListViewEntry { private const int CALLER_LABEL_HEIGHT = 11; private const int PADDING = 3; private const int MESSAGE_HEIGHT = ENTRY_HEIGHT - CALLER_LABEL_HEIGHT - PADDING * 2; private static readonly Color BG_COLOR = Color.DarkGray; private static readonly Color SELECTION_COLOR = Color.DarkCyan; private GUIPanel overlay; private GUIPanel main; private GUIPanel underlay; private GUITexture icon; private GUILabel messageLabel; private GUILabel functionLabel; private GUITexture background; private int entryIdx; private string file; private int line; /// public override void BuildGUI() { main = Layout.AddPanel(0, 1, 1, GUIOption.FixedHeight(ENTRY_HEIGHT)); overlay = main.AddPanel(-1, 0, 0, GUIOption.FixedHeight(ENTRY_HEIGHT)); underlay = main.AddPanel(1, 0, 0, GUIOption.FixedHeight(ENTRY_HEIGHT)); GUILayoutX mainLayout = main.AddLayoutX(); GUILayoutY overlayLayout = overlay.AddLayoutY(); GUILayoutY underlayLayout = underlay.AddLayoutY(); icon = new GUITexture(null); messageLabel = new GUILabel(new LocEdString(""), EditorStyles.MultiLineLabel, GUIOption.FixedHeight(MESSAGE_HEIGHT)); functionLabel = new GUILabel(new LocEdString(""), GUIOption.FixedHeight(CALLER_LABEL_HEIGHT)); GUILayoutY iconLayout = mainLayout.AddLayoutY(); mainLayout.AddSpace(PADDING); iconLayout.AddFlexibleSpace(); iconLayout.AddElement(icon); iconLayout.AddFlexibleSpace(); GUILayoutY messageLayout = mainLayout.AddLayoutY(); messageLayout.AddElement(messageLabel); messageLayout.AddElement(functionLabel); mainLayout.AddSpace(PADDING); background = new GUITexture(Builtin.WhiteTexture, GUIOption.FixedHeight(ENTRY_HEIGHT)); underlayLayout.AddElement(background); GUIButton button = new GUIButton(new LocEdString(""), EditorStyles.Blank, GUIOption.FixedHeight(ENTRY_HEIGHT)); overlayLayout.AddElement(button); button.OnClick += OnClicked; button.OnDoubleClick += OnDoubleClicked; } /// public override void UpdateContents(int index, ConsoleEntryData data) { if (index != sSelectedElementIdx) { if (index%2 != 0) { background.Visible = true; background.SetTint(BG_COLOR); } else { background.Visible = false; } } else { background.Visible = true; background.SetTint(SELECTION_COLOR); } switch (data.type) { case DebugMessageType.Info: icon.SetTexture(EditorBuiltin.GetLogIcon(LogIcon.Info, 32)); break; case DebugMessageType.Warning: icon.SetTexture(EditorBuiltin.GetLogIcon(LogIcon.Warning, 32)); break; case DebugMessageType.Error: icon.SetTexture(EditorBuiltin.GetLogIcon(LogIcon.Error, 32)); break; } messageLabel.SetContent(new LocEdString(data.message)); string method = ""; if (data.callstack != null && data.callstack.Length > 0) { file = data.callstack[0].file; line = data.callstack[0].line; if (string.IsNullOrEmpty(data.callstack[0].method)) method = "\tat " + file + ":" + line; else method = "\t" + data.callstack[0].method + " at " + file + ":" + line; } else { file = ""; line = 0; } functionLabel.SetContent(new LocEdString(method)); entryIdx = index; } /// /// Triggered when the entry is selected. /// private void OnClicked() { sSelectedElementIdx = entryIdx; ConsoleWindow window = GetWindow(); window.RefreshDetailsPanel(); RefreshEntries(); } /// /// Triggered when the entry is double-clicked. /// private void OnDoubleClicked() { if(!string.IsNullOrEmpty(file)) CodeEditor.OpenFile(file, line); } } } }