lotrointerface.com
Search Downloads

LoTROInterface SVN SequenceBars

[/] [trunk/] [Thurallor/] [SequenceBars/] [SequenceEditor.lua] - Rev 181

Go to most recent revision | Compare with Previous | Blame | View Log

SequenceEditor = class(Turbine.UI.Lotro.Window);

-- Class variable keeping track of all SequenceEditor instances:
SequenceEditor.instances = {};

function SequenceEditor:Constructor(bar, settings)
    Turbine.UI.Lotro.Window.Constructor(self);
    SequenceEditor.instances[self] = true;

    self.bar = bar;
    self.manager = self.bar.manager;
    self.settings = settings;
    self.globals = self.manager.settings;

    self:SetText(bar:GetName());
    self:SetZOrder(2);
    
    local minWidth = L:GetText("/SequenceEditor/MinWidth");
    if (not self.settings.sequenceEditor) then
        self.settings.sequenceEditor = {};
        self.settings.sequenceEditor.defaultIcon = resources.BlankIcons[3];
    end
    if (self.settings.sequenceEditor.position) then
        self:SetPosition(unpack(self.settings.sequenceEditor.position));
    else
        self:SetPosition(self.bar:GetPosition());
    end
    if (self.settings.sequenceEditor.size) then
        self:SetSize(unpack(self.settings.sequenceEditor.size));
    else
        self:SetSize(minWidth, 187);
    end
    
    self.relatedSlots = {};
    
    -- Enforce minimum size
    self:SetMinimumWidth(minWidth);
    if (self:GetWidth() < minWidth) then
        self:SetWidth(minWidth);
    end
    self:SetMinimumHeight(142);
    self:SetResizable(true);
    
    self:Redraw();
    self.horizontalLines = {};
    AddCallback(self, "SizeChanged", function()
        self.skipUpdates = 2;
        self:SetWantsUpdates(true); --redraw during update cycle
    end);
    
    self:SelectSlot(nil);
    self:SetVisible(true);
end

function SequenceEditor:UpdateBar(updateEventDirectory)
    self.bar:ShortcutChanged();
    self.manager:NotifyIncluders(self.bar:GetID());
    if (updateEventDirectory) then
        self.manager:SaveSettings(false, true);
    end
end

function SequenceEditor:Activated()
    -- Find the maximum Z order among all other Sequence Editors.
    local maxZ = self:GetZOrder() - 1;
    for editor in keys(SequenceEditor.instances) do
        if (editor ~= self) then
            local z = editor:GetZOrder();
            if (z > maxZ) then
                maxZ = z;
            end
        end
    end
    
    if (maxZ == 2147483647) then
        -- Move all other SequenceEditors back to make room at the front.
        for editor in keys(SequenceEditor.instances) do
            editor:SetZOrder(editor:GetZOrder() - 1);
        end
        maxZ = maxZ - 1;
    end

    -- Move the newly-activated Sequence Editor to the front of the stack.
    self:SetZOrder(maxZ + 1);
end

function SequenceEditor:SetText(barName)
    local title = L:GetText("/SequenceEditor/Title");
    title = string.gsub(title, "<name>", barName);
    Turbine.UI.Lotro.Window.SetText(self, title);
end

function SequenceEditor:SaveSettings()
    self.bar:SaveSettings();
end

