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);
}
}
}
}