lotrointerface.com
Search Downloads

LoTROInterface SVN BusyBeeTasks

[/] [trunk/] [Plugins/] [BoskoPlugins/] [BusyBee/] [BusyBeeWindow.lua] - Rev 34

Compare with Previous | Blame | View Log

--
-- Busy Bee Tasks plugin for Lord of The Rings Online
--
-- by Bosko
-- Patches:
-- Gardori
-- zonk
--
-- http://www.lotrointerface.com/downloads/fileinfo.php?id=733
--

import "Turbine";
import "Turbine.Gameplay";
import "Turbine.UI";
import "Turbine.UI.Extensions";
import "Turbine.UI.Lotro";
import "BoskoPlugins.BusyBee.BusyBeeTasks"

--FIX for locale in saving routine (German version) using Vindarpatch
--inserted by zonk, note can be removed for next version
import "BoskoPlugins.BusyBee.Patch";
--FIX end

function writeln(str)
    Turbine.Shell.WriteLine("[BusyBee] " .. str);
end

function gettablestr(t, fExpandTable)
    str = "{ ";
    for k, v in pairs(t) do
    
        if fExpandTable and type(v) == "table" then
            str = str .. k .. " = " .. gettablestr(v, false) .. ", ";
        elseif type(v) == "string" then
            str = str .. k .. " = \"" .. v .. "\", ";
        elseif type(v) == "number" then
            str = str .. k .. " = " .. v .. ", ";
        elseif type(v) == "boolean" then
            if v then
                str = str .. k .. " = true, ";
            else
                str = str .. k .. " = false, ";
            end
        else
            mt = getmetatable(v);
            if mt == nil then
                str = str .. k .. " = ?(" .. type(v) .. "), ";
            else
                str = str .. k .. " = " .. type(v) .. " " .. gettablestr(mt) .. ", ";
            end
        end
    end
    
    return str .. "}";
end

function printtable(t)
    Turbine.Shell.WriteLine(gettablestr(t, true));
end

--==============================================================================
-- Settings
--==============================================================================

local BusyBeeSettingsVersion = 1.2;

local TurbineTaskCategory = 207;

local HeadingTextColor = Turbine.UI.Color(0.80, 0.80, 0.30);
local EntryTextColor   = Turbine.UI.Color(1.00, 1.00, 0.70);
local ItemLabelColor   = Turbine.UI.Color(0.40, 0.40, 0.40);

local clrLevelTooLow0  = Turbine.UI.Color(0.28, 0.28, 0.28);
local clrLevelTooLow1  = Turbine.UI.Color(0.41, 0.41, 0.41);
local clrLevelTooLow2  = Turbine.UI.Color(0.51, 0.55, 0.52);
local clrLevelInRange0 = Turbine.UI.Color(0.56, 0.51, 0.35);
local clrLevelInRange1 = Turbine.UI.Color(0.80, 0.80, 0.47);
local clrLevelInRange2 = Turbine.UI.Color(0.33, 0.80, 0.15);
local clrLevelTooHigh0 = Turbine.UI.Color(0.45, 0.31, 0.31);
local clrLevelTooHigh1 = Turbine.UI.Color(0.60, 0.40, 0.40);
local clrLevelTooHigh2 = Turbine.UI.Color(0.80, 0.52, 0.45);

local clrTextButton = Turbine.UI.Color(0.40, 0.40, 0.40);
local clrTextButtonMouseOver = Turbine.UI.Color(0.65, 0.65, 0.65);
local clrTextButtonMouseDown = Turbine.UI.Color(1.00, 1.00, 1.00);

local minWindowSize = {w = 250, h = 35 };

local headingWidth = 400;
local headingHeight = 16;
local entryHeight = 14;

local itemCtrlPos = {x = -18, y = -17, w = 32, h = 29 };

local windowOpacityActive = 1.0;
local windowOpacityPassive = 1.0;

local WindowColorParams = {base = 40.0, low = -8, high = 20, R = 0.0, G = 0.0, B = 0.0 };

local windowOriginalWidth = 415;
local windowOriginalHeight = 200;

local marginX = 10;
local marginTop = 5;
local marginBottom = 10;
local buttonWidth0 = 42;
local button01margin = 8;
local buttonWidth1 = 54;
local buttonWidth2 = 16;
local buttonHeight = 20;

local buttonFont = Turbine.UI.Lotro.Font.Verdana12;
local buttonFont2 = Turbine.UI.Lotro.Font.Verdana16;

local buttonWidthLock = 18;
local fileLockedIcon = "BoskoPlugins/Resources/locked18.tga";
local fileUnlockedIcon = "BoskoPlugins/Resources/unlocked18.tga";
local resizeBottomRightIcon = "BoskoPlugins/Resources/resizeBR20.tga";

local FactionNone = "(none)";

local lvlRangeTooLow = "Too Low";
local lvlRangeMyLevel = "My Level";
local lvlRangeTooHigh = "Too High";

--==============================================================================
-- Data Generation
--==============================================================================

--------------------------------------------------------------------------------
local function BusyBeeSortById(itemA, itemB)

    return itemA.id < itemB.id;
end

--------------------------------------------------------------------------------
local function BusyBeeSortByFullName(itemA, itemB)

    if itemA.fullname == itemB.fullname then
        if itemA.lvlmin == itemB.lvlmin then
            return itemA.lvlmax < itemB.lvlmax;
        else
            return itemA.lvlmin < itemB.lvlmin;
        end
    else
        return itemA.fullname < itemB.fullname;
    end
end

--------------------------------------------------------------------------------
local function BusyBeeSortByLevel(itemA, itemB)

    if itemA.lvlmin == itemB.lvlmin and itemA.lvlmax == itemB.lvlmax then
        return BusyBeeSortByFullName(itemA, itemB);
    else
        if itemA.lvlmin == itemB.lvlmin then
            return itemA.lvlmax < itemB.lvlmax;
        else
            return itemA.lvlmin < itemB.lvlmin;
        end
    end
end

--------------------------------------------------------------------------------
local function BusyBeeSortByLocation(itemA, itemB)

    if itemA.location == itemB.location then
        return BusyBeeSortByLevel(itemA, itemB);
    else
        return itemA.location < itemB.location;
    end
end

--------------------------------------------------------------------------------
local function BusyBeeSortByName(itemA, itemB)

    if itemA.fullname == itemB.fullname then
        return BusyBeeSortByLevel(itemA, itemB);
    else
        return itemA.fullname < itemB.fullname;
    end
end

--------------------------------------------------------------------------------
local function BusyBeeSortByFaction(itemA, itemB)

    if itemA.faction == itemB.faction then
        return BusyBeeSortByLevel(itemA, itemB);
    else
        return itemA.faction < itemB.faction;
    end
end

--------------------------------------------------------------------------------
local function BusyBeeBuildTreeDataByKey(key, fnSort)

    local list = { };
    local count = 0;
    local lastKey = nil;
    
    -- Sort by key
    table.sort(BusyBeeTasks, fnSort);
    
    -- valid key or global group
    if BusyBeeTasks[1][key] == nil then
        table.insert(list, {key = key, ids = {}});
        count = 1;
        key = nil;
    end
    
    -- Build list
    for i, item in ipairs(BusyBeeTasks) do
        if key ~= nil then
            local thisKey = item[key];
            if thisKey ~= lastKey then
                table.insert(list, {key = thisKey, ids = {}});
                count = count + 1;
                lastKey = thisKey;
            end
        end
        
        -- Add item information
        table.insert(list[count].ids, item.id);
    end
    
    -- Restore order of tasks
    table.sort(BusyBeeTasks, function(a,b) return a.id < b.id; end );
    
    -- Return list
    return list;
end

--------------------------------------------------------------------------------
function BusyBeeFixLevelRanges()
    local playerLevel = Turbine.Gameplay.LocalPlayer.GetInstance():GetLevel();
    
    for i, item in ipairs(BusyBeeTasks) do
        if item.lvlmax < playerLevel then
            item.lvlrange = 0;
            item.lvlrangetext = lvlRangeTooLow;
        elseif item.lvlmin > playerLevel then
            item.lvlrange = 2;
            item.lvlrangetext = lvlRangeTooHigh;
        else
            item.lvlrange = 1;
            item.lvlrangetext = lvlRangeMyLevel;
        end
    end
