local commonPath = string.gsub(getfenv(1)._.Name, "%.UI%.LogWindow$", "");
import (commonPath .. ".UI.PlainWindow");
LogWindow = class(Thurallor.UI.PlainWindow);
LogWindow.startupTime = Turbine.Engine.GetGameTime();
function LogWindow:Constructor(title, sources, settings)
-- Default settings
if (not settings) then
settings = {};
settings.showTimeStamps = true;
settings.showTraceNames = true;
settings.traceEnable = {};
settings.traceName = {};
settings.x, settings.y = 0, 0;
settings.width, settings.height = 300, 200;
settings.colors = {};
settings.colors.hilight = Turbine.UI.Color(1, 1, 1, 1);
settings.colors.fore = Turbine.UI.Color(1, 0.75, 0.75, 0.75);
settings.colors.back = Turbine.UI.Color(1, 0.25, 0.25, 0.25);
end
self.settings = settings;
Thurallor.UI.PlainWindow.Constructor(self, title);
self.titleBar:SetReadOnly(false);
self.textArea = Turbine.UI.Control();
self:SetInnerControl(self.textArea);
self.textArea:SetBackColor(Turbine.UI.Color.Black);
self.textBox = Turbine.UI.TextBox();
self.textBox:SetParent(self.textArea);
self.textBox:SetLeft(10);
self.textBox:SetReadOnly(true);
self.textBox:SetSelectable(true);
self.textBox:SetMarkupEnabled(true);
self.textBox:SetFont(Turbine.UI.Lotro.Font.LucidaConsole12);
self.textBox:SetText(""); -- apply font choice
self.scrollBar = Turbine.UI.Lotro.ScrollBar();
self.scrollBar:SetParent(self.textArea);
self.scrollBar:SetOrientation(Turbine.UI.Orientation.Vertical);
self.scrollBar:SetWidth(10);
self.textBox:SetVerticalScrollBar(self.scrollBar);
-- Display a pulldown menu icon in the upper left corner.
self:SetMenu(Turbine.UI.ContextMenu());
local menuItem, subItem;
-- Add "Show traces" option in pulldown menu.
self.traceMenuItems = {};
self.tracesItem = Turbine.UI.MenuItem("Show traces");
self:AddMenuItem(self.tracesItem);
for trace, enable in pairs(self.settings.traceEnable) do
local name = self.settings.traceName[trace];
self:SetTrace(trace, name, enable);
end
-- Add "Show fields" option in pulldown menu.
menuItem = Turbine.UI.MenuItem("Show fields");
self:AddMenuItem(menuItem);
subItem = Turbine.UI.MenuItem("Timestamp", true, self.settings.showTimeStamps);
subItem.Click = function(item)
local showing = not self.settings.showTimeStamps;
self.settings.showTimeStamps = showing;
item:SetChecked(showing);
end
menuItem:GetItems():Add(subItem);
subItem = Turbine.UI.MenuItem("Trace name", true, self.settings.showTraceNames);
subItem.Click = function(item)
local showing = not self.settings.showTraceNames;
self.settings.showTraceNames = showing;
item:SetChecked(showing);
end
menuItem:GetItems():Add(subItem);
-- Add "Clear contents" option in pulldown menu.
menuItem = Turbine.UI.MenuItem("Clear contents");
menuItem.Click = function()
self.textBox:SetText(nil);
self.totalLength = 0;
self:AppendText("Window cleared");
end
self:AddMenuItem(menuItem);
-- Add "Clone" option in pulldown menu.
menuItem = Turbine.UI.MenuItem("Clone Window");
menuItem.Click = function()
self:Clone();
end
self:AddMenuItem(menuItem);
self.topLine = 1;
self.bottomLine = 0;
self.lineLengths = {};
self.totalLength = 0;
self.messageAlreadyReceived = {}; -- can theoretically grow forever, but for now, it's not a problem
self.prevLine = "";
-- Register callbacks with source object(s)
self.logCallback = function(src, args)
self:AppendText(unpack(args));
end
for _, source in pairs(sources) do
AddCallback(source, "Log", self.logCallback);
end
self.sources = ShallowTableCopy(sources);
-- Apply window colors, position, size
self:SetPosition(self.settings.x, self.settings.y);
self:SetSize(self.settings.width, self.settings.height);
self:SetForeColor(Turbine.UI.Color(settings.colors.fore.A, settings.colors.fore.R, settings.colors.fore.G, settings.colors.fore.B));
self:SetBackColor(Turbine.UI.Color(settings.colors.back.A, settings.colors.back.R, settings.colors.back.G, settings.colors.back.B));
self:SetHilightColor(Turbine.UI.Color(settings.colors.hilight.A, settings.colors.hilight.R, settings.colors.hilight.G, settings.colors.hilight.B));
end
function LogWindow:Close()
-- Unregister callbacks with source object(s)
for source in values(self.sources) do
RemoveCallback(source, "Log", self.logCallback);
end
Thurallor.UI.PlainWindow.Close(self);
end
function LogWindow:Clone()
local new = LogWindow(self.titleBar:GetText(), self.sources, DeepTableCopy(self.settings));
new.textBox:SetText(self.textBox:GetText());
new.topLine = self.topLine;
new.bottomLine = self.bottomLine;
new.lineLengths = ShallowTableCopy(self.lineLengths);
new.totalLength = self.totalLength;
new:SetVisible(true);
new:SetPosition(self.settings.x + 20, self.settings.y + 20);
end
function LogWindow:GetSettings()
return self.settings;
end
function LogWindow:SetTrace(trace, name, enable)
if (self.traceMenuItems[trace] == nil) then
local subItem = Turbine.UI.MenuItem(name, true, enable);
subItem.Click = function(item)
local enable = not self.settings.traceEnable[trace];
self.settings.traceEnable[trace] = enable;
item:SetChecked(enable);
end
self.traceMenuItems[trace] = subItem;
self.tracesItem:GetItems():Add(subItem);
else
self.traceMenuItems[trace]:SetChecked(enable);
end
self.settings.traceName[trace] = name;
self.settings.traceEnable[trace] = enable;
end
function LogWindow:SetHilightColor(color)
self.settings.colors.hilight = color;
Thurallor.UI.PlainWindow.SetForeColor(self, color);
end
function LogWindow:SetForeColor(color)
self.settings.colors.fore = color;
self.textBox:SetForeColor(color);
end
function LogWindow:SetBackColor(color)
self.settings.colors.back = color;
Thurallor.UI.PlainWindow.SetBackColor(self, color);
local dimColor = Thurallor.Utils.Color(1, color.R, color.G, color.B);
dimColor:Set("V", 0.5);
self.dimColor = dimColor:GetHex();
end
function LogWindow:AppendText(text, trace, onceOnly)
if (trace and not self.settings.traceEnable[trace]) then
return;
end
-- Ensure than 'onceOnly' messages are only displayed once per frame.
if (onceOnly) then
local messageId = trace .. " " .. text;
local lastReceivedTime = self.messageAlreadyReceived[messageId];
local currentTime = Turbine.Engine.GetGameTime();
if (lastReceivedTime == currentTime) then
return;
end
self.messageAlreadyReceived[messageId] = currentTime;
end
-- Remember the user's selection so we can reapply it after removing some text.
local selStart, selLen = self.textBox:GetSelectionStart(), self.textBox:GetSelectionLength();
newText = "";
if (self.settings.showTimeStamps) then
local gameTime = Turbine.Engine.GetGameTime() - LogWindow.startupTime;
newText = newText .. string.format("%.2f", gameTime) .. " ";
end
if (trace and self.settings.showTraceNames) then
newText = newText .. tostring(trace) .. " ";
end
newText = newText .. text;
local newTextLen = string.len(newText) + 1; -- newline will be prepended below
local maxLen = 64 * 1024 -1; -- limit for TextBox content seems to be 64 kB
if (newTextLen > maxLen) then
newText = string.sub(newText, -maxLen);
end
-- Remove text at the top (if necessary) to make way for appended text.
local overflow = self.totalLength + newTextLen - maxLen;
if (overflow > 0) then
local removed = 0;
while (removed < overflow) do
local firstLineLength = self.lineLengths[self.topLine];
self.lineLengths[self.topLine] = nil;
self.topLine = self.topLine + 1;
removed = removed + firstLineLength;
end
selStart = selStart - removed;
self.totalLength = self.totalLength - removed;
self.textBox:SetSelection(0, removed);
self.textBox:SetSelectedText(nil);
end
-- Dim the initial part of the line that is the same as the previous line (if any)
local prevLine = self.prevLine;
self.prevLine = newText;
local spaces = 0;
for n = 1, newText:len() do
if (newText:sub(n, n) == prevLine:sub(n, n)) then
spaces = spaces + 1;
else
break;
end
end
if (spaces > 0) then
newText = "<rgb=#" .. self.dimColor .. ">" .. newText:sub(1, spaces) .. "</rgb>" .. newText:sub(spaces + 1);
end
self.textBox:AppendText("\n" .. newText);
self.bottomLine = self.bottomLine + 1;
self.lineLengths[self.bottomLine] = newTextLen;
self.totalLength = self.totalLength + newTextLen;
-- if (self.totalLength ~= string.len(self.textBox:GetText())) then
-- Puts(self.totalLength .. " ~= " .. string.len(self.textBox:GetText()));
-- end
-- If the user was selecting something, reapply.
if (selLen > 0) then
self.textBox:SetSelection(selStart, selLen);
end
end
function LogWindow:SetSize(width, height)
self.settings.width, self.settings.height = width, height;
local textAreaHeight = height - 3 - self.titleBarHeight;
self.textArea:SetSize(width - 2, textAreaHeight);
self.textBox:SetSize(width - 12, textAreaHeight);
self.scrollBar:SetHeight(textAreaHeight);
Thurallor.UI.PlainWindow.SetSize(self, width, height);
end
function LogWindow:SetLeft(x)
self:SetPosition(x, self:GetTop());
end
function LogWindow:SetTop(y)
self:SetPosition(self:GetLeft(), y);
end
function LogWindow:SetPosition(x, y)
self.settings.x, self.settings.y = x, y;
Thurallor.UI.PlainWindow.SetPosition(self, x, y);
end
if (not Thurallor.UI) then
Thurallor.UI = {};
end
Thurallor.UI.LogWindow = LogWindow;