Group = class(Node);
function Group:Constructor(parent, groupID, settings)
Turbine.UI.Window.Constructor(self);
self.constructing = true;
self.parent = parent;
self.manager = parent.manager;
self.groupID = groupID;
self.objectID = groupID;
self.globals = self.manager.settings;
-- Default settings
local defaults = {};
defaults.type = "group";
defaults.hidden = false;
defaults.barsMoveTogether = false;
defaults.caption = {};
defaults.caption.text = L:GetText("/Default/GroupName");
defaults.caption.width = 80;
defaults.caption.height = 20;
defaults.caption.font = Turbine.UI.Lotro.Font.TrajanPro15;
defaults.color = "C0C0C0";
defaults.barIDs = { "new" };
defaults.groupIDs = {};
defaults.deletedBarIDs = {};
defaults.deletedGroupIDs = {};
defaults.eventHandlers = {};
defaults.eventsEnabled = false;
-- Previously-saved settings override the defaults
DeepTableCopy(settings, defaults);
DeepTableCopy(defaults, settings);
self.settings = settings;
self:ReapplySettings();
self:LoadBars();
self:LoadGroups();
self.constructing = nil;
self:SetWantsEvents(self.settings.eventsEnabled);
end
function Group:ReapplySettings()
-- Make color object from hex string
self.color = Thurallor.Utils.Color(1, 0, 0, 0);
self.color:SetHex(self.settings.color);
-- For backward compatibility
for _, handler in pairs(self.settings.eventHandlers) do
if (handler.action) then
handler.action = string.gsub(handler.action, "self:SetHidden%(false%); self:Activate%(%); ", "self:SetHidden(false); ");
handler.action = string.gsub(handler.action, "self:SetHidden%(false%); self:BringToFront%(%); ", "self:SetHidden(false); ");
handler.action = string.gsub(handler.action, "self:Expand%(%); self:Activate%(%); ", "self:Expand(); ");
handler.action = string.gsub(handler.action, "self:Expand%(%); self:BringToFront%(%); ", "self:Expand(); ");
end
end
-- Convert fadetogray's settings
if (self.settings.barsCaptionsMinimized) then
self.settings.barsCaptionsMinimized = nil;
for b = 1, #self.settings.barIDs, 1 do
local barID = self.settings.barIDs[b];
local barSettings = self.globals.bars[barID];
if (type(barSettings) == "table") then
barSettings.caption.hidden = nil;
barSettings.caption.visible = "WhenMouseIsPresent";
barSettings.caption.clickThru = true;
end
end
end
end
function Group:LoadBars()
for b = 1, #self.settings.barIDs, 1 do
local barID = self.settings.barIDs[b];
if (barID == "new") then
barID = self:FindNewObjectID();
self.settings.barIDs[b] = barID;
end
if (self.globals.bars[barID] == nil) then
self.globals.bars[barID] = {};
end
self.manager.objects[barID] = Bar(self, barID, self.globals.bars[barID]);
end
end
function Group:LoadGroups()
for g = 1, #self.settings.groupIDs, 1 do
local groupID = self.settings.groupIDs[g];
if (groupID == "new") then
groupID = self:FindNewObjectID();
self.settings.groupIDs[g] = groupID;
end
if (self.globals.groups[groupID] == nil) then
self.globals.groups[groupID] = {};
end
self.manager.objects[groupID] = Group(self, groupID, self.globals.groups[groupID]);
end
end
-- Iterator for bar objects
function Group:bar_objects()
local state = { ["index"] = 1 };
local function iterator(state)
local barID = self.settings.barIDs[state.index];
state.index = state.index + 1;
if (barID ~= nil) then
local bar = self.manager.objects[barID];
if (bar == nil) then -- proceed to the next bar ID
return iterator(state);
end
return bar;
end
end
return iterator, state, nil;
end
-- Iterator for subgroup objects
function Group:group_objects()
local state = { ["index"] = 1 };
local function iterator(state)
local groupID = self.settings.groupIDs[state.index];
state.index = state.index + 1;
if (groupID ~= nil) then
local group = self.manager.objects[groupID];
if (group == nil) then -- proceed to the next group ID
return iterator(state);
end
return group;
end
end
return iterator, state, nil;
end
function Group:ApplyHiddenness()
for bar in self:bar_objects() do
bar:ApplyHiddenness();
end
for group in self:group_objects() do
group:ApplyHiddenness();
end
end
function Group:Activate()
for bar in self:bar_objects() do
bar:Activate();
end
for group in self:group_objects() do
group:Activate();
end
end
function Group:BringToFront()
for bar in self:bar_objects() do
bar:BringToFront();
end
for group in self:group_objects() do
group:BringToFront();
end
end
function Group:Expand()
if (self.Log) then
DoCallbacks(self, "Log", {"expanding all bars", "GROUP"});
end
for bar in self:bar_objects() do
bar:Expand();
end
for group in self:group_objects() do
group:Expand();
end
end
function Group:IsExpanded()
for bar in self:bar_objects() do
if (not bar:IsExpanded()) then
return false;
end
end
for group in self:group_objects() do
if (not group:IsExpanded()) then
return false;
end
end
return true;
end
function Group:Collapse()
if (self.Log) then
DoCallbacks(self, "Log", {"collapsing all bars", "GROUP"});
end
for bar in self:bar_objects() do
bar:Collapse();
end
for group in self:group_objects() do
group:Collapse();
end
end
function Group:IsCollapsed()
for bar in self:bar_objects() do
if (not bar:IsCollapsed()) then
return false;
end
end
for group in self:group_objects() do
if (not group:IsCollapsed()) then
return false;
end
end
return true;
end
function Group:StartExecuting()
if (self.Log) then
DoCallbacks(self, "Log", {"starting continuous execution", "GROUP"});
end
for bar in self:bar_objects() do
bar:StartExecuting();
end
for group in self:group_objects() do
group:StartExecuting();
end
end
function Group:StopExecuting()
if (self.Log) then
DoCallbacks(self, "Log", {"halting continuous execution", "GROUP"});
end
for bar in self:bar_objects() do
bar:StopExecuting();
end
for group in self:group_objects() do
group:StopExecuting();
end
end
function Group:Redraw(noReset)
for bar in self:bar_objects() do
bar:Redraw(noReset);
end
for group in self:group_objects() do
group:Redraw(noReset);
end
end
function Group:SetTransparency(opacity)
for bar in self:bar_objects() do
bar:SetTransparency(opacity);
end
for group in self:group_objects() do
group:SetTransparency(opacity);
end
self:SaveSettings(true);
end
function Group:SetUseOnRightClick(useOnRightClick)
for bar in self:bar_objects() do
bar:SetUseOnRightClick(useOnRightClick);
end
for group in self:group_objects() do
group:SetUseOnRightClick(useOnRightClick);
end
end
function Group:CanHaveChildren()
return true;
end
function Group:Contains(object)
while (object.parent) do
if (object.parent == self) then
return true;
end
object = object.parent;
end
return false;
end
function Group:GetChildIDs()
local objectIDs = {};
for b = 1, #self.settings.barIDs, 1 do
local barID = self.settings.barIDs[b];
table.insert(objectIDs, barID);
end
for g = 1, #self.settings.groupIDs, 1 do
local groupID = self.settings.groupIDs[g];
table.insert(objectIDs, groupID);
end
return objectIDs;
end
function Group:GetChild(childID)
return self.manager.objects[childID];
end
function Group:GetBarsMoveTogether()
if (self.settings.barsMoveTogether) then
return true;
elseif (self.parent and self.parent.GetBarsMoveTogether) then
return self.parent:GetBarsMoveTogether();
else
return false;
end
end
function Group:IsEmpty()
return ((#self.settings.barIDs == 0) and (#self.settings.groupIDs == 0));
end
function Group:CloneBar(barID)
local oldSettings = self.globals.bars[barID];
local newBarID = self:FindNewObjectID();
local newSettings = { };
self.globals.bars[newBarID] = newSettings;
DeepTableCopy(oldSettings, newSettings);
local newBar = Bar(self, newBarID, newSettings);
self.manager.objects[newBarID] = newBar;
local gridSize = self.globals.gridSize;
newBar:OffsetPosition(gridSize, gridSize);
newBar:MoveOntoScreen();
table.insert(self.settings.barIDs, newBarID);
self:SaveSettings(true);
return newBarID;
end
function Group:CloneGroup(groupID)
--Alert("Settings before cloning", PrettyPrint(self.globals, ""));
local oldSettings = self.globals.groups[groupID];
local newGroupID = self:FindNewObjectID();
local newSettings = {};
-- Create a clone of the existing group, but with nothing in it
local oldGroup = self.manager.objects[groupID];
self.globals.groups[newGroupID] = newSettings;
DeepTableCopy(oldSettings, newSettings);
newSettings.barIDs = {};
newSettings.groupIDs = {};
table.insert(self.settings.groupIDs, newGroupID);
local newGroup = Group(self, newGroupID, newSettings);
self.manager.objects[newGroupID] = newGroup;
-- Clone the bars and subgroups, and transfer the clones from old group to new group
for b = 1, #oldSettings.barIDs, 1 do
local barID = oldSettings.barIDs[b];
local newBarID = oldGroup:CloneBar(barID);
self.manager:TransferBar(newBarID, oldGroup, newGroup);
end
for g = 1, #oldSettings.groupIDs, 1 do
local oldGroupID = oldSettings.groupIDs[g];
local _newGroupID = oldGroup:CloneGroup(oldGroupID);
self.manager:TransferGroup(_newGroupID, oldGroup, newGroup);
end
self:SaveSettings(true);
--Alert("Settings after cloning", PrettyPrint(self.globals, ""));
return newGroupID;
end
function Group:DeleteBar(barID)
local foundPos;
local bar = self.manager.objects[barID];
if (bar) then
self.globals.bars[barID].deleted = true;
bar:Destroy();
end
for pos = 1, #self.settings.barIDs, 1 do
if (self.settings.barIDs[pos] == barID) then
foundPos = pos;
break;
end
end
table.remove(self.settings.barIDs, foundPos);
table.insert(self.settings.deletedBarIDs, barID);
self.manager.objects[barID] = nil;
self:SaveSettings(true);
end
function Group:DeleteGroup(groupID)
local group = self.manager.objects[groupID];
if (group) then
self.globals.groups[groupID].deleted = true;
group:Destroy();
end
local foundPos = Search(self.settings.groupIDs, groupID);
if (foundPos) then
table.remove(self.settings.groupIDs, foundPos);
end
table.insert(self.settings.deletedGroupIDs, groupID);
self.manager.objects[groupID] = nil;
self:SaveSettings(true);
end
function Group:UndeleteBar(barID)
local foundPos = Search(self.settings.deletedBarIDs, barID);
if (foundPos) then
table.remove(self.settings.deletedBarIDs, foundPos);
self.globals.bars[barID].deleted = nil;
self.manager.objects[barID] = Bar(self, barID, self.globals.bars[barID]);
table.insert(self.settings.barIDs, barID);
self:SaveSettings(true);
end
end
function Group:UndeleteGroup(groupID)
local foundPos = Search(self.settings.deletedGroupIDs, groupID);
if (foundPos) then
table.remove(self.settings.deletedGroupIDs, foundPos);
self.globals.groups[groupID].deleted = nil;
self.manager.objects[groupID] = Group(self, groupID, self.globals.groups[groupID]);
table.insert(self.settings.groupIDs, groupID);
self:SaveSettings(true);
end
end
function Group:CreateBar()
local barID = self:FindNewObjectID();
self.globals.bars[barID] = {};
table.insert(self.settings.barIDs, barID);
local bar = Bar(self, barID, self.globals.bars[barID]);
self.manager.objects[barID] = bar;
bar:SnapToGrid();
self:SaveSettings(true);
return bar;
end
function Group:CreateGroup()
local groupID = self:FindNewObjectID();
self.globals.groups[groupID] = { };
table.insert(self.settings.groupIDs, groupID);
self.manager.objects[groupID] = Group(self, groupID, self.globals.groups[groupID]);
self:SaveSettings(true);
return group;
end
function Group:FindNewObjectID()
return self.manager:FindNewObjectID();
end
function Group:Destroy()
for bar in self:bar_objects() do
bar:Destroy();
end
for group in self:group_objects() do
group:Destroy();
end
if (self.importWindow) then
self.importWindow:Close();
end
Node.Destroy(self);
end
function Group:HaveTrash()
if (#self.settings.deletedBarIDs + #self.settings.deletedGroupIDs > 0) then
return true;
end
for group in self:group_objects() do
if (group:HaveTrash()) then
return true;
end
end
return false;
end
function Group:OpenImportWindow()
-- Create the import window
local title = L:GetText("/ImportWindow/Title");
title = string.gsub(title, "<group>", self.settings.caption.text);
self.importWindow = ImportWindow(title, self);
self.importWindow.Closing = function()
self.importWindow = nil;
end
end
function Group:Import(importData)
self.importData = importData;
-- Uncompress the data in the next Update cycle.
self:SetWantsUpdates(true);
self.updateCount = 0;
self.oldUpdateFunction = self.Update;
self.Update = function(g)
g.updateCount = g.updateCount + 1;
if (g.updateCount == 1) then
if (not string.find(g.importData, "{")) then
Puts(L:GetText("/ImportWindow/Decoding"));
g.encodedData = g.importData;
else
g.decodedData = g.importData;
end
g.importData = nil;
elseif (g.updateCount == 3) then
if (g.encodedData) then
-- Remove whitespace and newlines
g.encodedData = string.gsub(g.encodedData, "[%c%s]", "");
local success, decodedData = pcall(Text2Bin, g.encodedData);
if (not success) then
Puts(L:GetText("/ImportWindow/InvalidEncoding"));
g.encodedData = nil;
g.updateCount = 10;
return;
end
g.encodedData = nil;
g.decodedData = decodedData;
end
elseif (g.updateCount == 4) then
Puts(L:GetText("/ImportWindow/Decompressing"));
g.compressor = Thurallor.Utils.LibCompress();
elseif (g.updateCount == 6) then
if (g.decodedData) then
local success, uncompressedData = pcall(g.compressor.Decompress, g.compressor, g.decodedData);
if ((not success) or (not uncompressedData)) then
-- Data was either not compressed or invalid. Let's assume the former for now.
uncompressedData = g.decodedData;
end
g.decodedData = nil;
g.uncompressedData = uncompressedData;
end
elseif (g.updateCount == 7) then
local f, e = loadstring("return " .. g.uncompressedData);
if (not f) then
Puts(L:GetText("/ImportWindow/ParseError") .. tostring(e));
else
g.newObjects = f();
if (type(g.newObjects) == "table") then
Puts(L:GetText("/ImportWindow/Importing"));
g:AddImportedObjects();
else
Puts(L:GetText("/ImportWindow/NothingToDo"));
end
end
g.compressor = nil;
g.uncompressedData = nil;
g.newObjects = nil;
elseif (g.updateCount == 10) then
g:SetWantsUpdates(false);
g.Update = g.oldUpdateFunction; -- if this is a Manager object, it needs to have its Update function restored
g:SaveSettings(true);
end
end
end
function Group:AddImportedObjects()
-- Assign new object IDs to the new bars and groups.
-- Find out which one(s) are at the top level.
local newObjectIDs, hasParent = {}, {};
for oldObjectID, settings in pairs(self.newObjects) do
if (not newObjectIDs[oldObjectID]) then
newObjectIDs[oldObjectID] = self:FindNewObjectID();
--Puts(" " .. oldObjectID .. " -> " .. newObjectIDs[oldObjectID]);
end
if (settings.type == "group") then
local title = L:GetText("/GroupMenu/UndeleteMenu/GroupName");
title = string.gsub(title, "<name>", settings.caption.text);
Puts(" + " .. title);
for b = 1, #settings.barIDs do
local oldBarID = settings.barIDs[b];
if (not newObjectIDs[oldBarID]) then
newObjectIDs[oldBarID] = self:FindNewObjectID();
--Puts(" " .. oldObjectID .. " -> " .. newObjectIDs[oldObjectID]);
end
local barID = newObjectIDs[oldBarID];
settings.barIDs[b] = barID;
hasParent[barID] = true;
end
for g = 1, #settings.groupIDs do
local oldGroupID = settings.groupIDs[g];
if (not newObjectIDs[oldGroupID]) then
newObjectIDs[oldGroupID] = self:FindNewObjectID();
--Puts(" " .. oldObjectID .. " -> " .. newObjectIDs[oldObjectID]);
end
local groupID = newObjectIDs[oldGroupID];
settings.groupIDs[g] = groupID;
hasParent[groupID] = true;
end
else
local title = L:GetText("/GroupMenu/UndeleteMenu/BarName");
title = string.gsub(title, "<name>", settings.caption.text);
Puts(" + " .. title);
end
end
-- Update all "include" slots with new object IDs
for oldObjectID, settings in pairs(self.newObjects) do
if (settings.sequenceItemInfo) then
for i = 1, #settings.sequenceItemInfo, 1 do
local item = settings.sequenceItemInfo[i];
if (item.include and newObjectIDs[item.include]) then
item.include = newObjectIDs[item.include];
end
end
end
end
-- Add new group/bar settings to the database.
for oldObjectID, settings in pairs(self.newObjects) do
if (settings.type == "group") then
local groupID = newObjectIDs[oldObjectID];
self.globals.groups[groupID] = settings;
else
local barID = newObjectIDs[oldObjectID];
self.globals.bars[barID] = settings;
end
end
-- For the top-level bars/groups, add them to the current group.
for oldObjectID, settings in pairs(self.newObjects) do
if (settings.type == "group") then
local groupID = newObjectIDs[oldObjectID];
if (not hasParent[groupID]) then
table.insert(self.settings.groupIDs, groupID);
self.manager.objects[groupID] = Group(self, groupID, settings);
end
else
local barID = newObjectIDs[oldObjectID];
if (not hasParent[barID]) then
table.insert(self.settings.barIDs, barID);
self.manager.objects[barID] = Bar(self, barID, settings);
end
end
end
end
function Group:AddSettingsMenuItem(parent, itemName, amSubMenuOf, fromOptionsPanel)
self.amSubMenuOf = amSubMenuOf or self.amSubMenuOf;
local item = Turbine.UI.MenuItem(L:GetText(itemName), true, false);
item._name = itemName;
item._parent = parent;
parent._itemsByName = parent._itemsByName or {};
parent._itemsByName[itemName] = item;
parent:GetItems():Add(item);
if (itemName == "Root") then
local prevContext = L:SetContext("/GroupMenu");
parent:GetItems():Clear();
self:AddSettingsMenuItem(parent, "Hide");
if (not self:IsExpanded()) then
self:AddSettingsMenuItem(parent, "Expand");
end
if (not self:IsCollapsed()) then
self:AddSettingsMenuItem(parent, "Collapse");
end
self:AddSettingsMenuItem(parent, "CloneGroup");
self:AddSettingsMenuItem(parent, "DeleteGroup");
self:AddSettingsMenuItem(parent, "ExportGroup");
if (not self.importWindow) then
self:AddSettingsMenuItem(parent, "Import");
end
self:AddSettingsMenuItem(parent, "CreateNewBar");
self:AddSettingsMenuItem(parent, "CreateNewSubgroup");
if (#self.settings.deletedBarIDs + #self.settings.deletedGroupIDs > 0) then
self:AddSettingsMenuItem(parent, "Undelete");
end
self:AddSettingsMenuItem(parent, "ArrangeBars");
self:AddSettingsMenuItem(parent, "GroupEventBehaviors");
self:AddSettingsMenuItem(parent, "GroupSettings");
self:AddSettingsMenuItem(parent, "Debug");
L:SetContext(prevContext);
elseif (itemName == "Debug") then
item:SetText(item:GetText() .. "...");
item.Click = function()
self:OpenDebugWindow();
end
elseif (itemName == "ExportGroup") then
item.Click = function()
self:Export();
end
elseif (itemName == "Import") then
item.Click = function()
self:OpenImportWindow();
end
elseif (itemName == "Hide") then
item:SetChecked(self.settings.hidden);
item.Click = function()
self:SetHidden(not self.settings.hidden);
end
elseif (itemName == "GroupSettings") then
local prevContext = L:SetContext("/GroupMenu/GroupSettingsMenu");
self:AddSettingsMenuItem(item, "Rename");
self:AddSettingsMenuItem(item, "BarsMoveTogether");
L:SetContext(prevContext);
elseif (itemName == "Undelete") then
local prevContext = L:SetContext("/GroupMenu/UndeleteMenu");
for b = 1, #self.settings.deletedBarIDs, 1 do
local barID = self.settings.deletedBarIDs[b];
local barItemName = L:GetText("BarName");
if (self.globals.bars[barID]) then
local barCaption = self.globals.bars[barID].caption.text;
barItemName = string.gsub(barItemName, "<name>", barCaption);
local barItem = Turbine.UI.MenuItem(barItemName, true, false);
item:GetItems():Add(barItem);
barItem.Click = function()
self:UndeleteBar(barID);
end
end
end
for g = 1, #self.settings.deletedGroupIDs, 1 do
local groupID = self.settings.deletedGroupIDs[g];
local groupItemName = L:GetText("GroupName");
if (self.globals.groups[groupID]) then
local groupName = self.globals.groups[groupID].caption.text;
groupItemName = string.gsub(groupItemName, "<name>", groupName);
local groupItem = Turbine.UI.MenuItem(groupItemName, true, false);
item:GetItems():Add(groupItem);
groupItem.Click = function()
self:UndeleteGroup(groupID);
end
end
end
L:SetContext(prevContext);
elseif (itemName == "Rename") then
item:SetEnabled(not self.captionEditor);
item.Click = function()
self:EditCaption();
end
elseif (itemName == "BarsMoveTogether") then
local topGroup = self:GetTopTogetherGroup();
if (topGroup and (topGroup ~= self)) then
item:SetChecked(true);
local text = L:GetText("/GroupMenu/GroupSettingsMenu/BarsMoveTogetherInherited");
item:SetText(text:gsub("<name>", topGroup:GetName()));
item:SetEnabled(false);
else
item:SetChecked(self.settings.barsMoveTogether);
end
item.Click = function()
self.settings.barsMoveTogether = not self.settings.barsMoveTogether;
end
elseif (itemName == "ArrangeBars") then
local prevContext = L:SetContext("/GroupMenu/ArrangeBarsMenu");
local descendentBarIDs = self:FindAllDescendentIDs(nil, true, nil);
if (#descendentBarIDs < 2) then
item:SetEnabled(false);
else
self:AddSettingsMenuItem(item, "AlignVertically");
self:AddSettingsMenuItem(item, "AlignHorizontally");
end
L:SetContext(prevContext);
elseif ((itemName == "GroupEventBehaviors") or (itemName == "GlobalEventBehaviors")) then
local prevContext = L:SetContext("/EventBehaviorsMenu");
if (self.settings.eventsEnabled) then
self:AddSettingsMenuItem(item, "AddBehavior");
if (#self.settings.eventHandlers > 0) then
self:AddSettingsMenuItem(item, "CurrentBehaviors");
end
self:AddSettingsMenuItem(item, "DisableEvents");
else
self:AddSettingsMenuItem(item, "EnableEvents");
end
L:SetContext(prevContext);
elseif (itemName == "EnableEvents") then
item.Click = function(ctl)
self:SetEventHandlersEnabled(true);
self:RedisplayMenu(ctl._parent);
end
elseif (itemName == "DisableEvents") then
item.Click = function(ctl)
self:SetEventHandlersEnabled(false);
self:RedisplayMenu(ctl._parent);
end
elseif (itemName == "CurrentBehaviors") then
if (self.settings.eventHandlers) then
for h = 1, #self.settings.eventHandlers do
local handler = self.settings.eventHandlers[h];
local handlerItem = Turbine.UI.MenuItem(handler.description, true, false);
handlerItem._parent = item;
item:GetItems():Add(handlerItem);
local removeItem = Turbine.UI.MenuItem(L:GetText("Remove"), true, false);
removeItem._parent = handlerItem;
handlerItem:GetItems():Add(removeItem);
removeItem.Click = function(ctl)
self:RemoveEventHandler(h);
self:RedisplayMenu(ctl._parent._parent);
end
end
end
elseif (itemName == "AddBehavior") then
local prevContext = L:SetContext("/EventBehaviorsMenu/Actions");
self:AddSettingsMenuItem(item, "ShowGroup");
self:AddSettingsMenuItem(item, "HideGroup");
self:AddSettingsMenuItem(item, "ResetGroup");
self:AddSettingsMenuItem(item, "ExpandGroup");
self:AddSettingsMenuItem(item, "CollapseGroup");
self:AddSettingsMenuItem(item, "SetGroupTransparency");
self:AddSettingsMenuItem(item, "StartExecuting");
self:AddSettingsMenuItem(item, "StopExecuting");
L:SetContext(prevContext);
elseif (itemName == "SetGroupTransparency") then
local prevContext = L:SetContext("/BarMenu/SettingsMenu/TransparencyMenu");
local subItem = self:AddSettingsMenuItem(item, "SetGroupTransparency:");
subItem._name = "SetGroupTransparency";
subItem:SetText(L:GetText("UseGlobalSetting"));
subItem.argName = L:GetText("default");
subItem.argValue = "nil";
for i = 0, 10 do
subItem = self:AddSettingsMenuItem(item, "SetGroupTransparency:");
subItem._name = "SetGroupTransparency";
subItem.argName = L:GetText("PartiallyTransparent"):gsub("<pct>", i * 10);
if (i == 0) then
subItem:SetText(L:GetText("FullyTransparent"));
subItem.argValue = "0";
elseif (i == 10) then
subItem:SetText(L:GetText("FullyOpaque"));
subItem.argValue = "1";
else
subItem:SetText(subItem.argName);
subItem.argValue = "0." .. i;
end
end
L:SetContext(prevContext);
elseif (string.find("|SetGroupTransparency:|ShowGroup|HideGroup|ResetGroup|ExpandGroup|CollapseGroup|StartExecuting|StopExecuting|", "|" .. itemName .. "|")) then
local prevContext = L:SetContext("/EventBehaviorsMenu/Triggers");
if ((itemName ~= "ResetGroup") and (itemName ~= "StopExecuting")) then
self:AddSettingsMenuItem(item, "AtStartup");
end
self:AddSettingsMenuItem(item, "WhenCombatBegins");
self:AddSettingsMenuItem(item, "WhenCombatEnds");
self:AddSettingsMenuItem(item, "WhenTargetChanges");
if (string.find("|SetGroupTransparency:|ResetGroup|ExpandGroup|StartExecuting|", "|" .. itemName .. "|")) then
self:AddSettingsMenuItem(item, "WhenMouseArrives");
end
if (string.find("|SetGroupTransparency:|HideGroup|ResetGroup|CollapseGroup|StopExecuting|", "|" .. itemName .. "|")) then
self:AddSettingsMenuItem(item, "WhenMouseDeparts");
end
if (string.find("|SetGroupTransparency:|ShowGroup|ResetGroup|StartExecuting|StopExecuting|", "|" .. itemName .. "|")) then
self:AddSettingsMenuItem(item, "WhenTraitTreeChanges");
self:AddSettingsMenuItem(item, "WhenStanceChanges");
end
self:AddSettingsMenuItem(item, "WhenPetChanges");
self:AddSettingsMenuItem(item, "WhenMountChanges");
self:AddSettingsMenuItem(item, "WhenYouAreIncapacitated");
self:AddSettingsMenuItem(item, "WhenYouAreRevived");
self.availableEvents = self.manager:GetEventNames();
self:AddSettingsMenuItem(item, "WhenEventOccurs");
L:SetContext(prevContext);
elseif (itemName == "WhenEventOccurs") then
if (#self.availableEvents > 0) then
for e = 1, #self.availableEvents do
local eventName = self.availableEvents[e];
local eventNameItem = Turbine.UI.MenuItem(eventName, true, false);
eventNameItem._parent = item;
item:GetItems():Add(eventNameItem);
eventNameItem.Click = function(ctl)
self:AddEventHandler("WhenEventOccurs:" .. eventName, parent._name, parent.argValue, parent.argName);
self:RedisplayMenu(ctl._parent._parent._parent._parent, "CurrentBehaviors");
end
end
else
local noEventsItem = Turbine.UI.MenuItem(L:GetText("/EventBehaviorsMenu/NoEventsDefined"), false, false);
item:GetItems():Add(noEventsItem);
end
local eventDirectoryItem = Turbine.UI.MenuItem("( . . . )", true, false);
eventDirectoryItem.Click = function()
self.manager:ShowDirectory("events");
end
item:GetItems():Add(eventDirectoryItem);
elseif (string.find("|AtStartup|WhenTargetChanges|WhenCombatBegins|WhenCombatEnds|WhenMouseArrives|WhenMouseDeparts|WhenTraitTreeChanges|WhenStanceChanges|WhenPetChanges|WhenMountChanges|WhenYouAreIncapacitated|WhenYouAreRevived|", "|" .. itemName .. "|")) then
item.Click = function(ctl)
self:AddEventHandler(itemName, parent._name, parent.argValue, parent.argName);
self:RedisplayMenu(ctl._parent._parent._parent, "CurrentBehaviors");
end
elseif (itemName == "AlignVertically") then
local prevContext = L:SetContext("/GroupMenu/ArrangeBarsMenu/AlignVerticallyMenu");
self:AddSettingsMenuItem(item, "Tops");
self:AddSettingsMenuItem(item, "Middles");
self:AddSettingsMenuItem(item, "Bottoms");
L:SetContext(prevContext);
elseif (itemName == "AlignHorizontally") then
local prevContext = L:SetContext("/GroupMenu/ArrangeBarsMenu/AlignHorizontallyMenu");
self:AddSettingsMenuItem(item, "LeftSides");
self:AddSettingsMenuItem(item, "Centers");
self:AddSettingsMenuItem(item, "RightSides");
L:SetContext(prevContext);
elseif (string.find("|Tops|Middles|Bottoms|LeftSides|Centers|RightSides|", "|" .. itemName .. "|")) then
local checked = self:BarsAreAligned(itemName);
item:SetChecked(checked);
item:SetEnabled(not checked);
item.Click = function()
self:AlignBars(itemName);
end
elseif (itemName == "Expand") then
item.Click = function()
self:Expand();
end
elseif (itemName == "Collapse") then
item.Click = function()
self:Collapse();
end
elseif (itemName == "CloneGroup") then
item.Click = function()
self.parent:CloneGroup(self.groupID);
end
elseif (itemName == "DeleteGroup") then
item.Click = function()
self.parent:DeleteGroup(self.groupID);
end
elseif (itemName == "Global") then
self.manager:AddSettingsMenuItem(item, "Root", self);
elseif (itemName == "CreateNewBar") then
item.Click = function()
self:CreateBar();
end
elseif (itemName == "CreateNewSubgroup") then
item.Click = function()
self:CreateGroup();
end
end
return item;
end
function Group:Reset()
if (self.Log) then
DoCallbacks(self, "Log", {"reset", "GROUP"})
end
for bar in self:bar_objects() do
bar:Reset();
end
for group in self:group_objects() do
group:Reset();
end
end
function Group:FindAllDescendentIDs(descendents, includeBars, includeGroups)
if (descendents == nil) then
descendents = {};
end
for group in self:group_objects() do
if (includeGroups) then
table.insert(descendents, group.objectID);
end
group:FindAllDescendentIDs(descendents, includeBars, includeGroups);
end
if (includeBars) then
for bar in self:bar_objects() do
table.insert(descendents, bar.objectID);
end
end
return descendents;
end
function Group:GetBarLocationData(barIDs)
local lefts, tops, rights, bottoms, centers, middles = {}, {}, {}, {}, {}, {};
if (barIDs == nil) then
barIDs = self:FindAllDescendentIDs(nil, true, nil);
end
local numBars = 0;
for b = 1, #barIDs, 1 do
local barID = barIDs[b];
local bar = self.manager.objects[barID];
if (bar) then
numBars = numBars + 1;
local left, top, width, height = bar:GetVisibleLeft(), bar:GetVisibleTop(), bar:GetVisibleWidth(), bar:GetVisibleHeight();
table.insert(lefts, left);
table.insert(tops, top);
local right, bottom = left + width, top + height;
table.insert(rights, right);
table.insert(bottoms, bottom);
local center = (left + right) / 2;
local middle = (top + bottom) / 2;
table.insert(centers, center);
table.insert(middles, middle);
end
end
return numBars, lefts, tops, rights, bottoms, centers, middles;
end
function Group:GetBarLocationStats(numBars, lefts, tops, rights, bottoms, centers, middles)
local minLeft, maxRight = math.min(unpack(lefts)), math.max(unpack(rights));
local minTop, maxBottom = math.min(unpack(tops)), math.max(unpack(bottoms));
local avgCenter, avgMiddle = 0, 0;
for b = 1, numBars, 1 do
avgCenter = avgCenter + centers[b];
avgMiddle = avgMiddle + middles[b];
end
avgCenter = avgCenter / numBars;
avgMiddle = avgMiddle / numBars;
local minCenter, maxCenter = math.min(unpack(centers)), math.max(unpack(centers));
local minMiddle, maxMiddle = math.min(unpack(middles)), math.max(unpack(middles));
return numBars, minLeft, maxRight, minTop, maxBottom, minCenter, avgCenter, maxCenter, minMiddle, avgMiddle, maxMiddle;
end
function Group:BarsAreAligned(alignment)
local barIDs = self:FindAllDescendentIDs(nil, true, nil);
local numBars, lefts, tops, rights, bottoms, centers, middles = self:GetBarLocationData(barIDs);
local _, minLeft, maxRight, minTop, maxBottom, _, avgCenter, _, _, avgMiddle, _ = self:GetBarLocationStats(numBars, lefts, tops, rights, bottoms, centers, middles);
local aligned = true;
for b = 1, numBars, 1 do
if (alignment == "LeftSides") then
aligned = aligned and lefts[b] == minLeft;
elseif (alignment == "RightSides") then
aligned = aligned and rights[b] == maxRight;
elseif (alignment == "Tops") then
aligned = aligned and tops[b] == minTop;
elseif (alignment == "Bottoms") then
aligned = aligned and bottoms[b] == maxBottom;
elseif (alignment == "Centers") then
aligned = aligned and math.floor(centers[b]) == math.floor(avgCenter);
elseif (alignment == "Middles") then
aligned = aligned and math.floor(middles[b]) == math.floor(avgMiddle);
end
end
return aligned;
end
function Group:AlignBars(alignment)
local barIDs = self:FindAllDescendentIDs(nil, true, nil);
local numBars, lefts, tops, rights, bottoms, centers, middles = self:GetBarLocationData(barIDs);
local _, minLeft, maxRight, minTop, maxBottom, _, avgCenter, _, _, avgMiddle, _ = self:GetBarLocationStats(numBars, lefts, tops, rights, bottoms, centers, middles);
for bar in self:bar_objects() do
if (alignment == "LeftSides") then
bar:SetVisibleLeft(minLeft);
elseif (alignment == "RightSides") then
bar:SetVisibleLeft(maxRight - bar:GetVisibleWidth());
elseif (alignment == "Tops") then
bar:SetVisibleTop(minTop);
elseif (alignment == "Bottoms") then
bar:SetVisibleTop(maxBottom - bar:GetVisibleHeight());
elseif (alignment == "Centers") then
bar:SetVisibleLeft(avgCenter - math.floor((bar:GetVisibleWidth() + 0.5) / 2));
elseif (alignment == "Middles") then
bar:SetVisibleTop(avgMiddle - math.floor((bar:GetVisibleHeight() + 0.5) / 2));
end
bar:Redraw();
end
self:SaveSettings(false);
end
-- Finds the topmost group containing (or being) this group which has the "bars move together" option enabled.
function Group:GetTopTogetherGroup()
local group, topGroup = self;
while (group) do
if (group.settings.barsMoveTogether) then
topGroup = group;
end
group = group.parent;
end
return topGroup;
end
function Group:SnapToGrid()
-- Snap all children contained in this group and subgroups to grid
for bar in self:bar_objects() do
bar:SnapToGrid();
end
for group in self:group_objects() do
group:SnapToGrid();
end
end
function Group:OffsetPosition(deltaX, deltaY)
-- Move all children contained in this group and subgroups, irrespective of locks
for bar in self:bar_objects() do
bar:OffsetPosition(deltaX, deltaY);
end
for group in self:group_objects() do
group:OffsetPosition(deltaX, deltaY);
end
end
function Group:MouseArrive(objectID)
if (self.mouseInside) then
-- Already inside; do nothing.
return;
end
self.mouseInside = true;
if (self.parent) then
self.parent:MouseArrive(self.objectID);
end
DoCallbacks(self, "MouseArrived");
end
function Group:MouseInside(mouseLeft, mouseTop)
if (self:IsHidden()) then
return false;
end
for bar in self:bar_objects() do
if (bar:MouseInside(mouseLeft, mouseTop)) then
return true;
end
end
for group in self:group_objects() do
if (group:MouseInside(mouseLeft, mouseTop)) then
return true;
end
end
return false;
end
function Group:MouseDepart()
if (self.parent) then
self.parent:MouseDepart(self.objectID);
end
-- If a callback is registered, we need to make sure the mouse hasn't
-- entered another bar in the group before generating the MouseDeparted event.
if (self.MouseDeparted) then
local mouseLeft, mouseTop = Turbine.UI.Display:GetMousePosition();
if (not self:MouseInside(mouseLeft, mouseTop)) then
self.mouseInside = false;
DoCallbacks(self, "MouseDeparted");
end
else
self.mouseInside = false;
end
end
function Group:OpenDebugWindow()
local title = L:GetText("/GroupMenu/Debug") .. ": " .. self.settings.caption.text;
local debugWindow = Thurallor.UI.LogWindow(title, { self });
debugWindow:SetBackColor(self.color);
debugWindow:SetTrace("GROUP", "Group status changes", true);
debugWindow:SetTrace("EVENT", "Event behaviours", true);
debugWindow:SetVisible(true);
debugWindow:AppendText("Debugging started");
end
function Group:EditCaption(optionsNode)
if (self.captionEditor == nil) then
if (optionsNode) then
self.settings.caption.font = optionsNode:GetFont();
self.settings.caption.width = optionsNode:GetWidth();
self.settings.caption.height = optionsNode:GetHeight();
end
self.captionEditor = CaptionEditor("GroupName", self, self.settings.caption.width, self.settings.caption.height, self.settings.caption.text);
self:FormatCaptionEditorTextBox();
self.captionEditor.Closed = function()
self.captionEditor = nil;
end
end
end
function Group:FormatCaptionEditorTextBox()
if (self.captionEditor ~= nil) then
local control = self.captionEditor:GetTextBox();
control:SetFont(self.settings.caption.font);
control:SetForeColor(self.color);
control:SetFontStyle(Turbine.UI.FontStyle.Outline);
control:SetOutlineColor(Turbine.UI.Color(0.75, 0, 0, 0));
control:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleLeft);
control:SetWidth(self.settings.caption.width);
control:SetHeight(self.settings.caption.height);
self.captionEditor:Redraw();
end
end
function Group:SetCaptionSize(width, height)
Node.SetCaptionSize(self, width, height);
self:FormatCaptionEditorTextBox();
end
function Group:SetColor(color)
Node.SetColor(self, color);
self:FormatCaptionEditorTextBox();
end