end

------------------------------------------------------------------------------
function BusyBeeSortByLevelRange(itemA, itemB)
    if itemA.lvlrange == itemB.lvlrange then
        return BusyBeeSortByLevel(itemA, itemB);
    else
        return itemA.lvlrange < itemB.lvlrange;
    end
end

--==============================================================================
-- Tree Data
--==============================================================================

-- Sort by item name
table.sort(BusyBeeTasks, BusyBeeSortByFullName);

for i, item in ipairs(BusyBeeTasks) do
    item.visible = true;
    item.nodes = { };
    item.location = item.zone .. " - " .. item.place;
        --FIX for items without prefix (German version)
        --inserted by zonk, note can be removed for next version
        --FIXME: set item.quantity=10;
        --FIXME: only item.location and item.fullname
        if item.prefix == "" then
                item.fullname=item.name;
        else
                item.fullname = item.prefix .. " " .. item.name;
        end
        --FIX end
    if item.faction == "-" then
        item.faction = FactionNone;
    end
    
end

BusyBeeFixLevelRanges();

------------------------------------------------------------------------------
-- Build BusyBeeCounts
local BusyBeeCounts = { };
for i, item in ipairs(BusyBeeTasks) do
    local index = item.fullname;
    local tableItem = BusyBeeCounts[index];
    if (tableItem == nil) then
        -- Initialize the item
        BusyBeeCounts[index] = {count = 0,  = item.quantity, ids = {}, nodes = {}};
    end
    
    -- Add item information
    table.insert(BusyBeeCounts[index].ids, item.id);
end

------------------------------------------------------------------------------

local entryFont = Turbine.UI.Lotro.Font.Verdana12;

local BusyBeeTreeData = {
    { data = BusyBeeBuildTreeDataByKey("location", BusyBeeSortByLocation), 
        left = 0, fields = {
        { key = "level",  width = 50, alignment = "left" }, 
        { key = "fullname",  width = 124, alignment = "left" }, 
        { key = "count",  width = 60, alignment = "center" }, 
        { key = "faction",  width = 125, alignment = "left" }, 
    } },
    { data = BusyBeeBuildTreeDataByKey("faction", BusyBeeSortByFaction), 
        left = 0, fields = {
        { key = "level",  width = 50, alignment = "left" }, 
        { key = "fullname",  width = 124, alignment = "left" }, 
        { key = "count",  width = 60, alignment = "center" }, 
        { key = "location",  width = 165, alignment = "left" }, 
    } },
--     { data = BusyBeeBuildTreeDataByKey("lvlrangetext", BusyBeeSortByLevelRange), 
--         left = 0, fields = {
--         { key = "level",  width = 50, alignment = "left" }, 
--         { key = "fullname",  width = 124, alignment = "left" }, 
--         { key = "count",  width = 60, alignment = "center" }, 
--         { key = "faction",  width = 125, alignment = "left" }, 
--         { key = "location",  width = 165, alignment = "left" }, 
--     } },
--     { data = BusyBeeBuildTreeDataByKey("All Items (by Name)", BusyBeeSortByName), 
--         left = 0, fields = {
--         { key = "level",  width = 50, alignment = "left" }, 
--         { key = "fullname",  width = 124, alignment = "left" }, 
--         { key = "count",  width = 60, alignment = "center" }, 
--         { key = "faction",  width = 125, alignment = "left" }, 
--         { key = "location",  width = 165, alignment = "left" }, 
--     } },
    { data = BusyBeeBuildTreeDataByKey("fullname", BusyBeeSortByName), 
        left = 26, headerfields = {
        { key = "level",  width = 50, alignment = "left" }, 
        { key = "fullname",  width = 124, alignment = "left" }, 
        { key = "count",  width = 60, alignment = "center" }, 
        }, fields = {
        { key = "level",  width = 50, alignment = "left" }, 
        { key = "faction",  width = 125, alignment = "left" }, 
        { key = "location",  width = 165, alignment = "left" }, 
    } },
};


--==============================================================================
-- BusyBeeLabel
--==============================================================================
BusyBeeLabel = class(Turbine.UI.Label);

function BusyBeeLabel:Constructor(parent, x, w, h, alignment)
    Turbine.UI.Label.Constructor(self);
    
    -- Set the format
    self:SetParent(parent);
    self:SetPosition(x, 0);
    self:SetSize(w, h);
    self:SetFontStyle(Turbine.UI.FontStyle.Outline);
    self:SetOutlineColor(Turbine.UI.Color(0.00, 0.00, 0.00));
    self:SetTextAlignment(alignment);
    self:SetForeColor(EntryTextColor);
    self:SetFont(entryFont);
end

--==============================================================================
-- BusyBeeHeading
--==============================================================================

BusyBeeHeading = class(Turbine.UI.TreeNode);

--------------------------------------------------------------------------------
function BusyBeeHeading:Constructor(szText)
    Turbine.UI.TreeNode.Constructor(self);
    
    self:SetSize(headingWidth, headingHeight);
    
    -- Create and format the heading label
    self.header = Turbine.UI.Label();
    self.header:SetParent(self);
    self.header:SetPosition(0, 0);
    self.header:SetSize(headingWidth, headingHeight);
    self.header:SetFontStyle(Turbine.UI.FontStyle.Outline);
    self.header:SetForeColor(HeadingTextColor);
    self.header:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleLeft);
    self.header:SetFont(Turbine.UI.Lotro.Font.TrajanPro15);
    self.header:SetText(szText);
end

--------------------------------------------------------------------------------
function BusyBeeHeading:GetMaxHeight()
    return headingHeight;
end

--------------------------------------------------------------------------------
function BusyBeeHeading:UpdateCount()
    -- noop
end

--------------------------------------------------------------------------------
function BusyBeeHeading:ShowIcon(fShow)
    -- noop
end

--==============================================================================
-- BusyBeeNode
--==============================================================================

BusyBeeNode = class(Turbine.UI.TreeNode);

--------------------------------------------------------------------------------
function BusyBeeNode:Constructor(item, left, fields)
    Turbine.UI.TreeNode.Constructor(self);
    
    self.item = item;
    self.left = left;
    self.fields = fields;
    self.countField = nil;
    self.levelField = nil;
    self.fShowIcon = false;
    
    -- Full name and quantity are fixed values
    self.quantity = self:GetField(item, "quantity");
    self.fullname = self:GetField(item, "fullname");
    
    -- Create item control
    if left == 0 then
        self.itemCtrl = Turbine.UI.Lotro.ItemControl();
        self.itemCtrl:SetVisible(true);
        self.itemCtrl:SetParent(self);
        
        self.itemCtrl:SetPosition(left, 0);
        self.itemCtrl:SetSize(0, 0);
        self.itemCtrl:SetMouseVisible(true);
    end
    
    -- Create and populate the labels
    local nFields = 0;
    for i, desc in ipairs(fields) do
        nFields = nFields + 1;
        
        -- Identify alignment
        alignment = Turbine.UI.ContentAlignment.MiddleCenter;
        if (desc.alignment == "left") then
            alignment = Turbine.UI.ContentAlignment.MiddleLeft;
        elseif (desc.alignment == "right") then
            alignment = Turbine.UI.ContentAlignment.MiddleRight;
        end
        
        -- Create the label and fill the text
        self["field" .. nFields] = BusyBeeLabel(self, left, desc.width, entryHeight, alignment);
        self["field" .. nFields]:SetText(self:FormatText(desc.key));
        left = left + desc.width;
        
        -- Remember which field is "count"
        if desc.key == "count" then
            self.countField = nFields;
        end
        if desc.key == "level" then
            self.levelField = nFields;
        end
    end
    