function SequenceEditor:Redraw()
    if (self.noRedraw) then
        return;
    end
    local width, height = self:GetSize();
    local xMargin = 16;
    local topMargin = 35;
    local bottomMargin = 42;
    local minSlotTabCardWidth = L:GetNumber("/SequenceEditor/SlotTabCardWidth");
    
    if (not self.slotTabCard) then
        self.slotTabCard = Thurallor.UI.TabCard();
        self.slotTabCard:SetParent(self);
        self.slotTabCard:SetTabText(L:GetText("/SequenceEditor/SlotTab"));
        
        -- When mouse reenters the area, redisplay the current slot in case options have changed.
        self.slotTabCard.MouseEnter = function(ctl)
            if (not ctl.mouseInside) then
                ctl.mouseInside = true;
                self:DisplaySlot(self.selectedSlot);
            end
        end
        self.MouseEnter = function()
            if (self.slotTabCard.mouseInside) then
                local left, top = self.slotTabCard:GetMousePosition();
                local width, height = self.slotTabCard:GetSize();
                if ((left < 0) or (left >= width) or (top < 0) or (top >= height)) then
                    self.slotTabCard.mouseInside = false;
                end
            end
        end
        
        -- When slot properties card size changes, redisplay the current slot
        self.slotTabCard.SizeChanged = function()
            self:DisplaySlot(self.selectedSlot);
        end
    end
    
    if (not self.sequenceTabCard) then
        self.sequenceTabCard = Thurallor.UI.TabCard();
        self.sequenceTabCard:SetTabText(L:GetText("/SequenceEditor/SequenceTab"));
        self.slotPanel = Turbine.UI.Control();
        self.sequenceTabCard:SetInteriorControl(self.slotPanel);
        self.sequenceTabCard:SetInteriorAlignment(Turbine.UI.ContentAlignment.TopLeft);

        -- When sequence card size changes, update the slot grid layout
        self.sequenceTabCard.SizeChanged = function()
            self:DeleteEmptyTrailingSlots();
            local slotPanelWidth = self.sequenceTabCard:GetWidth() - 6;
            self.slotSize = self.settings.slotSize + self.settings.slotSpacing;
            self.slotsWide = math.floor((slotPanelWidth - 3) / self.slotSize);
            self.slotsHigh = math.max(math.ceil(#self.settings.sequenceItemInfo / self.slotsWide) + 1, 2);
            self:ExtendSequenceTo(self.slotsHigh * self.slotsWide);
            self.slotPanel:SetSize(self.slotsWide * self.slotSize + 3, self.slotsHigh * self.slotSize + 3);
            self.slotPanel:SetBackColor(self.bar.color);
            
            -- Draw some quickslots
            if (not self.sequenceItems) then
                self.sequenceItems = {};
            end
            local s = 1;
            for y = 1, self.slotsHigh, 1 do
                for x = 1, self.slotsWide, 1 do
                    self:RebuildSlot(s, x, y);
                    s = s + 1;
                end
            end
        end

    end
    
    if (not self.splitControl) then
        self.splitControl = Thurallor.UI.SplitControl(Turbine.UI.Orientation.Horizontal, self.sequenceTabCard, self.slotTabCard);
        self.splitControl:SetParent(self);
        self.splitControl:SetPosition(xMargin, topMargin);
        self.splitControl:SetSashSize(5);
        self.splitControl:SetSpacing(3);
        if (self.settings.sequenceEditor.sashPosition == nil) then
            self.splitControl:SetSashPosition(1);
            self.settings.sequenceEditor.sashPosition = self.splitControl:GetSashPosition();
        else
            self.splitControl:SetSashPosition(self.settings.sequenceEditor.sashPosition);
        end
        AddCallback(self.splitControl, "SashPositionChanged", function()
            self.settings.sequenceEditor.sashPosition = self.splitControl:GetSashPosition();
            self:SaveSettings();
        end);
    end
    local splitControlWidth = width - (2 * xMargin);
    self.splitControl:SetSize(splitControlWidth, height - (topMargin + bottomMargin));
    self.sequenceTabCard:SizeChanged();
        
    -- Enforce minimum width for slot properties card
    self.splitControl:SetMaximumSashPosition((splitControlWidth - minSlotTabCardWidth - 5.5) / splitControlWidth);
    -- Enforce minimum width for sequence card
    self.splitControl:SetMinimumSashPosition(124 / splitControlWidth);

    if (not self.instructions) then
        self.instructions = Turbine.UI.Label();
        self.instructions:SetParent(self);
        self.instructions:SetMouseVisible(false);
        self.instructions:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleCenter);
        self.instructions:SetFont(Turbine.UI.Lotro.Font.TrajanPro15);
        self.instructions:SetMultiline(true);
        self.instructions:SetForeColor(Turbine.UI.Color.PaleGoldenrod);
        self.instructions:SetText(L:GetText("/SequenceEditor/Instructions"));
    end
    self.instructions:SetSize(self:GetWidth() - 2 * xMargin, 30);
    self.instructions:SetPosition(xMargin, self:GetHeight() - 40);

    if (self.selectedSlot) then
        self:SelectSlot(self.selectedSlot);
    end
end

function SequenceEditor:SetMinimumHeight(height)
    Turbine.UI.Window.SetMinimumHeight(self, height);
    if (self:GetHeight() < height) then
        self:SetHeight(height);
        self.skipUpdates = 0;        
        self:SetWantsUpdates(true);
    end
end

function SequenceEditor:Update()
    if (self.skipUpdates > 0) then
        self.skipUpdates = self.skipUpdates - 1;
    else
        self:Redraw();
        self.settings.sequenceEditor.size = {self:GetSize()};
        self:SaveSettings();
        self:SetWantsUpdates(false);
    end
end

function SequenceEditor:PositionChanged()
    self.settings.sequenceEditor.position = {self:GetPosition()};
    self:SaveSettings();
end

function SequenceEditor:MoveOntoScreen()
    local screenWidth, screenHeight = Turbine.UI.Display:GetWidth(), Turbine.UI.Display:GetHeight();
    local left, top = self:GetPosition();
    local right, bottom = left + self:GetWidth(), top + self:GetHeight();
    local deltaX, deltaY = 0, 0;
    if (left < 0) then
        deltaX = -left;
    elseif (right > screenWidth) then
        deltaX = screenWidth - right;
    end
    if (top < 0) then
        deltaY = -top;
    elseif (bottom > screenHeight) then
        deltaY = screenHeight - bottom;
    end
    self:SetPosition(left + deltaX, top + deltaY);
end

function SequenceEditor:SetVisible(visible)
    Turbine.UI.Lotro.Window.SetVisible(self, visible);
    if (visible) then
        self:MoveOntoScreen();
    end
end

function SequenceEditor:DisplaySlot(s)
    if (self.ItemMovedCallback) then
        RemoveCallback(Thurallor.Utils.Watcher, "ItemMoved", self.ItemMovedCallback);
        self.ItemMovedCallback = nil;
    end

    self.slotTabCard:SetInteriorControl(nil);
    for _, control in pairs(self.horizontalLines) do
        control:SetParent(nil);
    end
    self.horizontalLines = {};
    self.slotTabCard:SetTabText(L:GetText("/SequenceEditor/SlotTab"));
    if ((s == nil) or (s > #self.settings.sequenceItemInfo)) then
        self.slotTabCard:SetVisible(false);
        return;
    else
        self.slotTabCard:SetVisible(true);
    end
    self:ExtendSequenceTo(s);
    local info = self.settings.sequenceItemInfo[s];
    local slotTabCardWidth = self.slotTabCard:GetWidth();

    -- Update slot tab caption and create properties container
    self.slotTabCard:SetTabText(L:GetText("/SequenceEditor/SlotTab") .. " " .. tostring(s));
    self.slotProperties = Turbine.UI.Control();
    self.slotTabCard:SetInteriorControl(self.slotProperties);
    self.slotProperties:SetWidth(slotTabCardWidth - 17);
    
    local function UpdateIcon()
        local center = math.floor((slotTabCardWidth - 20) / 2);
        self.selectedIcon = self:BuildSlot(s, self.slotProperties, self.selectedIcon, center - 16, 10);
        self.selectedIcon.numLabel:SetText(nil);     -- don't display slot number overlay
        self.selectedIcon.numLabel.MouseClick = nil; -- disable right-click menu
        self.selectedIcon.numLabel.MouseDown = nil;  -- disable dragging
        self.selectedIcon:SetActionEnabled(true);
        self.selectedIcon:SetAllowDrop(true);
        self:RebuildSlot(s, nil, nil, true);
    end
    UpdateIcon();

    -- Create a function for adding a centered line of text to the properties control
    local AddLabel = function(top, text, color)
        local label = Turbine.UI.Label();
        label:SetParent(self.slotProperties);
        label:SetPosition(0, top);
        label:SetSize(slotTabCardWidth - 20, 16);
        top = top + 16;
        label:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleCenter);
        label:SetFont(Turbine.UI.Lotro.Font.TrajanPro14);
        label:SetForeColor(color);
        label:SetMarkupEnabled(true);
        label:SetText(text);
        return top, label;
    end
    
    -- Create a function for adding a centered button to the properties control
    local AddButton = function(top, width, text, font)
        local button = Turbine.UI.Lotro.Button();
        button:SetParent(self.slotProperties);
        local left = math.floor((self.slotProperties:GetWidth() - width) / 2);
        button:SetPosition(left, top + 2);
        button:SetWidth(width);
        if (font == nil) then
            font = Turbine.UI.Lotro.Font.TrajanPro14;
        end
        button:SetFont(font);
        button:SetText(text);
        top = top + 2 + button:GetHeight();
        return top, button;
    end
    
    -- Create a function for adding a centered text box to the properties control
    local AddTextBox = function(top, text, color, resizable, textBoxSize)
        local textBox = Turbine.UI.Lotro.TextBox();
        textBox:SetParent(self.slotProperties);
        textBox:SetPosition(0, top);
        textBox:SetSize(slotTabCardWidth - 20, 20);
        if (textBoxSize) then
            textBox:SetHeight(textBoxSize[2]);
        end
        top = top + textBox:GetHeight();
        textBox:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleLeft);
        textBox:SetMultiline(false);
        textBox:SetFont(Turbine.UI.Lotro.Font.TrajanPro14);
        textBox:SetForeColor(color);
        textBox:SetText(text);
        if (resizable) then
            textBox.resizer = Thurallor.UI.Resizer(textBox);
            textBox.resizer:SetMinimumHeight(20);
            textBox.resizer:SetMinimumWidth(slotTabCardWidth - 20);
            textBox.resizer:SetMaximumWidth(slotTabCardWidth - 20);
        end
        return top, textBox;
    end

    -- Create a function for adding a left-justified checkbox to the properties control
    local AddCheckBox = function(top, text, color, checked)
        local checkBox = Turbine.UI.Lotro.CheckBox();
        checkBox:SetParent(self.slotProperties);
        checkBox:SetPosition(0, top);
        checkBox:SetSize(slotTabCardWidth - 20, 20);
        top = top + 20;
        checkBox:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleLeft);
        checkBox:SetFont(Turbine.UI.Lotro.Font.TrajanPro14);
        checkBox:SetForeColor(color);
        checkBox:SetText(text);
        if (checked) then
            checkBox:SetChecked(true);
        end
        return top, checkBox;
    end
    
    -- Create a function for adding a left-justified radio button to the properties control
    local AddRadioButton = function(top, text, color, checked)
        local button = Thurallor.UI.RadioButton(self.slotProperties, text, checked);
        button:SetPosition(0, top);
        button:SetSize(slotTabCardWidth - 20, 16);
        top = top + 16;
        button:SetFont(Turbine.UI.Lotro.Font.TrajanPro14);
        button:SetForeColor(color);
        return top, button;
    end

    -- Create a function for adding a drop-down listbox to the properties control
    local AddDropDown = function(top, items, default)
        local dropDown = Thurallor.UI.DropDown(items, default or items[1]);
        dropDown:SetParent(self.slotProperties);
        dropDown:SetPosition(0, top);
        dropDown:SetWidth(slotTabCardWidth - 20);
        top = top + dropDown:GetHeight();
        return top, dropDown;
    end

    -- Create a function for adding a pull-down hierarchical menu to the properties control
    local AddPullDown = function(top, items, default)
        local pullDown = Thurallor.UI.PullDownMenu(items, default);
        pullDown:SetParent(self.slotProperties);
        pullDown:SetPosition(0, top);
        pullDown:SetWidth(slotTabCardWidth - 20);
        top = top + pullDown:GetHeight();
        return top, pullDown;
    end

    -- Create a function for adding a horizontal scrollbar to the properties control
    local AddSlider = function(top, minimum, maximum, value)
        local scrollBar = Turbine.UI.Lotro.ScrollBar();
        scrollBar:SetParent(self.slotProperties);
        scrollBar:SetPosition(0, top);
        scrollBar:SetSize(slotTabCardWidth - 20, 10);
        scrollBar:SetMinimum(minimum);
        scrollBar:SetMaximum(maximum);
        scrollBar:SetSmallChange(1);
        scrollBar:SetValue(value);
        top = top + 10;
        return top, scrollBar;
    end

    local AddHorizontalLine = function(top)
        top = top + 32;
        local control = Turbine.UI.Control()
        control:SetBackColor(Turbine.UI.Color(1, 0.161, 0.188, 0.286));
        control:SetParent(self.slotTabCard);
        control:SetPosition(0, top + 8);
        control:SetSize(slotTabCardWidth, 1);
        table.insert(self.horizontalLines, control);
        return top + 1, control;
    end

    -- Create a function for adding an expand/collapse section header to the properties control
    local AddSection = function(top, text, color, expanded)
        top = AddHorizontalLine(top);
        local button = Turbine.UI.Control();
        button:SetParent(self.slotProperties);
        button:SetPosition(0, top);
        button:SetSize(16, 16);
        button:SetBlendMode(Turbine.UI.BlendMode.Overlay);
        local label = Turbine.UI.Label();
        label:SetParent(self.slotProperties);
        label:SetPosition(18, top);
        label:SetSize(slotTabCardWidth - 20 - 18, 16);
        top = top + 16;
        label:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleLeft);
        label:SetFont(Turbine.UI.Lotro.Font.TrajanPro14);
        label:SetForeColor(color);
        label:SetMarkupEnabled(true);
        label:SetText(text);
        function button.MouseClick()
            if (button.expanded) then
                button:SetBackground(resources.Icon.Expand);
                button.expanded = false;
                DoCallbacks(button, "ExpandedChanged", false);
            else
                button:SetBackground(resources.Icon.Collapse);
                button.expanded = true;
                DoCallbacks(button, "ExpandedChanged", true);
            end
        end
        label.MouseClick = button.MouseClick;
        button.expanded = not expanded;
        button:MouseClick();
        return top, button;
    end
    
    -- Draw the caption
    local top = 54;
    if (info.class == "Turbine.UI.Lotro.Quickslot") then
        top = AddLabel(top, L:GetText("/SequenceEditor/StandardQuickslot"), Turbine.UI.Color.PaleGoldenrod);
    elseif ((info.class == "Turbine.UI.Control") or (info.class == "Turbine.UI.Lotro.EntityControl")) then
        top = AddLabel(top, L:GetText("/SequenceEditor/SpecialSlot") .. ":", Turbine.UI.Color.PaleGoldenrod);
        local label;
        top, label = AddLabel(top, info.toolTip, Turbine.UI.Color.PaleGoldenrod);
        -- Some German strings need line wrapping here.
        label:SetHeight(32);
        label:SetTextAlignment(Turbine.UI.ContentAlignment.TopCenter);
    end

    -- Draw the arguments
    if (info.class == "Turbine.UI.Lotro.Quickslot") then
        for key, value in pairs(Turbine.UI.Lotro.ShortcutType) do
            if (info.type == value) then
                top = AddLabel(top, L:GetText("/SequenceEditor/ShortcutType") .. ": " .. L:GetText("/SequenceEditor/ShortcutTypes/" .. key), Turbine.UI.Color.Goldenrod);
                break;
            end
        end
        
        if (info.type == Turbine.UI.Lotro.ShortcutType.Alias) then
            -- Allow the user to specify the command
            top = AddHorizontalLine(top);
            top = AddLabel(top, L:GetText("/SequenceEditor/ChatCommand") .. ":", Turbine.UI.Color.Goldenrod);
            local textBox;
            top, textBox = AddTextBox(top, info.Data, Turbine.UI.Color.White);
            textBox.TextChanged = function(sender, args)
                local text = sender:GetText();
                -- Remove carriage returns
                local stripped = string.gsub(text, "\n", "");
                if (stripped ~= text) then
                    sender:SetText(stripped);
                    text = stripped;
                end
                info.Data = text;
                self:UpdateBar();
            end
        elseif (info.type == Turbine.UI.Lotro.ShortcutType.Item) then
            local shortcut = Turbine.UI.Lotro.Shortcut(info.type, info.Data);
            local item = shortcut:GetItem();
            if (pcall(item.GetMaxStackSize, item)) then
                if (item:GetMaxStackSize() == 1) then -- equippable
                                        
                    -- Allow the user to specify immediate advance, or wait-for-equip
                    top = AddHorizontalLine(top);
                    top = AddLabel(top, L:GetText("/SequenceEditor/AdvanceToNextSlot") .. ":", Turbine.UI.Color.Goldenrod);
                    local button1, button2;
                    top, button1 = AddRadioButton(top, L:GetText("/SequenceEditor/WithLeftClick"), Turbine.UI.Color.DarkGoldenrod, (not info.advanceEvent));
                    top, button2 = AddRadioButton(top, L:GetText("/SequenceEditor/WhenItemEquipped"), Turbine.UI.Color.DarkGoldenrod, info.advanceEvent);
                    Thurallor.UI.RadioButton.LinkPeers({button1, button2});
             
                    button1.Clicked = function()
                        info.advanceEvent = nil;
                        self:UpdateBar();
                    end
                    button2.Clicked = function()
                        info.advanceEvent = "ItemEquipped";
                        self:UpdateBar();
                    end
                end
            -- else "invalid entity" -- probably an item that no longer exists in your inventory
            end
        elseif (info.type == Turbine.UI.Lotro.ShortcutType.Skill) then
            local skillName = self.selectedIcon:GetSkillName();
            
            if (skillName) then
                -- Allow the user to specify immediate advance, or wait-for-execute
                top = AddHorizontalLine(top);
                top = AddLabel(top, L:GetText("/SequenceEditor/AdvanceToNextSlot") .. ":", Turbine.UI.Color.Goldenrod);
                local button1, button2;
                top, button1 = AddRadioButton(top, L:GetText("/SequenceEditor/WithLeftClick"), Turbine.UI.Color.DarkGoldenrod, (not info.advanceEvent));
                top, button2 = AddRadioButton(top, L:GetText("/SequenceEditor/WhenSkillExecuted"), Turbine.UI.Color.DarkGoldenrod, info.advanceEvent);
                Thurallor.UI.RadioButton.LinkPeers({button1, button2});
                if (button2:IsChecked()) then
                    top, checkBox = AddCheckBox(top, L:GetText("/SequenceEditor/UseAlternateDetectMethod"), Turbine.UI.Color.DarkGoldenrod, info.executeDetectByChat);
                    checkBox:SetLeft(16); -- indent
                    checkBox:SetSize(checkBox:GetWidth() - 16, 28);
                    top = top + 8;
                    checkBox.CheckedChanged = function(cb)
                        info.executeDetectByChat = cb:IsChecked();
                        self:UpdateBar();
                    end
                end

                button1.Clicked = function()
                    info.advanceEvent = nil;
                    self:DisplaySlot(self.selectedSlot);
                    self:UpdateBar();
                end
                button2.Clicked = function()
                    info.advanceEvent = "SkillExecuted";
                    self:DisplaySlot(self.selectedSlot);
                    self:UpdateBar();
                end
            -- else this skill isn't in moebius92's SkillData database
            end
        end
        
    elseif (info.class == "Turbine.UI.Lotro.EntityControl") then
        if (info.type == "SelectTarget") then

            local getTargetFuncs = {
                -- args are: watcher, localPlayer, info.raidMemberName, info.number
                ["Self"] =                  "local _, player = ...; return player;";
                ["CurrentTarget"] =         "local watcher = ...; return watcher:GetPlayerTarget();";
                ["CurrentTargetsTarget"] =  "local watcher = ...; return watcher:GetTargetsTarget();";
                ["Pet"] =                   "local _, player = ...; return player:GetPet();";
                ["RaidLeader"] =            "local watcher = ...; local party = watcher.GetParty(); return (party and party:GetLeader());";
                ["RaidMemberByName"] =      "local watcher, _, name = ...; return watcher.GetPartyMemberByName(name, true);";
                ["NextRaidMember"] =        "local watcher = ...; return watcher.GetNextPartyMember();";
                ["FirstAssistee"] =         "local watcher = ...; return watcher.GetPartyAssistTarget(1);";
                ["SecondAssistee"] =        "local watcher = ...; return watcher.GetPartyAssistTarget(2);";
                ["ThirdAssistee"] =         "local watcher = ...; return watcher.GetPartyAssistTarget(3);";
                ["FourthAssistee"] =        "local watcher = ...; return watcher.GetPartyAssistTarget(4);";
                ["NextAssistee"] =          "local watcher = ...; return watcher.GetNextAssistTarget();";
                ["FirstAssisteesTarget"] =  "local watcher = ...; local target = watcher.GetPartyAssistTarget(1); return (target and target:GetTarget());";
                ["SecondAssisteesTarget"] = "local watcher = ...; local target = watcher.GetPartyAssistTarget(2); return (target and target:GetTarget());";
                ["ThirdAssisteesTarget"] =  "local watcher = ...; local target = watcher.GetPartyAssistTarget(3); return (target and target:GetTarget());";
                ["FourthAssisteesTarget"] = "local watcher = ...; local target = watcher.GetPartyAssistTarget(4); return (target and target:GetTarget());";
                ["NextAssisteesTarget"] =   "local watcher = ...; local target = watcher.GetNextAssistTarget(); return (target and target:GetTarget());";
                ["SavedTarget"] =           "local watcher, _, _, number = ...; return watcher.GetSavedTarget(number);";
            };

            -- Allow the user to choose the target category
            top = AddHorizontalLine(top);
            top = AddLabel(top, L:GetText("/SequenceEditor/Target") .. ":", Turbine.UI.Color.Goldenrod);

            local currentItem = info.target;
            info.getTargetFunc = getTargetFuncs[currentItem];
            local prevContext;
            prevContext = L:SetContext("/SequenceEditor/TargetMenu");
            local targs, localTargs = L:GetItems(), {};
            for targ in values(targs) do
                table.insert(localTargs, L:GetText(targ));
            end
            table.sort(localTargs);
            local dropDown;
            top, dropDown = AddDropDown(top, localTargs, L:GetText(currentItem));
            dropDown:SetAlignment(Turbine.UI.ContentAlignment.MiddleLeft);
            dropDown:SetExpandedWidth(math.max(220, slotTabCardWidth - 20));
            dropDown.ItemChanged = function(sender, args)
                for targ in values(targs) do
                    if (L:GetText(targ) == args.Text) then
                        info.target = targ;
                        self:DisplaySlot(self.selectedSlot);
                        self:UpdateBar();
                        break;
                    end
                end
            end
            
            -- If "Raid Member" is chosen, allow the user to select raid member by name
            if (info.target == "RaidMemberByName") then
                info.raidMemberName = info.raidMemberName or "";

                local textBox;
                top, textBox = AddTextBox(top, info.raidMemberName, Turbine.UI.Color.White);
                textBox.TextChanged = function(sender, args)
                    local text = sender:GetText();
                    -- Remove carriage returns
                    local stripped = string.gsub(text, "\n", "");
                    if (stripped ~= text) then
                        sender:SetText(stripped);
                        text = stripped;
                    end
                    info.raidMemberName = text;
                    self:UpdateBar();
                end

                -- Allow the user to easily select the currently-targeted raid member, if not already selected
                local selectedMember = Thurallor.Utils.Watcher.TargetIsPartyMember();
                local selectedMemberName = selectedMember and selectedMember:GetName();
                if (selectedMemberName and (selectedMemberName:lower() ~= info.raidMemberName:lower())) then
                    top = textBox:GetTop();
                    local button1, button2;
                    top, button1 = AddRadioButton(top, selectedMemberName, Turbine.UI.Color.DarkGoldenrod);
                    _,   button2 = AddRadioButton(top, "", Turbine.UI.Color.DarkGoldenrod, true);
                    function button1.Clicked()
                        info.raidMemberName = selectedMemberName;
                        self:UpdateBar();
                    end
                    
                    Thurallor.UI.RadioButton.LinkPeers({button1, button2});
                    textBox:SetZOrder(1);
                    textBox:SetTop(button2:GetTop());
                    textBox:SetLeft(12);
                    textBox:SetWidth(textBox:GetWidth() - 12);
                    function textBox.FocusGained()
                        button2:MouseClick();
                        textBox:TextChanged();
                    end
                    top = top + textBox:GetHeight();
                end
                
            -- If "Saved target" is chosen, allow the user to select saved target number
            elseif (info.target == "SavedTarget") then
                info.number = info.number or 1;
                
                local numbers = {};
                for n = 1, 10 do
                    table.insert(numbers, tostring(n));
                end
                local dropDown;
                top, dropDown = AddDropDown(top, numbers, tostring(info.number));
                dropDown:SetWidth(40);
                dropDown:SetLeft(math.floor((slotTabCardWidth - 20) / 2) - 20);
                dropDown.ItemChanged = function(sender, args)
                    info.number = tonumber(args.Text);
                    self:UpdateBar();
                end
            end

            -- Evaluate the target selection and display the name of the targeted entity
            top, self.targetLabel = AddLabel(top, "", Turbine.UI.Color.Goldenrod);
            self.targetLabel.Update = function(obj)
                local entity = loadstring(info.getTargetFunc)(self.manager.watcher, self.manager.player, info.raidMemberName, info.number);
                if (entity) then
                    obj:SetText("(" .. entity:GetName() .. ")");
                    obj:SetForeColor(Turbine.UI.Color.Lime);
                else
                    obj:SetText(L:GetText("/SequenceEditor/NoTarget"));
                    obj:SetForeColor(Turbine.UI.Color.Red);
                end
            end
            self.targetLabel:SetWantsUpdates(true);
        end
        
        top = AddHorizontalLine(top);
        top = AddLabel(top, L:GetText("/SequenceEditor/AdvanceToNextSlot") .. ":", Turbine.UI.Color.Goldenrod);

    elseif (info.class == "Turbine.UI.Control") then
        if (info.type == "Delay") then
            
            -- Allow the user to specify the delay period
            top = AddHorizontalLine(top);
            top = AddLabel(top, L:GetText("/SequenceEditor/DelayPeriod") .. ":", Turbine.UI.Color.Goldenrod);
            local textBox;
            _, textBox = AddTextBox(top, info.delay, Turbine.UI.Color.White);
            textBox:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleRight);
            local center = math.floor((slotTabCardWidth - 20) / 2);
            textBox:SetWidth(50);
            textBox:SetLeft(center - 2 - 50);
            top, label = AddLabel(top + 2, L:GetText("/SequenceEditor/Seconds"), Turbine.UI.Color.DarkGoldenrod);
            label:SetLeft(center + 2);
            label:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleLeft);
            textBox.TextChanged = function(sender, args)
                info.delay = tonumber(sender:GetText());
                info.action = "local item, s = ...; item.bar:StartSlotCooldown(s, " .. tostring(info.delay):gsub(",", ".") .. ");";
                self:UpdateBar();
            end
        elseif (info.type == "SaveTarget") then

            -- Allow the user to specify the target number
            top = AddHorizontalLine(top);
            top = AddLabel(top, L:GetText("/SequenceEditor/TargetNumber") .. ":", Turbine.UI.Color.Goldenrod);

            local numbers = {};
            for n = 1, 10 do
                table.insert(numbers, tostring(n));
            end
            local dropDown;
            top, dropDown = AddDropDown(top, numbers, tostring(info.number));
            dropDown:SetWidth(40);
            dropDown:SetLeft(math.floor((slotTabCardWidth - 20) / 2) - 20);
            dropDown.ItemChanged = function(sender, args)
                info.number = tonumber(args.Text);
                info.action = "Thurallor.Utils.Watcher.SaveTarget(" .. info.number .. ");";
                self:UpdateBar();
            end
            
            top = top + 16; -- skip a line
            _, label = AddLabel(top, L:GetText("/SequenceEditor/SaveTargetWarning"), Turbine.UI.Color.Silver);
            label:SetTextAlignment(Turbine.UI.ContentAlignment.LeftCenter);
            label:SetHeight(14 * L:GetText("/SequenceEditor/SaveTargetWarningHeight"));
            top = top + label:GetHeight();
            
        elseif (info.type == "GenerateEvent") then
        
            -- Allow the user to specify the event name
            top = AddHorizontalLine(top);
            top = AddLabel(top, L:GetText("/SequenceEditor/EventName") .. ":", Turbine.UI.Color.Goldenrod);
            local textBox;
            top, textBox = AddTextBox(top, info.eventName, Turbine.UI.Color.White);
            textBox.TextChanged = function(sender, args)
                local text = sender:GetText();
                -- Remove carriage returns
                local stripped = string.gsub(text, "\n", "");
                if (stripped ~= text) then
                    sender:SetText(stripped);
                    text = stripped;
                end
                info.eventName = text;
                self:UpdateBar(true);
            end
        elseif (info.type == "SetUnequipDestination") then

            -- Temporary method to allow users to discover bag slot numbers.
            self.ItemMovedCallback = function(sender, args)
                local str = L:GetText("/ItemMoved");
                str = string.gsub(str, "<source>", tostring(args.OldIndex));
                str = string.gsub(str, "<destination>", tostring(args.NewIndex));
                Puts(str);
            end
            AddCallback(Thurallor.Utils.Watcher, "ItemMoved", self.ItemMovedCallback);
        
            -- Allow the user to specify the desired bag slot
            top = AddHorizontalLine(top);
            top = AddLabel(top, L:GetText("/SequenceEditor/PreferredBagSlot") .. ":", Turbine.UI.Color.Goldenrod);
            
            top, textBox = AddTextBox(top, tostring(info.bagSlot), Turbine.UI.Color.White);
            textBox:SetFont(Turbine.UI.Lotro.Font.Verdana14);            
            textBox.TextChanged = function(sender, args)
                local series = Thurallor.Utils.Series(sender:GetText())
                info.bagSlot = series:tostring();
                self:UpdateBar();
            end
        elseif (string.match(info.type, "^Remove ")) then
        
            -- Allow the user to edit choose the item to be unequipped
            top = AddHorizontalLine(top);
            top = AddLabel(top, L:GetText("/SequenceEditor/EquipmentType") .. ":", Turbine.UI.Color.Goldenrod);

            local currentItem = string.sub(info.type, 8);
            local prevContext = L:SetContext("/SequenceEditor/EquipmentSlots");
            local eqSlots, localEqSlots = L:GetItems(), {};
            for slot in values(eqSlots) do
                table.insert(localEqSlots, L:GetText(slot));
            end
            table.sort(localEqSlots);
            local dropDown;
            top, dropDown = AddDropDown(top, localEqSlots, L:GetText(currentItem));
            dropDown:SetExpandedWidth(math.max(220, slotTabCardWidth - 20));
            dropDown.ItemChanged = function(sender, args)
                for slot in values(eqSlots) do
                    if (L:GetText(slot) == args.Text) then
                        self:DeleteSlot(s);
                        self:CreateRemovalSlot(s, slot, info.bagSlot);
                        break;
                    end
                end
            end
        elseif (info.type == "If") then
    
            -- Allow the user to specify the conditional expression
            top = AddHorizontalLine(top);
            top = AddLabel(top, L:GetText("/SequenceEditor/Condition") .. ":", Turbine.UI.Color.Goldenrod);

            local prevContext = L:SetContext("/SequenceEditor/ConditionTypes");
            local condInfo = {
                ["Always"] =                      { expr = "return true;" };
                ["Never"] =                       { expr = "return false;" };
                ["Random"] =                      {  arg = "x";
                                                    expr = "return (math.random() < (<x> / 100));" };
                ["SkillReady"] =                  {  arg = "skillName";
                                                    expr = "return (Thurallor.Utils.Watcher.SkillReady(<skillName>));" };
                ["SkillNotReady"] =               {  arg = "skillName";
                                                    expr = "return (not Thurallor.Utils.Watcher.SkillReady(<skillName>));" };
                ["SkillUsable"] =                 {  arg = "skillName";
                                                    expr = "return (Thurallor.Utils.Watcher.SkillUsable(<skillName>));" };
                ["SkillNotUsable"] =              {  arg = "skillName";
                                                    expr = "return (not Thurallor.Utils.Watcher.SkillUsable(<skillName>));" };
                ["SkillTrained"] =                {  arg = "skillName";
                                                    expr = "return (Thurallor.Utils.Watcher.SkillTrained(<skillName>));" };
                ["SkillNotTrained"] =             {  arg = "skillName";
                                                    expr = "return (not Thurallor.Utils.Watcher.SkillTrained(<skillName>));" };
                ["PlayerEffectDisease"] =         { expr = "return Thurallor.Utils.Watcher.PlayerHasEffectCategory(Turbine.Gameplay.EffectCategory.Disease);" };
                ["PlayerEffectDiseaseCurable"] =  { expr = "return Thurallor.Utils.Watcher.PlayerHasEffectCategory(Turbine.Gameplay.EffectCategory.Disease, true);" };
                ["PlayerEffectFear"] =            { expr = "return Thurallor.Utils.Watcher.PlayerHasEffectCategory(Turbine.Gameplay.EffectCategory.Fear);" };
                ["PlayerEffectFearCurable"] =     { expr = "return Thurallor.Utils.Watcher.PlayerHasEffectCategory(Turbine.Gameplay.EffectCategory.Fear, true);" };
                ["PlayerEffectPoison"] =          { expr = "return Thurallor.Utils.Watcher.PlayerHasEffectCategory(Turbine.Gameplay.EffectCategory.Poison);" };
                ["PlayerEffectPoisonCurable"] =   { expr = "return Thurallor.Utils.Watcher.PlayerHasEffectCategory(Turbine.Gameplay.EffectCategory.Poison, true);" };
                ["PlayerEffectWound"] =           { expr = "return Thurallor.Utils.Watcher.PlayerHasEffectCategory(Turbine.Gameplay.EffectCategory.Wound);" };
                ["PlayerEffectWoundCurable"] =    { expr = "return Thurallor.Utils.Watcher.PlayerHasEffectCategory(Turbine.Gameplay.EffectCategory.Wound, true);" };
                ["PlayerEffectOther"] =           {  arg = "effect";
                                                    expr = "return Thurallor.Utils.Watcher.PlayerHasEffect(<effect>);" };
                ["NotPlayerEffectOther"] =        {  arg = "effect";
                                                    expr = "return (not Thurallor.Utils.Watcher.PlayerHasEffect(<effect>));" };
                ["TargetEffectDisease"] =         { expr = "return Thurallor.Utils.Watcher.TargetHasEffectCategory(Turbine.Gameplay.EffectCategory.Disease);" };
                ["TargetEffectFear"] =            { expr = "return Thurallor.Utils.Watcher.TargetHasEffectCategory(Turbine.Gameplay.EffectCategory.Fear);" };
                ["TargetEffectPoison"] =          { expr = "return Thurallor.Utils.Watcher.TargetHasEffectCategory(Turbine.Gameplay.EffectCategory.Poison);" };
                ["TargetEffectWound"] =           { expr = "return Thurallor.Utils.Watcher.TargetHasEffectCategory(Turbine.Gameplay.EffectCategory.Wound);" };
                ["TargetEffectOther"] =           {  arg = "effect";
                                                    expr = "return Thurallor.Utils.Watcher.TargetHasEffect(<effect>);" };
                ["NotTargetEffectOther"] =        {  arg = "effect";
                                                    expr = "return (not Thurallor.Utils.Watcher.TargetHasEffect(<effect>));" };
                ["PlayerInCombat"] =              { expr = "local _, player = ...; return player:IsInCombat();" };
                ["PlayerOutOfCombat"] =           { expr = "local _, player = ...; return not player:IsInCombat();" };
                ["PlayerMorale < x"] =            {  arg = "x";
                                                    expr = "local _, player = ...; return (player:GetMorale() < <x>)" };
                ["PlayerMorale < x%"] =           {  arg = "x";
                                                    expr = "local _, player = ...; return (player:GetMorale() < player:GetBaseMaxMorale() * <x> / 100)" };
                ["PlayerMorale > x"] =            {  arg = "x";
                                                    expr = "local _, player = ...; return (player:GetMorale() > <x>)" };
                ["PlayerMorale > x%"] =           {  arg = "x";
                                                    expr = "local _, player = ...; return (player:GetMorale() > player:GetBaseMaxMorale() * <x> / 100)" };
                ["TargetIsFellow"] =              { expr = "local _, player = ...; return Thurallor.Utils.Watcher.TargetIsPartyMember();" };
                ["TargetNotFellow"] =             { expr = "local _, player = ...; return not Thurallor.Utils.Watcher.TargetIsPartyMember();" };
                ["TargetMorale < x"] =            {  arg = "x";
                                                    expr = "local _, player = ...; local target = player:GetTarget(); return (target and target.GetMorale and (target:GetMorale() < <x>))" };
                ["TargetMorale < x%"] =           {  arg = "x";
                                                    expr = "local _, player = ...; local target = player:GetTarget(); return (target and target.GetMorale and (target:GetMorale() < target:GetBaseMaxMorale() * <x> / 100))" };
                ["TargetMorale > x"] =            {  arg = "x";
                                                    expr = "local _, player = ...; local target = player:GetTarget(); return (target and target.GetMorale and (target:GetMorale() > <x>))" };
                ["TargetMorale > x%"] =           {  arg = "x";
                                                    expr = "local _, player = ...; local target = player:GetTarget(); return (target and target.GetMorale and (target:GetMorale() > target:GetBaseMaxMorale() * <x> / 100))" };
                ["PlayerPower < x"] =             {  arg = "x";
                                                    expr = "local _, player = ...; return (player:GetPower() < <x>)" };
                ["PlayerPower < x%"] =            {  arg = "x";
                                                    expr = "local _, player = ...; return (player:GetPower() < player:GetBaseMaxPower() * <x> / 100)" };
                ["PlayerPower > x"] =             {  arg = "x";
                                                    expr = "local _, player = ...; return (player:GetPower() > <x>)" };
                ["PlayerPower > x%"] =            {  arg = "x";
                                                    expr = "local _, player = ...; return (player:GetPower() > player:GetBaseMaxPower() * <x> / 100)" };
                ["TargetPower < x"] =             {  arg = "x";
                                                    expr = "local _, player = ...; local target = player:GetTarget(); return (target and target.GetPower and (target:GetPower() < <x>))" };
                ["TargetPower < x%"] =            {  arg = "x";
                                                    expr = "local _, player = ...; local target = player:GetTarget(); return (target and target.GetPower and (target:GetPower() < target:GetBaseMaxPower() * <x> / 100))" };
                ["TargetPower > x"] =             {  arg = "x";
                                                    expr = "local _, player = ...; local target = player:GetTarget(); return (target and target.GetPower and (target:GetPower() > <x>))" };
                ["TargetPower > x%"] =            {  arg = "x";
                                                    expr = "local _, player = ...; local target = player:GetTarget(); return (target and target.GetPower and (target:GetPower() > target:GetBaseMaxPower() * <x> / 100))" };
                ["ChampionPlayerFervor < x"] =    {  arg = "x";
                                                    expr = "local _, player = ...; local attribs = player:GetClassAttributes(); return attribs.GetFervor and (attribs:GetFervor() < <x>);" };
                ["ChampionPlayerFervor > x"] =    {  arg = "x";
                                                    expr = "local _, player = ...; local attribs = player:GetClassAttributes(); return attribs.GetFervor and (attribs:GetFervor() > <x>);" };
                ["BeorningPlayerWrath < x%"] =    {  arg = "x";
                                                    expr = "local _, player = ...; local attribs = player:GetClassAttributes(); return attribs.GetWrath and (attribs:GetWrath() < <x>);" };
                ["BeorningPlayerWrath > x%"] =    {  arg = "x";
                                                    expr = "local _, player = ...; local attribs = player:GetClassAttributes(); return attribs.GetWrath and (attribs:GetWrath() > <x>);" };
                ["BeorningInBearForm"] =          { expr = "local _, player = ...; local attribs = player:GetClassAttributes(); return attribs.IsInBearForm and attribs:IsInBearForm();" };
                ["BeorningNotInBearForm"] =       { expr = "local _, player = ...; local attribs = player:GetClassAttributes(); return attribs.IsInBearForm and (not attribs:IsInBearForm());" };
                ["HunterFocus < x"] =             {  arg = "x";
                                                    expr = "local _, player = ...; local attribs = player:GetClassAttributes(); return attribs.GetFocus and (attribs:GetFocus() < <x>);" };
                ["HunterFocus > x"] =             {  arg = "x";
                                                    expr = "local _, player = ...; local attribs = player:GetClassAttributes(); return attribs.GetFocus and (attribs:GetFocus() > <x>);" };
                ["BrawlerMettle < x"] =           {  arg = "x";
                                                    expr = "return (Thurallor.Utils.Watcher.GetPlayerMettle() < <x>);" };
                ["BrawlerMettle > x"] =           {  arg = "x";
                                                    expr = "return (Thurallor.Utils.Watcher.GetPlayerMettle() > <x>);" };
                ["RunekeeperAttunement < x"] =    {  arg = "x";
                                                    expr = "local _, player = ...; local attribs = player:GetClassAttributes(); return attribs.GetAttunement and (attribs:GetAttunement() < <x>);" };
                ["RunekeeperAttunement > x"] =    {  arg = "x";
                                                    expr = "local _, player = ...; local attribs = player:GetClassAttributes(); return attribs.GetAttunement and (attribs:GetAttunement() > <x>);" };
                ["RunekeeperCharged"] =           { expr = "local _, player = ...; local attribs = player:GetClassAttributes(); return attribs.IsCharged and attribs:IsCharged();" };
                ["RunekeeperNotCharged"] =        { expr = "local _, player = ...; local attribs = player:GetClassAttributes(); return attribs.IsCharged and (not attribs:IsCharged());" };
                ["ItemEquipped"] =                {  arg = { "eqItemName", "eqSlot" };
                                                    expr = "return IsEquipped(<eqItemName>, <eqSlot>);" };
                ["ItemNotEquipped"] =             {  arg = { "eqItemName", "eqSlot" };
                                                    expr = "return (not IsEquipped(<eqItemName>, <eqSlot>));" };
                ["ItemQuantity > x"] =            {  arg = { "stackItemName", "x" };
                                                    expr = "return (Thurallor.Utils.Watcher.GetItemQuantity(<stackItemName>) > <x>);" };
                ["ItemQuantity < x"] =            {  arg = { "stackItemName", "x" };
                                                    expr = "return (Thurallor.Utils.Watcher.GetItemQuantity(<stackItemName>) < <x>);" };
                ["StanceSelected"] =              {  arg = "stance";
                                                    expr = "return (Thurallor.Utils.Watcher.GetStance() == <stance>);" };
                ["LuaScript"] =                   {  arg = "script";
                                                    expr = "<script>" };
            };
            local condNames = {};
            for k in keys(condInfo) do
                table.insert(condNames, L:GetText(k));
            end
            table.sort(condNames);

            local function SwitchArgs(arg)
                if (not info.condArgs) then
                    info.condArgs = {};
                end
                if (not arg) then
                    info.condArgs = nil;
                elseif ((arg == "x") and not info.condArgs.x) then
                    info.condArgs = { x = 0 };
                elseif ((arg == "skillName") and not info.condArgs.skillName) then
                    info.condArgs = { skillName = "nil" };
                elseif ((arg == "skill") and not info.condArgs.skill) then
                    info.condArgs = { skill = 0 };
                elseif ((arg == "effect") and not info.condArgs.effect) then
                    info.condArgs = { effect = "nil" };
                elseif ((arg == "stance") and not info.condArgs.stance) then
                    info.condArgs = { stance = 0 };
                elseif ((arg == "script") and not info.condArgs.script) then
                    if (info.condExpr) then
                        local script = info.condExpr;
                        if (info.condArgs) then
                            for name, value in pairs(info.condArgs) do
                                script = string.gsub(script, "<" .. name .. ">", tostring(value));
                            end
                        end
                        info.condArgs = { script = script };
                    else
                        info.condArgs = { script = "return true;" };
                    end
                elseif (type(arg) == "table") then
                    -- Multiple args
                    local prevArgs = info.condArgs;
                    info.condArgs = {};
                    for argName in values(arg) do
                        if (prevArgs[argName]) then
                            info.condArgs[argName] = prevArgs[argName];
                        end
                        if ((argName == "eqItemName") and not info.condArgs.eqItemName) then
                            info.condArgs.eqItemName = "nil";
                        elseif ((argName == "stackItemName") and not info.condArgs.stackItemName) then
                            info.condArgs.stackItemName = "nil";
                        elseif ((argName == "eqSlot") and not info.condArgs.eqSlot) then
                            info.condArgs.eqSlot = "nil";
                        elseif ((argName == "x") and not info.condArgs.x) then
                            info.condArgs.x  = 0;
                        end
                    end
                end
                info.condExpr = condInfo[info.condName].expr;
            end
            
            local dropDown;
            top, dropDown = AddDropDown(top, condNames, L:GetText(info.condName));
            dropDown:SetExpandedWidth(math.max(300, slotTabCardWidth - 20));
            dropDown:SetAlignment(Turbine.UI.ContentAlignment.MiddleLeft);
            dropDown.ItemChanged = function(sender, args)
                local prevContext = L:SetContext("/SequenceEditor/ConditionTypes");
                info.condName = L:GetItem(args.Text);
                L:SetContext(prevContext);
                local arg = condInfo[info.condName].arg;
                SwitchArgs(arg);
                self:UpdateBar();
                self:DisplaySlot(self.selectedSlot);
            end
            
            SwitchArgs(condInfo[info.condName].arg);
            L:SetContext(prevContext);
            
            -- Display additional arguments depending on the current condition type
            if (info.condArgs) then
                if (info.condArgs.stackItemName) then
                    local function GetStackableItems()
                        local hash = {};
                        local backpack = self.manager.player:GetBackpack();
                        for i = 1, backpack:GetSize(), 1 do
                            local item = backpack:GetItem(i);
                            if (item and (item:GetMaxStackSize() > 1)) then
                                hash[item:GetName()] = true;
                            end
                        end
                        local items = {};
                        for key in sorted_keys(hash) do
                            table.insert(items, key);
                        end
                        return items;
                    end
                    local stackItemNames = GetStackableItems();
                    local dropDown;
                    top, dropDown = AddDropDown(top + 2, stackItemNames);
                    dropDown:SetExpandedWidth(math.max(400, slotTabCardWidth - 20));
                    dropDown.ItemChanged = function(sender, args)
                        info.condArgs.stackItemName = "\"" .. args.Text .. "\"";
                        self:UpdateBar();
                    end
                    dropDown:SetParent(self.slotProperties);
                    local name = info.condArgs.stackItemName;
                    if (name == "nil") then
                        name = stackItemNames[1];
                        if (name) then
                            dropDown:ItemChanged({Text = name});
                        end
                    else
                        dropDown:SetText(string.sub(name, 2, -2));
                    end
                end
                if (info.condArgs.x) then
                    local _, xLabel = AddLabel(top + 3, "", Turbine.UI.Color.DarkGoldenrod);
                    xLabel:SetWidth(30);
                    xLabel:SetFont(Turbine.UI.Lotro.Font.Verdana16);
                    xLabel:SetText("x =");
                    
                    local xField;
                    top, xField = AddTextBox(top + 2, "", Turbine.UI.Color.White);
                    xField:SetLeft(30);
                    xField:SetWidth(slotTabCardWidth - 50);
                    xField.TextChanged = function(sender)
                        info.condArgs.x = tonumber(sender:GetText());
                        if (not info.condArgs.x) then
                            info.condArgs.x = 0;
                        end
                        self:UpdateBar();
                    end
                    xField:SetText(info.condArgs.x);
                    xLabel:SetParent(self.slotProperties);
                    xField:SetText(tostring(info.condArgs.x));
                    xField:SetParent(self.slotProperties);
                end
                if (info.condArgs.skillName) then
                    local trainedSkills = Thurallor.Utils.Watcher.GetTrainedSkillsInfo();
                    local skills = Thurallor.Utils.Watcher.GetSkillsInfo(true);
                    local displayNames = {};
                    local untrained = L:GetText("/SequenceEditor/Untrained");
                    for n = 1, #skills.names, 1 do
                        local name = skills.names[n];
                        local displayName = name;
                        local altNames = Turbine.Gameplay.Skill.GetAltNames(name);
                        if (altNames) then
                            displayName = displayName .. " / " .. altNames;
                        end
                        if (not trainedSkills.byName[name]) then
                            displayName = displayName .. " (" .. untrained .. ")";
                        end
                        table.insert(displayNames, displayName);
                    end
                    local dropDown;
                    top, dropDown = AddDropDown(top + 2, displayNames);
                    dropDown:SetExpandedWidth(math.max(300, slotTabCardWidth - 20));
                    dropDown.ItemChanged = function(sender, args)
                        local text = args.Text:gsub(" %(" .. L:GetText("/SequenceEditor/Untrained") .. "%)$", "");
                        text = text:gsub(" / .*$", "");
                        dropDown:SetText(text);
                        info.condArgs.skillName = "\"" .. text .. "\"";
                        self:UpdateBar();
                    end
                    dropDown:SetParent(self.slotProperties);
                    local skillName = info.condArgs.skillName;
                    if (skillName == "nil") then
                        skillName = skills.names[1]; -- first one always will be a trained skill
                        dropDown:ItemChanged({Text = skillName});
                    else
                        skillName = string.gsub(skillName, "\"", "")
                        dropDown:SetText(skillName);
                    end
                end
                if (info.condArgs.effect) then
                    local effectNames = Thurallor.Utils.Watcher.GetKnownEffectNames();
                    if (#effectNames >= 1) then
                        table.insert(effectNames, 1, L:GetText("/SequenceEditor/ConditionTypes/OtherEffect"));
                        local dropDown;
                        top, dropDown = AddDropDown(top + 2, effectNames);
                        dropDown:SetExpandedWidth(math.max(300, slotTabCardWidth - 20));
                        function dropDown.ItemChanged(ctl, args)
                            local effectNames = Thurallor.Utils.Watcher.GetKnownEffectNames();
                            local other = L:GetText("/SequenceEditor/ConditionTypes/OtherEffect");
                            table.insert(effectNames, 1, other);
                            if (args.Text == other) then
                                local ok = L:GetText("/ExportWindow/OK");
                                local window = Alert(other,
                                    L:GetText("/SequenceEditor/ConditionTypes/OtherEffectHelp"),
                                    ok);
                                CenterWindow(window);
                                window.label:SetFont(Turbine.UI.Lotro.Font.Verdana14);
                                window.label:SetText(window.label:GetText());
                                window.buttons[ok].Click = function()
                                    window:Close();
                                end
                                dropDown:SetText(string.gsub(info.condArgs.effect, "\"", ""));
                            elseif (not args.Text) then
                                return;
                            else
                                info.condArgs.effect = "\"" .. args.Text .. "\"";
                                self:UpdateBar();
                            end
                        end
                        dropDown:SetParent(self.slotProperties);
                        local effect = info.condArgs.effect;
                        if (effect == "nil") then
                            effect = effectNames[2];
                            dropDown:SetText(effect);
                            dropDown:ItemChanged({Text = effect});
                        else
                            effect = string.gsub(effect, "\"", "")
                            dropDown:SetText(effect);
                        end
                    end
                end
                if (info.condArgs.stance) then
                    local stanceNames = L:GetSortedTexts("/SequenceEditor/Stance");
                    local dropDown;
                    top, dropDown = AddDropDown(top + 2, stanceNames);
                    dropDown:SetExpandedWidth(math.max(500, slotTabCardWidth - 20));
                    function dropDown.ItemChanged(ctl, args)
                        local prevContext = L:SetContext("/SequenceEditor/Stance");
                        local stance = L:GetItem(args.Text);
                        info.condArgs.stance = tonumber(stance);
                        self:UpdateBar();
                    end
                    dropDown:SetParent(self.slotProperties);
                    local stance = info.condArgs.stance;
                    dropDown:SetText(L:GetText("/SequenceEditor/Stance/" .. tostring(stance)));
                end
                if (info.condArgs.script) then
                    top, self.scriptBox = AddTextBox(top + 2, info.condArgs.script, Turbine.UI.Color.White, true, self.settings.sequenceEditor.scriptBoxSize);
                    self.scriptBox:SetZOrder(1);
                    self.scriptBox:SetMultiline(true);
                    self.scriptBox:SetTextAlignment(Turbine.UI.ContentAlignment.TopLeft);
                    self.scriptBox:SetFont(Turbine.UI.Lotro.Font.Verdana12);
                    self.scriptBox:SetText(info.condArgs.script);
                    self.scriptBox:SetWantsUpdates(true);
                    function self.scriptBox.Resized()
                        self.settings.sequenceEditor.scriptBoxSize = {self.scriptBox:GetSize()};
                        self:SaveSettings();
                        self:DisplaySlot(self.selectedSlot);
                    end
                    self.scriptBox.Update = function(sender)
                        local script = sender:GetText();
                        if (script ~= sender.prevText) then
                            local success, errorMsg = loadstring(script, "Slot " .. tostring(s));
                            if (success) then
                                self.scriptBox:SetBackColor(Turbine.UI.Color.Black);
                                info.condArgs.script = script;
                                self:UpdateBar();
                            else
                                self.scriptBox:SetBackColor(Turbine.UI.Color.Red);
                            end
                        end
                        sender.prevText = script;
                    end
                end
                if (info.condArgs.eqItemName) then
                    local function GetEquippableItems()
                        local hash = { [L:GetText("/SequenceEditor/AnyEqItem")] = true };
                        for container in values({ self.manager.player:GetBackpack(), self.manager.player:GetEquipment() }) do
                            for i = 1, container:GetSize(), 1 do
                                local item = container:GetItem(i);
                                if (item and (item:GetMaxStackSize() == 1)) then
                                    hash[item:GetName()] = true;
                                end
                            end
                        end
                        local items = {};
                        for key in sorted_keys(hash) do
                            table.insert(items, key);
                        end
                        return items;
                    end
                    local eqItemNames = GetEquippableItems();
                    local anyItem = L:GetText("/SequenceEditor/AnyEqItem");
                    local dropDown;
                    top, dropDown = AddDropDown(top + 2, eqItemNames);
                    dropDown:SetExpandedWidth(math.max(400, slotTabCardWidth - 20));
                    dropDown.ItemChanged = function(sender, args)
                        local anyItem = L:GetText("/SequenceEditor/AnyEqItem");
                        if (args.Text == anyItem) then
                            info.condArgs.eqItemName = "nil";
                        else
                            info.condArgs.eqItemName = "\"" .. args.Text .. "\"";
                        end
                        self:UpdateBar();
                    end
                    dropDown:SetParent(self.slotProperties);
                    if (info.condArgs.eqItemName == "nil") then
                        dropDown:SetText(anyItem);
                    else
                        local name = string.sub(info.condArgs.eqItemName, 2, -2);
                        dropDown:SetText(name);
                    end
                end
                if (info.condArgs.eqSlot) then
                    local prevContext = L:SetContext("/SequenceEditor/EquipmentSlots");
                    local eqSlotNames, localEqSlotNames, eqSlotValues = L:GetItems(), {}, {};
                    for slot in values(eqSlotNames) do
                        local text = L:GetText(slot);
                        table.insert(localEqSlotNames, text);
                        eqSlotValues[text] = tostring(Turbine.Gameplay.Equipment[slot]);
                    end
                    local anySlot = L:GetText("/SequenceEditor/AnyEqSlot");
                    eqSlotValues[anySlot] = "nil";
                    table.insert(localEqSlotNames, anySlot);
                    table.sort(localEqSlotNames);
                    local dropDown;
                    top, dropDown = AddDropDown(top + 2, localEqSlotNames);
                    dropDown.ItemChanged = function(sender, args)
                        info.condArgs.eqSlot = eqSlotValues[args.Text];
                        self:UpdateBar();
                    end                    
                    dropDown:SetParent(self.slotProperties);
                    if (info.condArgs.eqSlot == "nil") then
                        dropDown:SetText(anySlot);
                    else
                        local slotName = Search(Turbine.Gameplay.Equipment, tonumber(info.condArgs.eqSlot));
                        dropDown:SetText(L:GetText(slotName));
                    end
                    L:SetContext(prevContext);
                end
            end

            -- Evaluate the condition and display the current value
            local function GetValueStr()
                local sequence = self.bar.sequence;
                local slot = sequence:GetSlot(sequence:GetNewIndex(s));
                if (slot and slot.condFunc) then
                    local text;
                    local success, value = pcall(slot.condFunc, slot, self.manager.player);
                    if (success) then
                        if (value) then
                            text = L:GetText("/SequenceEditor/True");
                        else
                            text = L:GetText("/SequenceEditor/False");
                        end
                    else
                        text = "Error: " .. value;
                    end
                    return "(" .. text .. ")";
                end
            end
            if (info.condArgs and info.condArgs.script) then
                top, self.playButton = AddButton(top, 40, "►", Turbine.UI.Lotro.Font.Arial12);
                top, label = AddLabel(top, "", Turbine.UI.Color.Goldenrod);
                AddCallback(self.playButton, "Click", function()
                    label:SetText(GetValueStr());
                end);
                AddCallback(self.playButton, "MouseLeave", function()
                    label:SetText("");
                end);
            else
                top, self.resultLabel = AddLabel(top, "", Turbine.UI.Color.Goldenrod);
                self.resultLabel.Update = function(obj)
                    obj:SetText(GetValueStr());
                end
                self.resultLabel:SetWantsUpdates(true);
            end

        local isTargetEffectCondition = {
            ["TargetEffectDisease"] = true;
            ["TargetEffectFear"] = true;
            ["TargetEffectPoison"] = true;
            ["TargetEffectWound"] = true; 
            ["TargetEffectOther"] = true;
            ["NotTargetEffectOther"] = true;
        };
        if (isTargetEffectCondition[info.condName]) then
            top = top + 16; -- skip a line
            _, label = AddLabel(top, L:GetText("/SequenceEditor/TargetEffectWarning"), Turbine.UI.Color.Silver);
            label:SetTextAlignment(Turbine.UI.ContentAlignment.LeftCenter);
            label:SetHeight(14 * L:GetNumber("/SequenceEditor/TargetEffectWarningHeight"));
            top = top + label:GetHeight();
        end

        elseif (info.type == "LuaScript") then

            -- Display script editor box
            top, self.scriptBox = AddTextBox(top + 2, info.action, Turbine.UI.Color.White, true, self.settings.sequenceEditor.scriptBoxSize);
            self.scriptBox:SetZOrder(1);
            self.scriptBox:SetMultiline(true);
            self.scriptBox:SetTextAlignment(Turbine.UI.ContentAlignment.TopLeft);
            self.scriptBox:SetFont(Turbine.UI.Lotro.Font.Verdana12);
            self.scriptBox:SetText(info.action);
            self.scriptBox:SetWantsUpdates(true);
            function self.scriptBox.Resized()
                self.settings.sequenceEditor.scriptBoxSize = {self.scriptBox:GetSize()};
                self:SaveSettings();
                self:DisplaySlot(self.selectedSlot);
            end
            self.scriptBox.Update = function(sender)
                local script = sender:GetText();
                if (script ~= sender.prevText) then
                    local success, errorMsg = loadstring(script, "Slot " .. tostring(s));
                    if (success) then
                        self.scriptBox:SetBackColor(Turbine.UI.Color.Black);
                        self.playButton:SetEnabled(true);
                        info.action = script;
                        self:UpdateBar();
                    else
                        self.scriptBox:SetBackColor(Turbine.UI.Color.Red);
                        self.playButton:SetEnabled(false);
                    end
                end
                sender.prevText = script;
            end

            -- Create a button for executing the script
            top, self.playButton = AddButton(top, 40, "►", Turbine.UI.Lotro.Font.Arial12);
            top, label = AddLabel(top, "", Turbine.UI.Color.Goldenrod);
            AddCallback(self.playButton, "Click", function()
                local script = self.scriptBox:GetText();
                local success, result = pcall(loadstring("setfenv(1, _G);" .. script, "Slot " .. tostring(s)));
                if (success) then
                    if (type(result) == "string") then
                        result = "\"" .. result .. "\"";
                    else
                        result = tostring(result); -- or "nil";
                    end
                    label:SetText("returned: " .. result);
                else
                    label:SetText("error: " .. result);
                end
            end);
            AddCallback(self.playButton, "MouseLeave", function()
                label:SetText("");
            end);

        elseif (info.type == "Include") then
            top = AddHorizontalLine(top);
            top = AddLabel(top, L:GetText("/SequenceEditor/OtherSequence") .. ":", Turbine.UI.Color.Goldenrod);
            local function GetObjectTree()
                -- Get a list of all bar IDs.
                local objectIDs = self.manager:FindAllDescendentIDs(nil, true, false);
                
                -- Including self doesn't make sense, so prevent that.
                local foundSelf;
                for o = 1, #objectIDs, 1 do
                    local object = self.manager:GetObject(objectIDs[o]);
                    if (object == self.bar) then
                        foundSelf = o;
                    end
                end
                if (foundSelf) then
                    table.remove(objectIDs, foundSelf);
                end
                
                -- Add nonempty groups.
                local groupIDs = {};
                for o = 1, #objectIDs, 1 do
                    local objectID = objectIDs[o];
                    local object = self.manager:GetObject(objectID);
                    while (object.parent) do
                        object = object.parent;
                        objectID = object:GetID();
                        if (objectID) then
                            groupIDs[objectID] = 1;
                        end
                    end
                end
                for groupID in keys(groupIDs) do
                    table.insert(objectIDs, groupID);
                end
                
                -- Create item list for PullDownMenu.
                local items = {};
                for o = 1, #objectIDs, 1 do
                    local objectID = objectIDs[o];
                    local object = self.manager:GetObject(objectID);
                    local parentID = nil;
                    if (object.parent) then
                        parentID = object.parent:GetID();
                    end
                    table.insert(items, { objectID, object:GetName(), parentID });
                end
                return items;
            end
            local pullDown;
            top, pullDown = AddPullDown(top, GetObjectTree(), info.include);
            pullDown.SelectionChanged = function(sender, id)
                info.include = id;
                self:UpdateBar();
                self:SelectSlot(s);
            end
            AddCallback(pullDown, "MouseClick", function(sender, args)
                -- Right-click displays the bar menu.
                if (args.Button == Turbine.UI.MouseButton.Right) then
                    if (info.include) then
                        local bar = self.manager.objects[info.include];
                        if (bar) then
                            bar:ShowSettingsMenu();
                        end
                    end
                end                
            end);

            -- Add the "unlink" button if a sequence is selected.
            if (info.include) then
                local button;
                local text, width = L:GetText("/SequenceEditor/Unlink"), L:GetNumber("/SequenceEditor/UnlinkButtonWidth");
                top, button = AddButton(top, width, text);
                AddCallback(button, "Click", function()
                    self:UnlinkIncludedSequence(s);
                end);
            end
        end

        -- Allow the user to specify whether activation is click-based or automatic.
        if (info.type ~= "Include") then
            top = AddHorizontalLine(top);
            top = AddLabel(top, L:GetText("/SequenceEditor/Activation") .. ":", Turbine.UI.Color.Goldenrod);
            local button1, button2;
            top, button1 = AddRadioButton(top, L:GetText("/SequenceEditor/WithLeftClick"), Turbine.UI.Color.DarkGoldenrod, (not info.automatic));
            top, button2 = AddRadioButton(top, L:GetText("/SequenceEditor/Automatic"), Turbine.UI.Color.DarkGoldenrod, info.automatic);
            Thurallor.UI.RadioButton.LinkPeers({button1, button2});
            button1.Clicked = function()
                info.automatic = nil;
                self:UpdateBar();
            end
            button2.Clicked = function()
                info.automatic = true;
                self:UpdateBar();
            end
        end
    end

    -- Allow the user to specify that advancement should not occur until there is a target
    if ((info.type == Turbine.UI.Lotro.ShortcutType.Skill) or (info.type == "SelectTarget")) then
        local checkbox;
        top, checkbox = AddCheckBox(top, L:GetText("/SequenceEditor/RequireTarget"), Turbine.UI.Color.DarkGoldenrod, info.requireTarget);
        
        checkbox.CheckedChanged = function(chkbx)
            if (chkbx:IsChecked()) then
                info.requireTarget = true;
            else
                info.requireTarget = nil;
            end
            self:UpdateBar();
        end
    end

    -- Allow the user to modify slot appearance (text, icon)
    local button;
    top, button = AddSection(top, L:GetText("/SequenceEditor/Appearance"), Turbine.UI.Color.Goldenrod, self.appearanceExpanded);
    function button.ExpandedChanged(sender, expanded)
        self.appearanceExpanded = expanded;
        self:DisplaySlot(self.selectedSlot);
    end

    if (self.appearanceExpanded) then

        -- Allow the user to optionally overlay some text
        top = top + 16; -- skip a line
        top = AddLabel(top, L:GetText("/SequenceEditor/TextOverlay") .. ":", Turbine.UI.Color.Goldenrod);
        local textBox;
        top, textBox = AddTextBox(top, info.textOverlay, Turbine.UI.Color.White);
        textBox:SetFont(Turbine.UI.Lotro.Font.Verdana12);
        textBox.TextChanged = function(sender, args)
            local text = sender:GetText();
            if (text == "") then
                text = nil;
            end
            info.textOverlay = text;
            UpdateIcon();
            self:UpdateBar();
        end
        
        -- Allow the user to optionally change the icon
        top = top + 16; -- skip a line
        top = AddLabel(top, L:GetText("/SequenceEditor/ChangeIcon") .. ":", Turbine.UI.Color.Goldenrod);
        local button1, button2, slider2, button3, slider3, button4, textbox;
        top, button1 = AddRadioButton(top, L:GetText("/SequenceEditor/DefaultIcon"), Turbine.UI.Color.DarkGoldenrod);
        top, button2 = AddRadioButton(top, L:GetText("/SequenceEditor/BlankIcon"), Turbine.UI.Color.DarkGoldenrod);
        top, slider2 = AddSlider(top, 1, #resources.BlankIcons, 1);
        slider2:SetLeft(12);
        slider2:SetWidth(slider2:GetWidth() - 12);
        top, button3 = AddRadioButton(top, L:GetText("/SequenceEditor/SkillIcon"), Turbine.UI.Color.DarkGoldenrod);
        local skillsInfo = Thurallor.Utils.Watcher.GetSkillsInfo();
        local gambitsInfo = Thurallor.Utils.Watcher.GetGambitsInfo();
        self.skillIcons = {};
        for id in sorted_keys(skillsInfo.byIcon) do
            table.insert(self.skillIcons, id);
        end
        for id in sorted_keys(gambitsInfo.byIcon) do
            table.insert(self.skillIcons, id);
        end
        top, slider3 = AddSlider(top, 1, #self.skillIcons, 1);
        slider3:SetLeft(12);
        slider3:SetWidth(slider3:GetWidth() - 12);
        top, button4 = AddRadioButton(top, L:GetText("/SequenceEditor/SpecificIconID"), Turbine.UI.Color.DarkGoldenrod);
        top, textBox = AddTextBox(top, "", Turbine.UI.Color.White);
        textBox:SetFont(Turbine.UI.Lotro.Font.Verdana12);
        textBox:SetLeft(12);
        textBox:SetWidth(textBox:GetWidth() - 12);
        if (info.altIcon == nil) then
            button1:SetChecked(true);
        else
            local blankIcon = Search(resources.BlankIcons, info.altIcon);
            if (blankIcon) then
                button2:SetChecked(true);
                slider2:SetValue(blankIcon);
            else
                local skillIcon = Search(self.skillIcons, info.altIcon);
                if (skillIcon) then
                    button3:SetChecked(true);
                    slider3:SetValue(skillIcon);
                else
                    button4:SetChecked(true);
                end
            end
        end
        local function UpdateIconID()
            local id = info.altIcon;
            if (id == nil) then
                id = info.background;
            end
            if (type(id) == "number") then
                textBox:SetText(string.format("0x%8.8X", id));
            elseif (type(id) == "string") then
                textBox:SetText(id);
            else
                -- Get icon ID from quickslot
                
            end
        end
        UpdateIconID();
        Thurallor.UI.RadioButton.LinkPeers({button1, button2, button3, button4});
        function button1.Clicked()
            info.altIcon = nil;
            UpdateIcon();
            UpdateIconID();
            self:UpdateBar();
        end
        function button2.Clicked()
            slider2:ValueChanged();
        end
        function button3.Clicked()
            slider3:ValueChanged();
        end
        function button4.Clicked()
            textBox:TextChanged();
        end
        slider2.ValueChanged = function(sender)
            if (not button2:IsChecked()) then
                button2:MouseClick();
                return;
            end
            info.altIcon = resources.BlankIcons[sender:GetValue()];
            UpdateIcon();
            UpdateIconID();
            self:UpdateBar();
            self.settings.sequenceEditor.defaultIcon = info.altIcon;
        end
        slider3.ValueChanged = function(sender)
            if (not button3:IsChecked()) then
                button3:MouseClick();
                return;
            end
            info.altIcon = self.skillIcons[sender:GetValue()];
            UpdateIcon();
            UpdateIconID();
            self:UpdateBar();
            self.settings.sequenceEditor.defaultIcon = info.altIcon;
        end
        function textBox.FocusGained()
            button4:MouseClick();
        end
        textBox.TextChanged = function(sender)
            if (not button4:IsChecked()) then
                return;
            end
            local id = sender:GetText();
            if (tonumber(id) ~= nil) then
                id = tonumber(id);
            end
            local valid = pcall(GetAssetSize, id);
            if (valid) then
                textBox:SetForeColor(Turbine.UI.Color.White);
                info.altIcon = id;
                UpdateIcon();
                self:UpdateBar();
            else
                textBox:SetForeColor(Turbine.UI.Color.Red);
            end
        end
    end
    
    self.slotProperties:SetHeight(top);
    self:SetMinimumHeight(top + 110);
end

function SequenceEditor:FindSlotAtPosition(x, y, drawCaret)
    local originX, originY = self.slotPanel:GetParent():PointToScreen(self.slotPanel:GetPosition());
    local relX, relY = x - originX, y - originY + 3;
    local posX, posY = 1 + math.floor(relX / self.slotSize + 0.5), 1 + math.floor(relY / self.slotSize);
    local s = nil;
    if ((posX >= 1) and (posX <= self.slotsWide + 1) and (posY >= 1) and (posY <= self.slotsHigh)) then
        s = ((posY - 1) * self.slotsWide) + posX;
    end
    if ((posX == self.slotsWide + 1) and (posY == self.slotsHigh)) then
        s = nil;
    end
    
    -- Update caret position.
    if ((not drawCaret) or (not s)) then
        -- Hide carets in all Sequence Editors
        for editor in keys(SequenceEditor.instances) do
            if (editor.caret) then
                editor.caret:SetParent(nil);
                editor.caret = nil;
            end
        end
    else
        if (not self.caret) then
            self.caret = Turbine.UI.Control();
            self.caret:SetParent(self.slotPanel);
            self.caret:SetBackground(resources.Icon.Caret);
            self.caret:SetBlendMode(Turbine.UI.BlendMode.AlphaBlend);
            self.caret:SetSize(3, 36)
        end
        self.caret:SetPosition((posX - 1) * self.slotSize, (posY - 1) * self.slotSize);
    end

    return s;
end

function SequenceEditor:FindForeignSlotAtPosition(x, y, drawCaret)

    -- Find the frontmost SequenceEditor under the mouse cursor
    local frontEditor = self;
    local maxZ = -2147483648;
    for editor in keys(SequenceEditor.instances) do
        if (editor ~= self) then
            local left, top = editor:GetPosition();
            local width, height = editor:GetSize();
            if ((x >= left) and (x <= left + width) and (y >= top) and (y <= top + height)) then
                if (editor:GetZOrder() >= maxZ) then
                    frontEditor = editor;
                    maxZ = editor:GetZOrder();
                end
            end
        end
    end

    return frontEditor, frontEditor:FindSlotAtPosition(x, y, drawCaret);
end

function SequenceEditor:BuildSlot(slot, parent, object, left, top)
--Puts("BuildSlot(" .. tostring(slot) .. ", " .. tostring(parent) .. ", " .. tostring(object) .. ", " .. tostring(left) .. ", " .. tostring(top));

    -- Get slot info, or initialize to default
    local info = self.settings.sequenceItemInfo[slot];
    if (object) then
        -- Reuse the existing object
        object:SetParent(parent);
        object:SetInfo(info);
        object:SetPosition(left, top);
        object:SetNumLabel(slot);
        return object;
    end

    -- Need to create a new object
    object = Slot(info);
    object:SetPosition(left, top);
    object:SetNumLabel(slot);
    object:SetParent(parent);
    object:SetActionEnabled(false);
    object:SetAllowDrop(true);
    object:SetDraggable(true);

    -- Desired mouse behaviors:
    --   Left-click selects the slot and shows its properties in the "Slot" tab.
    --   Right-click displays the settings menu.    
    --   Left-button-drag initiates a drag-and-drop (move) operation.

    object.ShortcutChanged = function(sender, shortcutType, shortcutData)
        local s = sender:GetNumLabel();
        self:ExtendSequenceTo(s);
        local info = sender:GetInfo();
        self.settings.sequenceItemInfo[s] = info;
        
        if (shortcutType == Turbine.UI.Lotro.ShortcutType.Alias) then
            info.textOverlay = string.gsub(info.Data, "^/", "");
            info.textOverlay = string.gsub(info.textOverlay, "%s.*$", "");
            info.background = self.settings.sequenceEditor.defaultIcon;
            sender:SetInfo(info);
        end

        self:UpdateBar();
        self:Redraw();
        self:SelectSlot(s);
    end

    object.MouseClick = function(sender, args)
        -- Left-click selects the slot and shows its properties in the "Slot" tab.
        if (args.Button == Turbine.UI.MouseButton.Left) then
            self:SelectSlot(sender:GetNumLabel());

        -- Right-click displays the settings menu.
        elseif (args.Button == Turbine.UI.MouseButton.Right) then
            self.clickedItem = sender:GetNumLabel();
            self:ShowContextMenu();
        end
    end

    object.DropPositionValid = function(sender, left, top)
        local s = sender:GetNumLabel();

        -- Draw a "caret" showing where the slot will be inserted.
        local editor = self;
        local newSlot = self:FindSlotAtPosition(left, top, true);
        if (not newSlot) then
            editor, newSlot = self:FindForeignSlotAtPosition(left, top, true);
        end
        if ((editor == self) and ((newSlot == s) or (newSlot == s + 1))) then
            self:FindSlotAtPosition(0, 0, false); -- hide caret if returning to the same position
        end
        return newSlot; -- if newSlot is nil, a red "X" will be shown
    end
    
    object.DragDropComplete = function(sender, left, top)
        local s = sender:GetNumLabel();
    
        -- Select destination slot.  Hide the caret.
        local editor = self;
        local newSlot = self:FindSlotAtPosition(left, top, false);
        if (not newSlot) then
            editor, newSlot = self:FindForeignSlotAtPosition(left, top, false);
        end
        
        -- Do the move operation, and redraw.
        if (newSlot and (editor ~= self)) then
            editor:InsertSlot(newSlot, self.settings.sequenceItemInfo[s]);
            self:DeleteSlot(s);
        elseif (newSlot and (newSlot ~= s) and (newSlot ~= s + 1)) then
            self:MoveSlot(s, newSlot);
        else
            self:Redraw();
        end
    end    

    return object;
end

function SequenceEditor:RebuildSlot(s, x, y, alreadyDisplaying)
    local oldObject = self.sequenceItems[s];
    
    -- If x and y aren't specified, then presumably the object has already been positioned previously
    if (x == nil) then
        x = oldObject.x;
        y = oldObject.y;
    end
    local left, top = self:GetSlotPosition(x, y);

    local newObject = self:BuildSlot(s, self.slotPanel, oldObject, left, top);
    newObject.x = x;
    newObject.y = y;
    self.sequenceItems[s] = newObject;

    if (not alreadyDisplaying and (self.selectedSlot == s)) then
        self:DisplaySlot(s);
    end
end

function SequenceEditor:SelectSlot(s)
--Puts("SelectSlot(" .. tostring(s) .. "): self.selectedSlot = " .. tostring(self.selectedSlot));
    if (self.selectedSlot) then
        local object = self.sequenceItems[self.selectedSlot];
        object:SetSelected(false);
        local info = self.settings.sequenceItemInfo[self.selectedSlot];
        if (info and (info.class == "Turbine.UI.Lotro.Quickslot") and (not info.type)) then
            -- Delete empty shortcut
            info.class = nil;
        end
    end
    for o = 1, #self.relatedSlots, 1 do
        local otherSlot, otherObject = unpack(self.relatedSlots[o]);
        otherObject:SetSelected(nil);
        self.relatedSlots[o] = nil;
    end
    if (s ~= nil) then
        local info = self.settings.sequenceItemInfo[s];
        if ((not info) or (not info.class)) then
            s = nil;
        else
            local object = self.sequenceItems[s];
            object:SetSelected(true);
            local sequence = self.bar:GetSequence();
            local barObject = sequence:GetSlot(sequence:GetNewIndex(s));
            if (barObject) then
                for otherSlot, _ in pairs({ifSlot = 1; elseSlot = 1; endIfSlot = 1}) do
                    if (barObject[otherSlot]) then
                        local otherObject = self.sequenceItems[sequence:GetOldIndex(barObject[otherSlot])];
                        otherObject:SetSelected(true, Turbine.UI.Color.Yellow);
                        table.insert(self.relatedSlots, {otherSlot, otherObject});
                    end
                end
            end
        end
    end
    self.selectedSlot = s;
    self:DisplaySlot(s);
end

function SequenceEditor:GetSlotPosition(x, y)
    local left = (x - 1) * (self.settings.slotSize + self.settings.slotSpacing) - self.settings.slotSpacing;
    local top = (y - 1) * (self.settings.slotSize + self.settings.slotSpacing) - self.settings.slotSpacing;
    return left, top;
end

function SequenceEditor:Closing()
    SequenceEditor.instances[self] = nil;
    self:DeleteEmptyTrailingSlots();
    self:UpdateBar();
    if (self.ItemMovedCallback) then
        RemoveCallback(Thurallor.Utils.Watcher, "ItemMoved", self.ItemMovedCallback);
        self.ItemMovedCallback = nil;
    end
end

function SequenceEditor:Close()
    Turbine.UI.Lotro.Window.Close(self);
end

function SequenceEditor:ShowContextMenu()
    -- Build the settings menu.
    self.settingsMenu = Turbine.UI.ContextMenu();
    local prevContext = L:SetContext("/SequenceEditor/RightClickMenu");
    self:AddSettingsMenuItem(self.settingsMenu, "Root", false);
    L:SetContext(prevContext);
    self.settingsMenu:ShowMenu();
end

function SequenceEditor:AddSettingsMenuItem(parent, itemName)
    local item = Turbine.UI.MenuItem(L:GetText(itemName), true, false);
    parent:GetItems():Add(item);

    if (itemName == "Root") then
        parent:GetItems():Clear();
        self:AddSettingsMenuItem(parent, "CreateSpecialSlot");
        self:AddSettingsMenuItem(parent, "InsertEmptySlot");
        self:AddSettingsMenuItem(parent, "CloneSlot");
        self:AddSettingsMenuItem(parent, "DeleteSlot");
    elseif (itemName == "CloneSlot") then
        item.Click = function()
            self:CloneSlot(self.clickedItem);
        end
    elseif (itemName == "InsertEmptySlot") then
        item.Click = function()
            self:InsertEmptySlot(self.clickedItem);
        end
    elseif (itemName == ("DeleteSlot")) then
        item.Click = function()
            self:DeleteSlot(self.clickedItem);
        end
    elseif (itemName == "CreateSpecialSlot") then
        local prevContext = L:SetContext("SpecialSlotMenu");
        local texts = L:GetSortedTexts();
        for i = 1, #texts, 1 do
            self:AddSettingsMenuItem(item, L:GetItem(texts[i]));
        end
        L:SetContext(prevContext);
    elseif (string.find("StopAnimating|GenerateEvent|SetUnequipDestination", itemName)) then
        item.Click = function()
            self:CreateCommandSlot(self.clickedItem, itemName, item:GetText());
        end
    elseif (itemName == "ChatCommand") then
        local commands = Turbine.Shell.GetCommands();
        local index = math.random(#commands);
        item.Click = function()
            self:CreateAliasSlot(self.clickedItem, "/" .. commands[index]);
        end
    elseif (itemName == "RemoveEquipment") then
        item.Click = function()
            self:CreateRemovalSlot(self.clickedItem, "Gloves");
        end
    elseif ((itemName == "IfThen") or (itemName == "IfThenElse")) then
        item.Click = function()
            self:CreateConditional(self.clickedItem, itemName);
        end
    elseif (itemName == "Include") then
        item.Click = function()
            self:CreateIncludeSlot(self.clickedItem);
        end
    elseif (itemName == "Delay") then
        item.Click = function()
            self:CreateDelaySlot(self.clickedItem);
        end
    elseif (itemName == "SelectTarget") then
        item.Click = function()
            self:CreateSelectTargetSlot(self.clickedItem);
        end
    elseif (itemName == "SaveTarget") then
        item.Click = function()
            self:CreateSaveTargetSlot(self.clickedItem);
        end
    elseif (itemName == "LuaScript") then
        item.Click = function()
            self:CreateLuaScriptSlot(self.clickedItem);
        end
    end
end

function SequenceEditor:CreateAliasSlot(s, command)
    self:InsertEmptySlot(s);
    local info = self.settings.sequenceItemInfo[s];
    info.class = "Turbine.UI.Lotro.Quickslot";
    info.background = nil;
    info.type = Turbine.UI.Lotro.ShortcutType.Alias;
    info.Data = command;
    info.altIcon = self.settings.sequenceEditor.defaultIcon;
    if (info.background) then
        info.textOverlay = string.gsub(info.Data, "^/", "");
        info.textOverlay = string.gsub(info.textOverlay, "%s.*$", "");
    end
    self:RebuildSlot(s);
    self:UpdateBar();
    self:Redraw();
    self:SelectSlot(s);
end

function SequenceEditor:CreateDelaySlot(s)
    self:InsertEmptySlot(s);
    local info = self.settings.sequenceItemInfo[s];
    info.class = "Turbine.UI.Control";
    info.background = resources.Icon.Clock;
    info.altIcon = nil;
    info.type = "Delay";
    info.toolTip = L:GetText("/SequenceEditor/RightClickMenu/SpecialSlotMenu/Delay");
    info.delay = 0.25;
    info.action = "local item, s = ...; item.bar:StartSlotCooldown(s, " .. tostring(info.delay):gsub(",", ".") .. ");";
    info.advanceEvent = "DelayComplete";

    self:RebuildSlot(s);
    self:UpdateBar();
    self:Redraw();
    self:SelectSlot(s);
end

function SequenceEditor:CreateSelectTargetSlot(s)
    self:InsertEmptySlot(s);
    local info = self.settings.sequenceItemInfo[s];
    info.class = "Turbine.UI.Lotro.EntityControl";
    info.altIcon = nil;
    info.type = "SelectTarget";
    info.toolTip = L:GetText("/SequenceEditor/RightClickMenu/SpecialSlotMenu/SelectTarget");
    info.target = "Self";
    info.getTargetFunc = "local _, player = ...; return player;";

    self:RebuildSlot(s);
    self:UpdateBar();
    self:Redraw();
    self:SelectSlot(s);

end

function SequenceEditor:CreateSaveTargetSlot(s)
    self:InsertEmptySlot(s);
    local info = self.settings.sequenceItemInfo[s];
    info.class = "Turbine.UI.Control";
    info.background = resources.Icon.SaveTarget;
    info.altIcon = nil;
    info.type = "SaveTarget";
    info.toolTip = L:GetText("/SequenceEditor/RightClickMenu/SpecialSlotMenu/SaveTarget");
    info.number = 1;
    info.action = "Thurallor.Utils.Watcher.SaveTarget(1);";

    self:RebuildSlot(s);
    self:UpdateBar();
    self:Redraw();
    self:SelectSlot(s);

end

function SequenceEditor:CreateIncludeSlot(s)
    self:InsertEmptySlot(s);
    local info = self.settings.sequenceItemInfo[s];
    info.class = "Turbine.UI.Control";
    info.background = resources.Icon.Include;
    info.altIcon = nil;
    info.type = "Include";
    info.toolTip = L:GetText("/SequenceEditor/RightClickMenu/SpecialSlotMenu/Include");
    
    self:RebuildSlot(s);
    self:UpdateBar();
    self:Redraw();
    self:SelectSlot(s);
end

function SequenceEditor:CreateLuaScriptSlot(s)
    self:InsertEmptySlot(s);
    local info = self.settings.sequenceItemInfo[s];
    info.class = "Turbine.UI.Control";
    info.background = resources.Icon.LuaScript;
    info.altIcon = nil;
    info.type = "LuaScript";
    info.toolTip = L:GetText("/SequenceEditor/RightClickMenu/SpecialSlotMenu/LuaScript");
    info.action = "Turbine.Shell.WriteLine(\"Hello, world!\");";
    
    self:RebuildSlot(s);
    self:UpdateBar();
    self:Redraw();
    self:SelectSlot(s);
end

function SequenceEditor:UnlinkIncludedSequence(s)
    local info = self.settings.sequenceItemInfo[s];
    if (not info.include) then
        return;
    end
    local otherSequenceSettings = self.globals.bars[info.include];
    if (not otherSequenceSettings) then
        return;
    end
    local otherItemInfo = otherSequenceSettings.sequenceItemInfo;
    self:DeleteSlot(s);
    for i = 1, #otherItemInfo, 1 do
        if (otherItemInfo[i].class) then
            table.insert(self.settings.sequenceItemInfo, s + i - 1, {});
            DeepTableCopy(otherItemInfo[i], self.settings.sequenceItemInfo[s + i - 1]);
        end
    end
    self:Redraw();
    self:SelectSlot(s);
end

function SequenceEditor:CreateConditional(s, condType)
    local prevContext = L:SetContext("/SequenceEditor");
    local ifSlot = s;
    self.noRedraw = true;
    self:InsertEmptySlot(s);
    if (condType == "IfThenElse") then
        self:InsertEmptySlot(s);
    end
    self:InsertEmptySlot(s);
    local info = self.settings.sequenceItemInfo[s];
    info.class = "Turbine.UI.Control";
    info.background = resources.Icon["If"];
    info.altIcon = nil;
    info.type = "If";
    info.condName = "Always";
    info.condExpr = "return true;";
    info.toolTip = L:GetText("If");
    info.automatic = true;
    if (condType == "IfThenElse") then
        s = s + 1;
        info = self.settings.sequenceItemInfo[s];
        info.class = "Turbine.UI.Control";
        info.background = resources.Icon["Else"];
        info.altIcon = nil;
        info.type = "Else";
        info.toolTip = L:GetText("Else");
        info.automatic = true;
    end
    s = s + 1;
    info = self.settings.sequenceItemInfo[s];
    info.class = "Turbine.UI.Control";
    info.background = resources.Icon["EndIf"];
    info.altIcon = nil;
    info.type = "EndIf";
    info.toolTip = L:GetText("EndIf");
    info.automatic = true;
    self.noRedraw = false;
    self:Redraw();
    self:UpdateBar();
    self:SelectSlot(ifSlot);
    L:SetContext(prevContext);
end

function SequenceEditor:CreateCommandSlot(s, slotName, toolTip)
    self:InsertEmptySlot(s);
    local info = self.settings.sequenceItemInfo[s];
    info.class = "Turbine.UI.Control";
    info.background = resources.Icon[slotName];
    info.altIcon = nil;
    if (slotName == "StopAnimating") then
        info.action = "local item = ...; item.bar.animationStopped = true; ";
    elseif (slotName == "GenerateEvent") then
        info.eventName = L:GetText("/SequenceEditor/NewEvent");
        info.action = "local item = ...; item.bar.manager:PropagateEvent(item.info.eventName);";
    elseif (slotName == "SetUnequipDestination") then
        info.bagSlot = 1;
        info.action = "local item = ...; item.bar.manager:SetUnequipDestination(item.info.bagSlot);";
    end
    info.type = slotName;
    info.toolTip = toolTip;
    self:RebuildSlot(s);
    self:UpdateBar();
    self:Redraw();
    self:SelectSlot(s);
end

function SequenceEditor:CreateRemovalSlot(s, eqSlotName, bagSlot)
    self:InsertEmptySlot(s);
    local info = self.settings.sequenceItemInfo[s];
    info.class = "Turbine.UI.Control";
    info.background = resources.Icon["Unequip" .. eqSlotName];
    info.altIcon = nil;
    info.action = "local item = ...; item.bar.manager:Unequip(Turbine.Gameplay.Equipment." .. eqSlotName .. ");";
    info.type = "Remove " .. eqSlotName;
    
    local prevContext = L:SetContext("/SequenceEditor");
    local toolTip = L:GetText("RemoveItem");
    L:SetContext("EquipmentSlots");
    local localEqSlotName = L:GetText(eqSlotName);
    info.toolTip = string.gsub(toolTip, "<item>", localEqSlotName);
    L:SetContext(prevContext);
    
    self:RebuildSlot(s);
    self:UpdateBar();
    self:Redraw();
    self:SelectSlot(s);
end

function SequenceEditor:ExtendSequenceTo(s)
    if (s <= #self.settings.sequenceItemInfo) then
        return false;
    end
    while (s > #self.settings.sequenceItemInfo) do
        table.insert(self.settings.sequenceItemInfo, {});
    end
    return true;
end

function SequenceEditor:DeleteEmptyTrailingSlots()
    local sequenceItemInfo = self.settings.sequenceItemInfo;
    for s = #sequenceItemInfo, 1, -1 do
        local info = sequenceItemInfo[s];
        if (not info.class) then
            table.remove(sequenceItemInfo, s);
            -- Remove empty slot displays
            if (self.sequenceItems) then
                local object = self.sequenceItems[s];
                if (object) then
                    if (object.numLabel) then
                        object.numLabel:SetParent(nil);
                    end
                    object:SetParent(nil);
                    self.sequenceItems[s] = nil;
                end
            end
        else
            break;
        end
    end
end

function SequenceEditor:CloneSlot(s)
    self:InsertEmptySlot(s);
    DeepTableCopy(self.settings.sequenceItemInfo[s + 1], self.settings.sequenceItemInfo[s]);
    self:RebuildSlot(s);
    self:UpdateBar();
    self:Redraw();
    self:SelectSlot(s);
end

function SequenceEditor:MoveSlot(sourceSlot, destSlot)
    self:InsertEmptySlot(destSlot);
    if (sourceSlot >= destSlot) then
        sourceSlot = sourceSlot + 1;
    end
    DeepTableCopy(self.settings.sequenceItemInfo[sourceSlot], self.settings.sequenceItemInfo[destSlot]);
    self:RebuildSlot(destSlot);
    self:DeleteSlot(sourceSlot);
    if (sourceSlot <= destSlot) then
        destSlot = destSlot - 1;
    end
    self:UpdateBar();
    self:Redraw();
    self:SelectSlot(destSlot);
end

function SequenceEditor:InsertSlot(s, info)
    if (s > #self.settings.sequenceItemInfo) then
        self:ExtendSequenceTo(s);
    else
        table.insert(self.settings.sequenceItemInfo, s, {});
    end
    if (info) then
        DeepTableCopy(info, self.settings.sequenceItemInfo[s]);
    end
    self:Redraw();
    self:UpdateBar();
    self:SelectSlot(s);
end

function SequenceEditor:InsertEmptySlot(s)
    self:InsertSlot(s, nil);
end

function SequenceEditor:DeleteSlot(s)
    table.remove(self.settings.sequenceItemInfo, s);
    self:Redraw();
    self:UpdateBar();
    self:ExtendSequenceTo(s);
    self:SelectSlot(nil);
end

Go to most recent revision | Compare with Previous | Blame


All times are GMT -5. The time now is 02:58 PM.


Our Network
EQInterface | EQ2Interface | Minion | WoWInterface | ESOUI | LoTROInterface | MMOUI | Swtorui