--     if item.ids then
--         for i, desc in ipairs(self.fields) do
--             self["field" .. i]:SetFont(Turbine.UI.Lotro.Font.Verdana12);
--         end
--     end
    
    -- Set the size and update the count
    self:SetSize(left, self:GetMaxHeight());
end

--------------------------------------------------------------------------------
function BusyBeeNode:GetField(item, field)
    local value = item[field];
    if (value == nil) then
        local n = 0;
        for i, id in ipairs(item.ids) do
            local item2 = BusyBeeTasks[id];
            if item2.visible then
                n = n + 1;
                if (n == 1) then
                    -- First field
                    value = item2[field];
                elseif n == 2 then
                    if value ~= item2[field] then
                        -- Different field, mark as such
                        value = "multiple " .. field .. "s";
                    else
                        -- Same field as before
                        n = 1;
                    end
                end
            end
        end
    end
    
    if value == nil then
        value = "???";
    end
    return value;
end

--------------------------------------------------------------------------------
function BusyBeeNode:GetFieldRange(item, field)
    local minValue = item[field];
    local maxValue = item[field];
    if (minValue == nil) then
        minValue = 0;
        maxValue = 0;
        local n = 0;
        for i, id in ipairs(item.ids) do
            local item2 = BusyBeeTasks[id];
            if item2.visible then
                n = n + 1;
                if (n == 1) then
                    minValue = item2[field];
                    maxValue = item2[field];
                else
                    if (minValue > item2[field]) then
                        minValue = item2[field];
                    elseif (maxValue < item2[field]) then
                        maxValue = item2[field];
                    end
                end
            end
        end
    end
    return minValue, maxValue;
end


--------------------------------------------------------------------------------
function BusyBeeNode:FormatText(field)

    local id = self.item.id;
    if id == nil then
        if type(self.item.ids) ~= "table" then
            return "";
        end
        id = self.item.ids[1];
    end
    
    if (field == "level") then
        local min1, max1 = self:GetFieldRange(self.item, "lvlmin");
        local min2, max2 = self:GetFieldRange(self.item, "lvlmax");
        return string.format("[%d-%d]", min1, max2);
    elseif (field == "fullname") then
        return self.fullname;
    elseif (field == "count") then
        return "";
    elseif (field == "sellvalue") then
        return string.format("%.2f", BusyBeeTasks[id].sellvalue);
    else
        return self:GetField(self.item, field);
    end
end

--------------------------------------------------------------------------------
function BusyBeeNode:UpdateCount()
    local lvlMin, lvlMax2 = self:GetFieldRange(self.item, "lvlmin");
    local lvlMin2, lvlMax = self:GetFieldRange(self.item, "lvlmax");
    local lvlrangemin, lvlrangemax = self:GetFieldRange(self.item, "lvlrange");
    local lvlrange = 1;
    count = BusyBeeCounts[self.fullname].count;
    quantity = BusyBeeCounts[self.fullname].quantity;
    
    if lvlrangemin == lvlrangemax then
        lvlrange = lvlrangemin;
    end
    
    local clr = clrLevelInRange0;
    
    if self.countField ~= nil then
        self["field" .. self.countField]:SetText(count .. "/" .. quantity);
    end
    
    if self.levelField ~= nil then
        self["field" .. self.levelField]:SetText("[" .. lvlMin .. "-" .. lvlMax .. "]");
    end
    
    if lvlrange == 0 then
        if count == 0 then
            clr = clrLevelTooLow0;
        elseif count < quantity then
            clr = clrLevelTooLow1;
        else
            clr = clrLevelTooLow2;
        end
    elseif lvlrange == 2 then
        if count == 0 then
            clr = clrLevelTooHigh0;
        elseif count < quantity then
            clr = clrLevelTooHigh1;
        else
            clr = clrLevelTooHigh2;
        end
    else
        if count == 0 then
            clr = clrLevelInRange0;
        elseif count < quantity then
            clr = clrLevelInRange1;
        else
            clr = clrLevelInRange2;
        end
    end
    
    for i, desc in ipairs(self.fields) do
        self["field" .. i]:SetForeColor(clr);
    end
end

--------------------------------------------------------------------------------
function BusyBeeNode:FixVisibility()
    local oldHeight = self:GetHeight();
    local height = 0;
    local parentNode = self:GetParentNode();
    if parentNode ~= nil then
        
        for i, desc in ipairs(self.fields) do
            if i ~= self.countField then
                self["field" .. i]:SetText(self:FormatText(desc.key));
            end
        end
        
        if self.item.id ~= nil then
            if BusyBeeTasks[self.item.id].visible then
                height = self:GetMaxHeight();
            end
        else
            for i, id in ipairs(self.item.ids) do
                if BusyBeeTasks[id].visible then
                    height = self:GetMaxHeight();
                end
            end
        end
        
        if oldHeight ~= height then
            self:SetHeight(height);
            
            local childNodes = parentNode:GetChildNodes();
            local fFound = 0;
            for j = 1,childNodes:GetCount(), 1 do
                local subItem = childNodes:Get(j);
                if 0 < subItem:GetHeight() then
                    fFound = 1;
                    break;
                end
            end
            parentNode:SetHeight(parentNode:GetMaxHeight() * fFound);
        end
    else
        self:UpdateCount();
    end
end

--------------------------------------------------------------------------------
function BusyBeeNode:FixItemCtrl(backpack)
    local itemBeingRemoved = nil;    
    if self.itemCtrl ~= nil then
        local itemOld = self.itemCtrl:GetItem();
        
        if BusyBeeCounts[self.fullname].count > 0 then
            -- find the best item
            local backpackSize = backpack:GetSize();
            local bestItem = nil;
            for i = 1, backpackSize, 1 do
                local item = backpack:GetItem(i);
                if item ~= nil and item ~= itemBeingRemoved and item:GetName() == self.fullname then
                    if bestItem == nil or bestItem:GetQuantity() < item:GetQuantity() then
                        bestItem = item;
                    end
                end
            end
            
            self.itemCtrl:SetItem(bestItem);
            if itemOld == nil then
                self:FixLayout();
            end
        else
            self.itemCtrl:SetItem(nil);
            if itemOld ~= nil then
                self:FixLayout();
            end
        end
    end
end

--------------------------------------------------------------------------------
function BusyBeeNode:GetMaxHeight()
    local height = entryHeight;
--     if self.item.ids then
--         height = headingHeight;
--     end
    if (self.itemCtrl ~= nil and self.fShowIcon 
        and self.itemCtrl:GetItem() ~= nil 
        and self.itemCtrl:GetItem():GetQuantity() > 0) then
        height = math.max(height, itemCtrlPos.y + itemCtrlPos.h);
    end
    
    return height;
end

--------------------------------------------------------------------------------
function BusyBeeNode:ShowIcon(fShow)

    if self.fShowIcon ~= fShow then
        self.fShowIcon = fShow;
        self:FixLayout();
    end
end


--------------------------------------------------------------------------------
function BusyBeeNode:FixLayout()

    local left = self.left;
    local height = self:GetMaxHeight();
    local oldHeight = self:GetHeight();
    
    if self.itemCtrl ~= nil then
        self.itemCtrl:SetPosition(left, 0);
        
        -- Show or hide item control
        if self.fShowIcon then
            if (self.itemCtrl:GetItem() ~= nil and self.itemCtrl:GetItem():GetQuantity() > 0) then
            self.itemCtrl:SetPosition(left + itemCtrlPos.x, itemCtrlPos.y);
                self.itemCtrl:SetSize(itemCtrlPos.w, itemCtrlPos.h);
                height = self:GetMaxHeight();
            else
                self.itemCtrl:SetSize(0, 0);
            end
            left = left + itemCtrlPos.x + itemCtrlPos.w + 2;
        else
            self.itemCtrl:SetSize(0, 0);
        end
        
--         self.itemCtrl:SetBackColor(ItemLabelColor);
    end
    
    -- Move the labels
    for i, desc in ipairs(self.fields) do
        -- Create the label and fill the text
        self["field" .. i]:SetPosition(left, 0);
        self["field" .. i]:SetSize(desc.width, height);
        left = left + desc.width;
    end
    
    self:SetWidth(left);
    if oldHeight > 0 then
        self:SetHeight(height);
    end
end

--==============================================================================
-- BusyBeeTree
--==============================================================================
BusyBeeTree = class(Turbine.UI.Control);

--------------------------------------------------------------------------------
function BusyBeeTree:Constructor(items, left, headerfields, fields)
    Turbine.UI.Control.Constructor(self);
    
    -- Create tree view
    self.treeView = Turbine.UI.TreeView();
    self.treeView:SetParent(self);

    -- Create tree view scrollbar
    self.scrollBar = Turbine.UI.Lotro.ScrollBar();
    self.scrollBar:SetOrientation(Turbine.UI.Orientation.Vertical);
    self.scrollBar:SetParent(self);
    self.treeView:SetVerticalScrollBar(self.scrollBar);

    -- Populate the tree view
    local level1 = self.treeView:GetNodes();
    
    for i, value in ipairs(items) do
        local heading = nil;
        if headerfields == nil then
            heading = BusyBeeHeading(value.key);
        else
            heading = BusyBeeNode(value, 0, headerfields);
            table.insert(BusyBeeCounts[heading.fullname].nodes, heading);
        end
        level1:Add(heading);
        local level2 = heading:GetChildNodes();
        for j, id in ipairs(value.ids) do
            local item = BusyBeeTasks[id];
            local node = BusyBeeNode(item, left, fields);
            level2:Add(node);
            table.insert(BusyBeeCounts[BusyBeeTasks[id].fullname].nodes, node);
            table.insert(BusyBeeTasks[id].nodes, node);
            if headerfields ~= nil then
                table.insert(BusyBeeTasks[id].nodes, heading);
            end
        end
    end
    
    self.SizeChanged = function(sender, args)
        sender:FixLayout();
    end
end

--------------------------------------------------------------------------------
function BusyBeeTree:GetState()

    -- Compute state
    local level1 = self.treeView:GetNodes();
    local treeState = 0;
    for i = 1,level1:GetCount(), 1 do
        local item = level1:Get(i);
        local level2 = item:GetChildNodes();
        local subItem = level2:Get(1);
        treeState = 2 * treeState;
        if subItem:IsVisible() then
            treeState = treeState + 1;
        end
    end
    
    return treeState;
end

--------------------------------------------------------------------------------
function BusyBeeTree:RestoreState(treeState)

    -- Restore tree state (expand appropriate nodes)
    local level1 = self.treeView:GetNodes();
    for i = level1:GetCount(), 1, -1 do
        if (treeState % 2 ~= 0) then
            local item = level1:Get(i);
            item:SetExpanded(true);
            treeState = treeState - 1;
        end
        treeState = treeState / 2;
    end
end

--------------------------------------------------------------------------------
function BusyBeeTree:FixLayout()
    self:Layout({ });
end

--------------------------------------------------------------------------------
function BusyBeeTree:Layout(args)
    local left, top = self:GetPosition();
    local width, height = self:GetSize();
    local scrollWidth = 10;
    
    self.scrollBar:SetPosition(width - scrollWidth, 0);
    self.scrollBar:SetSize(scrollWidth, height);
    self.treeView:SetPosition(0, 0);
    self.treeView:SetSize(width - scrollWidth, height);
end

--------------------------------------------------------------------------------
function BusyBeeTree:ExpandAll()
    self.treeView:ExpandAll();
end

--------------------------------------------------------------------------------
function BusyBeeTree:CollapseAll()
    self.treeView:CollapseAll();
end

--------------------------------------------------------------------------------
function BusyBeeTree:GetSubTreeHeight(childNodes)
    local totalHeight = 0;
    for i = childNodes:GetCount(), 1, -1 do
        local childNode = childNodes:Get(i);
        local childHeight = childNode:GetHeight();
        if childNode:IsExpanded() and childNode:GetChildNodes() then
            childHeight = childHeight + self:GetSubTreeHeight(childNode:GetChildNodes());
        end
        
        totalHeight = totalHeight + childHeight;
    end
    
    return totalHeight;
end

--------------------------------------------------------------------------------
function BusyBeeTree:ShowIcons(fShow)

    local level1 = self.treeView:GetNodes();
    for i = 1, level1:GetCount(), 1 do
        local childNode = level1:Get(i);
        childNode:ShowIcon(fShow);
        local level2 = childNode:GetChildNodes();
        for i = 1, level2:GetCount(), 1 do
            local childNode2 = level2:Get(i);
            childNode2:ShowIcon(fShow);
        end
    end
end

--------------------------------------------------------------------------------
function BusyBeeTree:ShowScrollBar(show)

    -- Only show the scrollbar if 
    if show then
        local totalHeight = self:GetSubTreeHeight(self.treeView:GetNodes());
        local height = self:GetHeight();
        if height < totalHeight then
            self.scrollBar:SetVisible(true);
        end
    else
        self.scrollBar:SetVisible(false);
    end
    
end

--==============================================================================
-- BusyBeeWindow
--==============================================================================
BusyBeeWindow = class(Turbine.UI.Window);

--------------------------------------------------------------------------------
function BusyBeeWindow:Constructor()
    Turbine.UI.Window.Constructor(self);

    self:LoadSettings();

    self.dragging = false;
    
    -- Position the window in top-center
    self:SetSize(self.settings.windowWidth, self.settings.windowHeight);
    self:SetBackColor(Turbine.UI.Color(0.0, 
        WindowColorParams.R, WindowColorParams.G, WindowColorParams.B));
    self:SetPosition(self.settings.windowLeft, self.settings.windowTop);
    self:SetText("Busy Bee Tasks");
    self:SetMouseVisible(true);

    -- Set up mouse-triggered fade
--     self:SetFadeSpeed(0.4);
    self:SetPassiveOpacity(self.settings.passiveOpacity);
    self:SetZOrder(-1);
    self.bkOpacity = 0;
    self.targetBkOpacity = self.bkOpacity;
    self.Update = function(sender, args)
--         writeln(self.bkOpacity .. "->" .. self.targetBkOpacity);
        if self.bkOpacity == self.targetBkOpacity then
            self:SetWantsUpdates(false);
        elseif self.bkOpacity < self.targetBkOpacity then
            self.bkOpacity = self.bkOpacity + 1;
            if self.bkOpacity > 0 then
                self:SetBackColor(Turbine.UI.Color(self.bkOpacity / WindowColorParams.base, 
                    WindowColorParams.R, WindowColorParams.G, WindowColorParams.B));
            end
        elseif self.bkOpacity > self.targetBkOpacity then
            self.bkOpacity = self.bkOpacity - 1;
            self:SetBackColor(Turbine.UI.Color(self.bkOpacity / WindowColorParams.base, 
                WindowColorParams.R, WindowColorParams.G, WindowColorParams.B));
        end
    end
    
    self.MouseEnter = function(sender, args)
        if not self.dragging then
            self:FadeIn();
            if not self.isLocked then
                local bkOpacity = math.floor(WindowColorParams.base * self:GetBackColor().A);
                if bkOpacity == 0 then
                    bkOpacity = WindowColorParams.low;
                end
                self.bkOpacity = bkOpacity;
                self.targetBkOpacity = WindowColorParams.high;
                self:SetWantsUpdates(true);
            end
        end
    end

    self.MouseLeave = function(sender, args)
        if not self.dragging then
            self:FadeOut();
            if not self.isLocked then
                local bkOpacity = math.floor(WindowColorParams.base * self:GetBackColor().A);
                if bkOpacity > 0 then
                    self.targetBkOpacity = 0;
                    self:SetWantsUpdates(true);
                else
                    self.bkOpacity = 0;
                    self:SetWantsUpdates(false);
                end
            end
            self:SaveSettings();
        end
    end

    self.player = Turbine.Gameplay.LocalPlayer.GetInstance();
    self.backpack = self.player:GetBackpack();

    local mainWindow = self;
    
    -- Level change handler
     self.player.LevelChanged = function(sender, args)
        BusyBeeFixLevelRanges();
        --TODO: Rebuild 'by level' tree
        for k, v in pairs(BusyBeeCounts) do
            for i, node in ipairs(v.nodes) do
                node:UpdateCount();
            end
        end
        self:UpdateAllTasks();
    end

    -- Button-related states
    self.fShowButtons = false;
    self.nShowCount = self.settings.nShowCount;
    self.fShowAllLevels = self.settings.fShowAllLevels;
    self.fShowRepAward = self.settings.fShowRepAward;
    self.fShowIcons = self.settings.fShowIcons;
    
    -- Create mode button
    self.ModeButton = Turbine.UI.Lotro.Button();
    self.ModeButton:SetParent(self);
    self.ModeButton:SetFont(buttonFont);
    self.ModeButton:SetText("Next");
    self.ModeButton.Click = function(sender, args)
        local fNextTree = false;
        for i, tree in ipairs(mainWindow.trees) do
            if fNextTree then
                tree:SetVisible(true);
                fNextTree = false;
            elseif tree:IsVisible() then
                tree:SetVisible(false);
                fNextTree = true;
            end
        end
        if fNextTree then
            mainWindow.trees[1]:SetVisible(true);
        end
    end
    
--     -- Create menu items
--     contextMenu = Turbine.UI.ContextMenu();
--     
--     -- Populate the menu with some items.
--     menuItems = contextMenu:GetItems();
--     menuItems:Add(Turbine.UI.MenuItem("Show Icon"));
--     menuItems:Add(Turbine.UI.MenuItem(nil));
--     menuItems:Add(Turbine.UI.MenuItem( "By Location"));
--     menuItems:Add(Turbine.UI.MenuItem( "By Faction"));
--     menuItems:Add(Turbine.UI.MenuItem( "By Name"));
--     
--     -- Register event handlers with all the menu items.
--     local i;
--     
--     -- Register an event handler to display what menu item was clicked
--     -- in the chat window.
--     for i = 1, menuItems:GetCount() do
--         local menuItem = menuItems:Get( i );
--         
--         menuItem.Click = function( sender, args )
--             Turbine.Shell.WriteLine( "You have selected " .. sender:GetText() .. "." );
--         end
--     end
    
    -- Create show icon toggle button
    self.ShowIconButton = Turbine.UI.Lotro.Button();
    self.ShowIconButton:SetParent(self);
    self.ShowIconButton:SetFont(buttonFont);
    self.ShowIconButton:SetText("Icon");
    self.ShowIconButton.Click = function(sender, args)
        mainWindow.fShowIcons = not mainWindow.fShowIcons;
        mainWindow:ShowIcons();
        mainWindow:PerformLayout();
        
--         -- Display the menu at the current mouse location.
--         contextMenu:ShowMenu();
    end
    
    -- Create item count toggle button
    self.ItemCountButton = Turbine.UI.Lotro.GoldButton();
    self.ItemCountButton:SetParent(self);
    self.ItemCountButton:SetFont(buttonFont);
    self.ItemCountButton.Click = function(sender, args)
        mainWindow.nShowCount = (mainWindow.nShowCount + 1) % 3;
        mainWindow:UpdateAllTasks();
        mainWindow:PerformLayout();
    end
    
    -- Create level eligibility toggle button
    self.AllLevelsButton = Turbine.UI.Lotro.GoldButton();
    self.AllLevelsButton:SetParent(self);
    self.AllLevelsButton:SetFont(buttonFont);
    self.AllLevelsButton.Click = function(sender, args)
        mainWindow.fShowAllLevels = not mainWindow.fShowAllLevels;
        mainWindow:UpdateAllTasks();
        mainWindow:PerformLayout();
    end
    
    -- Create reputation award toggle button
    self.RepAwardButton = Turbine.UI.Lotro.GoldButton();
    self.RepAwardButton:SetParent(self);
    self.RepAwardButton:SetFont(buttonFont);
    self.RepAwardButton.Click = function(sender, args)
        mainWindow.fShowRepAward = not mainWindow.fShowRepAward;
        mainWindow:UpdateAllTasks();
        mainWindow:PerformLayout();
    end
    
    -- Create 'Expand All' button
    self.ExpandButton = Turbine.UI.Button();
    self.ExpandButton:SetParent(self);
    self.ExpandButton:SetFont(buttonFont2);
    self.ExpandButton:SetText("+");
    self.ExpandButton:SetForeColor(clrTextButton);
    self.ExpandButton.Click = function(sender, args)
        for i, tree in ipairs(self.trees) do
            if tree:IsVisible() then
                tree:ExpandAll();
            end
        end
    end
    self.ExpandButton.MouseEnter = function(sender, args)
        sender.over = true;
        sender:SetForeColor(clrTextButtonMouseOver);
    end
    self.ExpandButton.MouseLeave = function(sender, args)
        sender.over = false;
        sender:SetForeColor(clrTextButton);
    end
    self.ExpandButton.MouseDown = function(sender, args)
        sender:SetForeColor(clrTextButtonMouseDown);
    end
    self.ExpandButton.MouseUp = function(sender, args)
        if sender.over then
            sender:SetForeColor(clrTextButtonMouseOver);
        else
            sender:SetForeColor(clrTextButton);
        end
    end
    
    -- Create 'Collapse All' button
    self.CollapseButton = Turbine.UI.Button();
    self.CollapseButton:SetParent(self);
    self.CollapseButton:SetFont(buttonFont2);
    self.CollapseButton:SetText("-");
    self.CollapseButton:SetForeColor(clrTextButton);
    self.CollapseButton.Click = function(sender, args)
        for i, tree in ipairs(self.trees) do
            if tree:IsVisible() then
                tree:CollapseAll();
            end
        end
    end
    self.CollapseButton.MouseEnter = function(sender, args)
        sender.over = true;
        sender:SetForeColor(clrTextButtonMouseOver);
    end
    self.CollapseButton.MouseLeave = function(sender, args)
        sender.over = false;
        sender:SetForeColor(clrTextButton);
    end
    self.CollapseButton.MouseDown = function(sender, args)
        sender:SetForeColor(clrTextButtonMouseDown);
    end
    self.CollapseButton.MouseUp = function(sender, args)
        if sender.over then
            sender:SetForeColor(clrTextButtonMouseOver);
        else
            sender:SetForeColor(clrTextButton);
        end
    end

    -- Create 'Lock' button
    self.LockButton = Turbine.UI.Button();
    self.LockButton:SetParent(self);
    self.LockButton:SetBackground(fileUnlockedIcon);
    self.isLocked = false;
    self.LockButton:SetBlendMode(Turbine.UI.BlendMode.AlphaBlend);
    self.LockButton.Click = function(sender, args)
        self:SetLocked();
        self:SaveSettings();
    end

    -- Create the resize handle and set up the resize behavior
    self.resizeHandle = Turbine.UI.Control();
    self.resizeHandle:SetParent(self);
    self.resizeHandle:SetZOrder(100);
    self.resizeHandle:SetSize(20, 20);
    self.resizeHandle:SetBackground(resizeBottomRightIcon);
    self.resizeHandle:SetBlendMode(Turbine.UI.BlendMode.AlphaBlend);
    self.resizeHandle:SetPosition(self:GetWidth() - self.resizeHandle:GetWidth(), 
        self:GetHeight() - self.resizeHandle:GetHeight());

    self.resizeHandle.MouseDown = function(sender, args)
        if not self.isLocked then
            sender.dragStartX = args.X;
            sender.dragStartY = args.Y;
            self.dragging = true;
        end
    end

    self.resizeHandle.MouseMove = function(sender, args)
        local width, height = self:GetSize();
        if (self.dragging) then
            local newWidth = math.max(minWindowSize.w, width + (args.X - sender.dragStartX));
            local newHeight = math.max(minWindowSize.h, height + (args.Y - sender.dragStartY));
            self:SetSize(newWidth, newHeight);
            sender:SetPosition(self:GetWidth() - self.resizeHandle:GetWidth(), 
                self:GetHeight() - self.resizeHandle:GetHeight())
            self:PerformLayout();
        end
    end

    self.resizeHandle.MouseUp = function(sender, args)
        self.dragging = false;
    end

    -- Setup the window move behavior
    self.MouseDown = function(sender, args)
        if not self.isLocked then
            sender.dragStartX = args.X;
            sender.dragStartY = args.Y;
            self.dragging = true;
        end
    end

    self.MouseMove = function(sender, args)
        local width, height = mainWindow:GetSize();

        if (sender.dragging) then
            sender:SetPosition(mainWindow:GetLeft() + (args.X - sender.dragStartX), 
                mainWindow:GetTop() + (args.Y - sender.dragStartY));
            mainWindow:PerformLayout();
        end
    end

    self.MouseUp = function(sender, args)
        self.dragging = false;
    end

    -- Set up QuantityChanged callbacks for all items
    local backpackSize = self.backpack:GetSize();
    for j = 1, backpackSize, 1 do
        local item = self.backpack:GetItem(j);
        if item and item.QuantityChanged == nil then
            item.QuantityChanged = function(sender, args)
                if item:GetCategory() == TurbineTaskCategory then
                    mainWindow:UpdateCount(item:GetName());
                end
            end
        end
    end

    -- Backpack size changed should almost never happen
    self.backpack.SizeChanged = function(sender, args)
        -- Set up QuantityChanged callbacks for all items
        local backpackSize = self.backpack:GetSize();
        for j = 1, backpackSize, 1 do
            local item = self.backpack:GetItem(j);
            if item and item.QuantityChanged == nil then
                item.QuantityChanged = function(sender, args)
                    if item:GetCategory() == TurbineTaskCategory then
                        mainWindow:UpdateCount(item:GetName());
                    end
                end
            end
        end
    end

    -- Refresh list on backpack item added event
    self.backpack.ItemAdded = function(sender, args)
        local item = self.backpack:GetItem(args.Index);
        if item:GetCategory() == TurbineTaskCategory then
            mainWindow:UpdateCount(item:GetName());
        end
        -- Restore QuantityChanged event -- not sure if this is needed
        if item.QuantityChanged == nil then
            item.QuantityChanged = function(sender, args)
                if item:GetCategory() == TurbineTaskCategory then
                    mainWindow:UpdateCount(item:GetName());
                end
            end
        end
    end

    -- Refresh list on backpack item removed event
    self.backpack.ItemRemoved = function(sender, args)
        local item = self.backpack:GetItem(args.Index);
        if item:GetCategory() == TurbineTaskCategory then
                        itemBeingRemoved = item;
                mainWindow:UpdateCount(item:GetName(), item:GetQuantity());
                        itemBeingRemoved = nil;
        end
    end

    -- Create the trees
    self.trees = { };
    self.nOfTrees = 0;
    for i, item in ipairs(BusyBeeTreeData) do
        self.nOfTrees = self.nOfTrees + 1;
        self.trees[self.nOfTrees] = BusyBeeTree(item.data, item.left, 
            item.headerfields, item.fields);
        self.trees[self.nOfTrees]:SetParent(self);
        self.trees[self.nOfTrees]:SetVisible(self.settings.visibleTree == self.nOfTrees);
        if self.settings.treeStates ~= nil and self.settings.treeStates[self.nOfTrees] then
            self.trees[self.nOfTrees]:RestoreState(self.settings.treeStates[self.nOfTrees]);
        end
    end
    if self.settings.visibleTree < 1 or self.settings.visibleTree > self.nOfTrees then
        self.trees[1]:SetVisible(true);
    end
    
    -- Update counts
    for k, v in pairs(BusyBeeCounts) do
        for i, node in ipairs(v.nodes) do
            node:UpdateCount();
        end
    end
    
    -- Create the item count label
    self.itemLabel = Turbine.UI.Label();
    self.itemLabel:SetParent(self);
    self.itemLabel:SetPosition(10, 10);
    self.itemLabel:SetSize(200, 20);
    self.itemLabel:SetFontStyle(Turbine.UI.FontStyle.Outline);
    self.itemLabel:SetForeColor(ItemLabelColor);
    self.itemLabel:SetTextAlignment(Turbine.UI.ContentAlignment.MiddleLeft);
    self.itemLabel:SetFont(entryFont);
    self.itemLabelText = "";
    self.itemLabel:SetMouseVisible(false);
    
    self:SetLocked(self.settings.isLocked);
    self.showFullItemLabel = false;
    self:ShowIcons();
    self:UpdateAllTasks();
    self:UpdateCount();
    self:PerformLayout();
end

--------------------------------------------------------------------------------
function BusyBeeWindow:FadeIn()
    if not self.isLocked then
        self:SetOpacity(windowOpacityActive);
        self.fShowButtons = true;
--         self.resizeHandle:SetVisible(true);
    else
        self.showFullItemLabel = true;
    end
    for i, tree in ipairs(self.trees) do
        tree:ShowScrollBar(true);
    end
    self:PerformLayout();
end

--------------------------------------------------------------------------------
function BusyBeeWindow:FadeOut()
    if self.isLocked then
        self.showFullItemLabel = false;
    end
    self:SetOpacity(windowOpacityPassive);
    self.fShowButtons = false;
    for i, tree in ipairs(self.trees) do
        tree:ShowScrollBar(false);
    end
--     self.resizeHandle:SetVisible(false);
    self:PerformLayout();
    self:SaveSettings();
end

--------------------------------------------------------------------------------
function BusyBeeWindow:ShowIcons()
    for i, tree in ipairs(self.trees) do
        tree:ShowIcons(self.fShowIcons);
        tree:SetSize(listWidth, listHeight);
    end
    
end

--------------------------------------------------------------------------------
function BusyBeeWindow:Sort()
    local backpackSize = self.backpack:GetSize();
    local first = 1;
    local size = self.backpack:GetSize();
    
    -- Count how many task items
    local nTaskItems = 0;
    for i = 1, size, 1 do
        local item = self.backpack:GetItem(i);
        if item ~= nil and item:GetCategory() == TurbineTaskCategory then
            nTaskItems = nTaskItems + 1;
        end
    end
    
    writeln(string.format("Task Items: %d", nTaskItems));
    
    -- Clear out the last N items
    first = size - nTaskItems;
    for i = size, size - nTaskItems + 1, -1 do
        local item = self.backpack:GetItem(i);
        if item ~= nil and item:GetCategory() ~= TurbineTaskCategory then
            local item2 = self.backpack:GetItem(first);
            while item2 ~= nil and item2:GetCategory() ~= TurbineTaskCategory do
                first = first - 1;
                item2 = self.backpack:GetItem(first);
            end
            
            writeln(string.format("Moving: %s(%d) to %d", item:GetName(), i, first));
            self.backpack:PerformItemDrop(item, first, false);
            first = first - 1;
        end
    end
    
    first = size - nTaskItems + 1;
    for i = 1, size - nTaskItems, 1 do
        local item = self.backpack:GetItem(i);
        if item ~= nil and item:GetCategory() == TurbineTaskCategory then
            local item2 = self.backpack:GetItem(first);
            while item2 ~= nil and item2:GetCategory() == TurbineTaskCategory do
                first = first + 1;
                item2 = self.backpack:GetItem(first);
            end
            
            writeln(string.format("Moving: %s(%d) to %d", item:GetName(), i, first));
            self.backpack:PerformItemDrop(item, first, false);
            first = first + 1;
        end
    end
end

--------------------------------------------------------------------------------
function BusyBeeWindow:SetLocked(fLocked)

    -- There is only something to do if setting to a different value
    if fLocked == self.isLocked then
        return;
    end
    
    -- Flip the locked value
    self.isLocked = not self.isLocked;
    
    -- Change the appearance
    if self.isLocked then
        self.LockButton:SetBackground(fileLockedIcon);
        self:SetOpacity(windowOpacityPassive);
        self:SetBackColor(Turbine.UI.Color(0.0, 
            WindowColorParams.R, WindowColorParams.G, WindowColorParams.B));
        self.showFullItemLabel = true;
    else
        self.LockButton:SetBackground(fileUnlockedIcon);
        self:SetOpacity(windowOpacityActive);
        self:SetBackColor(Turbine.UI.Color(WindowColorParams.high / WindowColorParams.base, 
            WindowColorParams.R, WindowColorParams.G, WindowColorParams.B));
        self.showFullItemLabel = false;
    end
    
    -- Show/hide buttons
    self.fShowButtons = not self.isLocked;
    
    -- Enable or disable resizing
    self.resizeHandle:SetMouseVisible(not self.isLocked);
    
    -- Hide the main window from the mouse (tree can still be manipulated)
    self:SetMouseVisible(not self.isLocked);
    
    -- Update the layout
    self:PerformLayout();
end

--------------------------------------------------------------------------------
function BusyBeeWindow:SetPassiveOpacity(opacity)
    windowOpacityPassive = opacity;
    self:SetOpacity(windowOpacityPassive);
end

--------------------------------------------------------------------------------
function BusyBeeWindow:UpdateVisibility(item, fForceUpdate)

    local fChanged = false;
    
    if item == nil then
        for i, item2 in ipairs(BusyBeeTasks) do
            local fChanged2 = self:UpdateVisibility(item2, true);
            fChanged = fChanged or fChanged2;
        end
    elseif item.id == nil then
        for i, id in ipairs(item.ids) do
            local fChanged2 = self:UpdateVisibility(BusyBeeTasks[id]);
            fChanged = fChanged or fChanged2;
        end
    else
        local isVisible = true;
        if self.nShowCount == 2 and BusyBeeCounts[item.fullname].count < BusyBeeCounts[item.fullname].quantity then
            isVisible = false;
        elseif self.nShowCount == 1 and BusyBeeCounts[item.fullname].count == 0 then
            isVisible = false;
        elseif not self.fShowAllLevels and item.lvlrange ~= 1 then
            isVisible = false;
        elseif self.fShowRepAward and item.faction == FactionNone then
            isVisible = false;
        end
        
        if fForceUpdate or isVisible ~= item.visible then
            fChanged = true;
            item.visible = isVisible;
            for i, node in ipairs(item.nodes) do
                node:FixVisibility();
            end
        end
    end
    
    return fChanged;
end

--------------------------------------------------------------------------------
function BusyBeeWindow:PrintJunkItems()

    local backpackSize = self.backpack:GetSize();
    local fJunkItems = false;
    
    -- List task items not used for any tasks
    for i = 1, backpackSize, 1 do
        local item = self.backpack:GetItem(i);
        if (item ~= nil and item:GetCategory() == TurbineTaskCategory 
            and BusyBeeCounts[item:GetName()] == nil) then
            fJunkItems = true;
            writeln(string.format("Item %s(%d) is not used for any tasks", 
                item:GetName(), item:GetQuantity()));
        end
    end
    
    -- List task items that do not award any rep
    for i = 1, backpackSize, 1 do
        local item = self.backpack:GetItem(i);
        if (item ~= nil and BusyBeeCounts[item:GetName()] ~= nil) then
            local fRepAward = false;
            for j, id in ipairs(BusyBeeCounts[item:GetName()].ids) do
                fRepAward = fRepAward or (BusyBeeTasks[id].faction ~= FactionNone);
            end
            if not fRepAward then
                fJunkItems = true;
                writeln(string.format("Item %s(%d) is only used on tasks with no rep", 
                    item:GetName(), item:GetQuantity()));
            end
        end
    end
    
    if not fJunkItems then
        writeln("No junk items found.");
    end
end

--------------------------------------------------------------------------------
function BusyBeeWindow:UpdateCount(itemName, delta)

    local backpackSize = self.backpack:GetSize();
    local fVisibilityChanged = false;
    
    -- Recalculate backpack contents
    local counts = { };
    for i = 1, backpackSize, 1 do
        local item = self.backpack:GetItem(i);
        if item ~= nil and BusyBeeCounts[item:GetName()] ~= nil then
            if counts[item:GetName()] == nil then
                if itemName == item:GetName() and delta then
                    counts[item:GetName()] = item:GetQuantity() - delta;
                else
                    counts[item:GetName()] = item:GetQuantity();
                end
            else
                counts[item:GetName()] = counts[item:GetName()] + item:GetQuantity();
            end
        end
    end
    
    for key, value in pairs(BusyBeeCounts) do
        if itemName == nil or itemName == key then
            local count = 0;
            if counts[key] ~= nil then
                count = counts[key];
            end
            
            if BusyBeeCounts[key].count ~= count then

                -- Output items to turn in
--              if BusyBeeCounts[key].count < BusyBeeCounts[key].quantity and count >= BusyBeeCounts[key].quantity then
--                  for i, id in ipairs(BusyBeeCounts[key].ids) do
--                      if BusyBeeTasks[id].lvlrange == 1 then
--                          if BusyBeeTasks[id].faction ~= FactionNone then
--                              Turbine.Shell.WriteLine(string.format("[BusyBee] %s(%d) can be turned in at %s for %s.",
--                                  key, BusyBeeCounts[key].quantity, BusyBeeTasks[id].location, BusyBeeTasks[id].faction));
--                          else
--                              Turbine.Shell.WriteLine(string.format("[BusyBee] %s(%d) can be turned in at %s.",
--                                  key, BusyBeeCounts[key].quantity, BusyBeeTasks[id].location));
--                          end
--                      end
--                  end
--              end
                BusyBeeCounts[key].count = count;
                
                for i, node in ipairs(BusyBeeCounts[key].nodes) do
                    node:UpdateCount();
                    node:FixItemCtrl(self.backpack);
                end
                
                local fVisibilityChanged2 = self:UpdateVisibility(BusyBeeCounts[key]);
                fVisibilityChanged = fVisibilityChanged or fVisibilityChanged2;
                
            end
        end
    end
    
    if fVisibilityChanged then
        self:UpdateItemLabel();
        self:PerformLayout();
    end
end

--------------------------------------------------------------------------------
function BusyBeeWindow:UpdateItemLabel()
    local nVisible = 0;
    for key, value in pairs(BusyBeeCounts) do
        local fVisible = 0;
        for i, id in ipairs(value.ids) do
            if BusyBeeTasks[id].visible then
                fVisible = 1;
                break;
            end
        end
        nVisible = nVisible + fVisible;
    end
    if nVisible == 0 then
        self.itemLabelText = "No busy bee items";
    elseif nVisible == 1 then
        self.itemLabelText = nVisible .. " unique busy bee item";
    else
        self.itemLabelText = nVisible .. " unique busy bee items";
    end
end

--------------------------------------------------------------------------------
function BusyBeeWindow:UpdateAllTasks()
    for i, item in pairs(BusyBeeTasks) do
        local isVisible = true;
        if self.nShowCount == 2 and BusyBeeCounts[item.fullname].count < BusyBeeCounts[item.fullname].quantity then
            isVisible = false;
        elseif self.nShowCount == 1 and BusyBeeCounts[item.fullname].count == 0 then
            isVisible = false;
        elseif not self.fShowAllLevels and item.lvlrange ~= 1 then
            isVisible = false;
        elseif self.fShowRepAward and item.faction == FactionNone then
            isVisible = false;
        end
        
        if isVisible ~= item.visible then
            item.visible = isVisible;
            for i, node in ipairs(item.nodes) do
                node:FixVisibility();
            end
        end
    end
    
    self:UpdateItemLabel();
end

--------------------------------------------------------------------------------
function BusyBeeWindow:PerformLayout()
    self:Layout({ })
end

--------------------------------------------------------------------------------
function BusyBeeWindow:Layout(args)
    local width, height = self:GetSize();
    
    -- Determine filter text
    local filterCountText = "Cnt > N";
    if self.nShowCount == 0 then
        filterCountText = "All Cnt";
    elseif self.nShowCount == 1 then
        filterCountText = "Cnt > 0";
    end
    
    local filterLevelText = "My Lvl";
    if self.fShowAllLevels then
        filterLevelText = "All Lvl";
    end
    
    local filterRepText = "All Rep";
    if self.fShowRepAward then
        filterRepText = "Rep > 0";
    end
    
    local buttonLeft = marginX;
    if self.isLocked or self.fShowButtons then
        self.LockButton:SetPosition(buttonLeft, marginTop);
        self.LockButton:SetSize(buttonWidthLock, buttonHeight);
        buttonLeft = buttonLeft + buttonWidthLock;
    end
    
    -- Position the buttons
    if self.fShowButtons then
        self.ShowIconButton:SetPosition(buttonLeft, marginTop);
        self.ShowIconButton:SetSize(buttonWidth0, buttonHeight);
        buttonLeft = buttonLeft + buttonWidth0;
        self.ModeButton:SetPosition(buttonLeft, marginTop);
        self.ModeButton:SetSize(buttonWidth0, buttonHeight);
        buttonLeft = buttonLeft + buttonWidth0 + button01margin;
        self.ItemCountButton:SetPosition(buttonLeft, marginTop);
        self.ItemCountButton:SetSize(buttonWidth1, buttonHeight);
        buttonLeft = buttonLeft + buttonWidth1;
        self.AllLevelsButton:SetPosition(buttonLeft, marginTop);
        self.AllLevelsButton:SetSize(buttonWidth1, buttonHeight);
        buttonLeft = buttonLeft + buttonWidth1;
        self.RepAwardButton:SetPosition(buttonLeft, marginTop);
        self.RepAwardButton:SetSize(buttonWidth1, buttonHeight);
        buttonLeft = buttonLeft + buttonWidth1;
        
        if (buttonLeft < width - marginX - 2 * buttonWidth2) then
            buttonLeft = width - marginX - 2 * buttonWidth2;
        end
        
        self.ExpandButton:SetPosition(buttonLeft, marginTop);
        self.ExpandButton:SetSize(buttonWidth2, buttonHeight);
        buttonLeft = buttonLeft + buttonWidth2;
        self.CollapseButton:SetPosition(buttonLeft, marginTop);
        self.CollapseButton:SetSize(buttonWidth2, buttonHeight);
        buttonLeft = buttonLeft + buttonWidth2;
        
        -- Set button text
        self.ItemCountButton:SetText(filterCountText);
        self.AllLevelsButton:SetText(filterLevelText);
        self.RepAwardButton:SetText(filterRepText);
--         self.ShowIconButton:SetText(showIconText);
        
    else
        self.itemLabel:SetPosition(buttonLeft, marginTop);
        self.itemLabel:SetSize(600, buttonHeight);
        if self.showFullItemLabel then
            self.itemLabel:SetText(self.itemLabelText .. 
                string.lower(string.format(" (%s, %s, %s)",
                 filterCountText, filterLevelText, filterRepText)));
        else
            self.itemLabel:SetText(self.itemLabelText);
        end
    end
    
    -- Show or hide the buttons
    self.LockButton:SetVisible(self.isLocked or self.fShowButtons);
    self.ModeButton:SetVisible(self.fShowButtons);
    self.ItemCountButton:SetVisible(self.fShowButtons);
    self.AllLevelsButton:SetVisible(self.fShowButtons);
    self.RepAwardButton:SetVisible(self.fShowButtons);
    self.ShowIconButton:SetVisible(self.fShowButtons);
    self.ExpandButton:SetVisible(self.fShowButtons);
    self.CollapseButton:SetVisible(self.fShowButtons);
    self.itemLabel:SetVisible(not self.fShowButtons);
    self.resizeHandle:SetVisible(self.fShowButtons);
    
    -- Position the tree views
    local listTop = marginTop + buttonHeight;
    local listWidth = width - 2 * marginX;
    local listHeight = height - listTop - marginBottom;
    for i, tree in ipairs(self.trees) do
        tree:SetPosition(marginX, listTop);
        tree:SetSize(listWidth, listHeight);
    end
    
end

--------------------------------------------------------------------------------
function BusyBeeWindow:LoadSettings()
    --FIX for locale in saving routine (German version) using Vindarpatch
        --inserted by zonk, note can be removed for next version
        self.settings = BoskoPlugins.BusyBee.PatchDataLoad(Turbine.DataScope.Character, "BusyBeeSettings");
        --FIX end
    if (type(self.settings) ~= "table"  or self.settings.version ~= BusyBeeSettingsVersion) then
        self.settings = { };
        self.settings.version = BusyBeeSettingsVersion;
        self.settings.windowTop = 0;
        self.settings.windowLeft = (Turbine.UI.Display.GetWidth() - windowOriginalWidth) / 2;
        self.settings.windowWidth = windowOriginalWidth;
        self.settings.windowHeight = windowOriginalHeight;
        self.settings.passiveOpacity = windowOpacityPassive;
        self.settings.treeStates = { 0, 0, 0 };
        self.settings.visibleTree = 1;
        self.settings.isLocked = false;
        self.settings.nShowCount = 0;
        self.settings.fShowAllLevels = false;
        self.settings.fShowRepAward = false;
        self.settings.fShowIcons = false;
        Turbine.Shell.WriteLine("[BusyBee] Busy Bee Tasks initialized.");
    else
        windowOpacityPassive = self.settings.passiveOpacity;
        Turbine.Shell.WriteLine("[BusyBee] Busy Bee Tasks loaded.");
    end
    
    if self.settings.windowLeft + self.settings.windowWidth > Turbine.UI.Display.GetWidth() then
        self.settings.windowLeft = Turbine.UI.Display.GetWidth() - self.settings.windowWidth;
    end
    if self.settings.windowTop + self.settings.windowHeight > Turbine.UI.Display.GetHeight() then
        self.settings.windowTop = Turbine.UI.Display.GetHeight() - self.settings.windowHeight;
    end
end

--------------------------------------------------------------------------------
function BusyBeeWindow:SaveSettings()
    local width, height = self:GetSize();
    local left, top = self:GetPosition();
    
    local visibleTree = 1;
    treeStates = { };
    for i, tree in ipairs(self.trees) do
        if tree:IsVisible() then
            visibleTree = i;
        end
        treeStates[i] = tree:GetState();
    end

    if (self.settings.windowTop ~= top or
        self.settings.windowLeft ~= left or
        self.settings.windowWidth ~= width or
        self.settings.windowHeight ~= height or
        self.settings.passiveOpacity ~= windowOpacityPassive or
        self.settings.treeStates ~= treeStates or
        self.settings.visibleTree ~= visibleTree or
        self.settings.isLocked ~= self.isLocked or
        self.settings.nShowCount ~= self.nShowCount or
        self.settings.fShowAllLevels ~= self.fShowAllLevels or
        self.settings.fShowRepAward ~= self.fShowRepAward or
        self.settings.fShowIcons ~= self.fShowIcons) then
        
        self.settings.windowTop = top;
        self.settings.windowLeft = left;
        self.settings.windowWidth = width;
        self.settings.windowHeight = height;
        self.settings.passiveOpacity = windowOpacityPassive;
        self.settings.treeStates = treeStates;
        self.settings.visibleTree = visibleTree;
        self.settings.isLocked = self.isLocked;
        self.settings.nShowCount = self.nShowCount;
        self.settings.fShowAllLevels = self.fShowAllLevels;
        self.settings.fShowRepAward = self.fShowRepAward;
        self.settings.fShowIcons = self.fShowIcons;
        
        --FIX for locale in saving routine (German version) using Vindarpatch
                --inserted by zonk, note can be removed for next version
                BoskoPlugins.BusyBee.PatchDataSave(Turbine.DataScope.Character, "BusyBeeSettings", self.settings);
                --FIX end
--         Turbine.Shell.WriteLine("[BusyBee] Busy Bee Tasks saved.");
    end
end

Compare with Previous | Blame


All times are GMT -5. The time now is 05:30 AM.


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