WoWInterface SVN Ara_Broker_Tradeskills

[/] [trunk/] [Ara_Broker_Tradeskills.lua] - Rev 7

Compare with Previous | Blame | View Log

-- TODO: save/restore skill filters if window is opened

local f = CreateFrame("Frame", "AraSkills", UIParent)
local g = CreateFrame("Frame", nil, UIParent)
local e = CreateFrame"Frame"
local dropdown = CreateFrame("Frame", "AraSkillsDD", nil, "UIDropDownMenuTemplate")
local dropdown_init, tipShown, nbEntries, Block_OnEnter

local config, realm, options, c
local playerName, realmName = GetUnitName"player", GetRealmName()
local CDs, shortcuts, workingFrame, prevAltSkill

local BUTTON_HEIGHT, ICON_SIZE, GAP, TEXT_OFFSET = 17, 15, 10, 4
local PANEL, PROFESSION, COMBAT_SKILL, LINK, SECONDARY, SPELL_ID = 1, 2, 4, 8, 16, 256

local L = setmetatable( _G.ABTS_T or {}, { __index = function(t,k) return k end, __call = function(t,k) return t[k] end } )

local bit_band, date, format, strmatch, tonumber, wipe, ipairs, tremove, GetSkillLineInfo, GetSpellInfo, GetTradeSkillInfo, GetTradeSkillRecipeLink, tip =
        bit.band, date, format, strmatch, tonumber, wipe, ipairs, tremove, GetSkillLineInfo, GetSpellInfo, GetTradeSkillInfo, GetTradeSkillRecipeLink

local skills = {
        [L.Alchemy]=            PROFESSION +  2259*SPELL_ID + PANEL + LINK,
        [L.Blacksmithing]=      PROFESSION +  2018*SPELL_ID + PANEL + LINK,
        [L.Cooking]=            PROFESSION +  2550*SPELL_ID + PANEL + LINK + SECONDARY, -- campfire
        [L.Enchanting]=         PROFESSION +  7411*SPELL_ID + PANEL + LINK, -- d/e
        [L.Engineering]=        PROFESSION +  4036*SPELL_ID + PANEL + LINK,
        [L"First Aid"]=         PROFESSION +  3273*SPELL_ID + PANEL + LINK + SECONDARY,
        [L.Fishing]=            PROFESSION +  7620*SPELL_ID + SECONDARY,
        [L.Herbalism]=          PROFESSION +  2366*SPELL_ID, -- track herbs
        [L.Inscription]=        PROFESSION + 45357*SPELL_ID + PANEL + LINK, -- mill
        [L.Jewelcrafting]=      PROFESSION + 25229*SPELL_ID + PANEL + LINK, -- prospect
        [L.Leatherworking]=     PROFESSION +  2108*SPELL_ID + PANEL + LINK,
        [L.Lockpicking]=        PROFESSION +  1804*SPELL_ID + SECONDARY,
        [L.Runeforging]=        PROFESSION + 53428*SPELL_ID + PANEL + SECONDARY,
        [L.Skinning]=           PROFESSION +  8613*SPELL_ID,
        [L.Smelting]=           PROFESSION +  2580*SPELL_ID + PANEL, -- track minerals (icons: 2575=mining, 2580=smelting)
        [L.Tailoring]=          PROFESSION +  3908*SPELL_ID + PANEL + LINK,
        [L.Axes]=               COMBAT_SKILL,
        [L.Bows]=               COMBAT_SKILL,
        [L.Crossbows]=          COMBAT_SKILL,
        [L.Daggers]=            COMBAT_SKILL,
        [L.Defense]=            COMBAT_SKILL,
        [L.Guns]=               COMBAT_SKILL,
        [L.Maces]=              COMBAT_SKILL,
        [L.Polearms]=           COMBAT_SKILL,
        [L.Staves]=             COMBAT_SKILL,
        [L.Swords]=             COMBAT_SKILL,
        [L.Thrown]=             COMBAT_SKILL,
        [L"Two-Handed Swords"]= COMBAT_SKILL,
        [L"Two-Handed Axes"]=   COMBAT_SKILL,
        [L"Two-Handed Maces"]=  COMBAT_SKILL,
        [L.Unarmed]=            COMBAT_SKILL,
        [L.Wands]=              COMBAT_SKILL,
        None =                  0
}
local altNames = {
        [L.Mining] =            L.Smelting,
        [L.RuneforgingDN] =     L.Runeforging,
}
local altIcons = {
        [L.Axes]=               "Interface\\Icons\\INV_Axe_44",
        [L.Bows]=               "Interface\\Icons\\INV_Weapon_Bow_05",
        [L.Crossbows]=          "Interface\\Icons\\INV_Weapon_Crossbow_01",
        [L.Daggers]=            "Interface\\Icons\\Ability_SteelMelee",
        [L.Defense]=            "Interface\\Icons\\INV_Shield_09",
        [L.Guns]=               "Interface\\Icons\\INV_Weapon_Rifle_01",
        [L.Maces]=              "Interface\\Icons\\INV_Mace_04",
        [L.Polearms]=           "Interface\\Icons\\INV_Spear_06",
        [L.Staves]=             "Interface\\Icons\\INV_Staff_08",
        [L.Swords]=             "Interface\\Icons\\INV_Sword_14",
        [L.Thrown]=             "Interface\\Icons\\Ability_Throw",
        [L"Two-Handed Axes"]=   "Interface\\Icons\\INV_Axe_09",
        [L"Two-Handed Maces"]=  "Interface\\Icons\\INV_Hammer_16",
        [L"Two-Handed Swords"]= "Interface\\Icons\\INV_Sword_27",
        [L.Unarmed]=            "Interface\\Icons\\Ability_Warrior_SecondWind",
        [L.Wands]=              "Interface\\Icons\\INV_Wand_05",
}
local groupIcons = {
        ["Transmutes"] = "Interface\\Icons\\Spell_Nature_ElementalPrecision_2"
}

local HOUR, DAY = 3600, 86400
local TRANSMUTE_G1 = { skill= L.Alchemy, CD=20*HOUR, group= "Transmutes" }

local watchedCDs = {
        [11479]= TRANSMUTE_G1, -- Iron to Gold
        [11480]= TRANSMUTE_G1, -- Mithril to Truesilver
        [60350]= TRANSMUTE_G1, -- Titanium
        [17559]= TRANSMUTE_G1, -- Air to Fire
        [17566]= TRANSMUTE_G1, -- Earth to Life
        [17561]= TRANSMUTE_G1, -- Earth to Water
        [17560]= TRANSMUTE_G1, -- Fire to Earth
        [17565]= TRANSMUTE_G1, -- Life to Earth
        [17563]= TRANSMUTE_G1, -- Undeath to Earth
        [17562]= TRANSMUTE_G1, -- Water to Air
        [17564]= TRANSMUTE_G1, -- Water to Undeath
        [28566]= TRANSMUTE_G1, -- Primal Air to Fire
        [28585]= TRANSMUTE_G1, -- Primal Earth to Life
        [28567]= TRANSMUTE_G1, -- Primal Earth to Water
        [28568]= TRANSMUTE_G1, -- Primal Fire to Earth
        [28583]= TRANSMUTE_G1, -- Primal Fire to Mana
        [28584]= TRANSMUTE_G1, -- Primal Life to Earth
        [28582]= TRANSMUTE_G1, -- Primal Mana to Fire
        [28580]= TRANSMUTE_G1, -- Primal Shadow to Water
        [28569]= TRANSMUTE_G1, -- Primal Water to Air
        [28581]= TRANSMUTE_G1, -- Primal Water to Shadow
        [53777]= TRANSMUTE_G1, -- Eternal Air to Earth
        [52776]= TRANSMUTE_G1, -- Eternal Air to Water
        [53781]= TRANSMUTE_G1, -- Eternal Earth to Air
        [53782]= TRANSMUTE_G1, -- Eternal Earth to Shadow
        [53775]= TRANSMUTE_G1, -- Eternal Fire to Life
        [53774]= TRANSMUTE_G1, -- Eternal Fire to Water
        [53773]= TRANSMUTE_G1, -- Eternal Life to Fire
        [53771]= TRANSMUTE_G1, -- Eternal Life to Shadow
        [54020]= TRANSMUTE_G1, -- Eternal Might
        [53779]= TRANSMUTE_G1, -- Eternal Shadow to Earth
        [52780]= TRANSMUTE_G1, -- Eternal Shadow to Life
        [53783]= TRANSMUTE_G1, -- Eternal Water to Air
        [53784]= TRANSMUTE_G1, -- Eternal Water to Fire
        [28027]= { skill= L.Enchanting,         CD= 2*DAY         },    -- Prismatic Sphere
        [28028]= { skill= L.Enchanting,         CD= 2*DAY         },    -- Void Sphere
        [47280]= { skill= L.Jewelcrafting,      CD=       20*HOUR },    -- Brilliant Glass
        [62242]= { skill= L.Jewelcrafting,      CD=       20*HOUR },    -- Icy Prism            -- NEW 3.0.8
        [56002]= { skill= L.Tailoring,          CD= 3*DAY+20*HOUR },    -- Ebonweave
        [56003]= { skill= L.Tailoring,          CD= 3*DAY+20*HOUR },    -- Spellweave
        [56001]= { skill= L.Tailoring,          CD= 3*DAY+20*HOUR },    -- Moonshroud
        [61288]= { skill= L.Inscription,        CD=       20*HOUR },    -- Minor Glyph RS
        [61177]= { skill= L.Inscription,        CD=       20*HOUR },    -- Major Glyph RS
        [55208]= { skill= L.Smelting,           CD=       20*HOUR },    -- Smelt Titansteel
        [60893]= { skill= L.Alchemy,            CD= 2*DAY+20*HOUR },    -- Alchemy Research
}

local watchedIndex, watchedSpell, watchedID, watchedIcon
local orgDoTradeSkill = _G.DoTradeSkill

function DoTradeSkill(index, ...)
        watchedID = tonumber( strmatch( GetTradeSkillRecipeLink(index), "enchant:(%d+)" ) )
        if watchedCDs[watchedID] then
                watchedIndex, watchedSpell, watchedIcon = index, GetTradeSkillInfo(index), GetTradeSkillIcon(index)
                f:RegisterEvent"UNIT_SPELLCAST_SUCCEEDED"
                f:RegisterEvent"UNIT_SPELLCAST_STOP"
        end
        return orgDoTradeSkill(index, ...)
end

function f:UNIT_SPELLCAST_STOP(event, unit, spell)
        if unit ~= "player" or spell ~= watchedSpell then return end
        self:UnregisterEvent"UNIT_SPELLCAST_SUCCEEDED"
        self:UnregisterEvent"UNIT_SPELLCAST_STOP"
        if event ~= "UNIT_SPELLCAST_SUCCEEDED" then return end
        local cd = watchedCDs[watchedID]
        char[watchedID] = time() + cd.CD
        if cd.group then config.names[cd.group] = watchedID end
        config.icons[cd.group or watchedID] = watchedIcon
        if config.displayCDs then e.timer = 0 end
        if tipShown then Block_OnEnter(tipShown) end
end
f.UNIT_SPELLCAST_SUCCEEDED = f.UNIT_SPELLCAST_STOP


local tables = {}

local function new(...)
        local t = tremove(tables) or {}
        for i = 1, select("#", ...), 2 do
                local k, v = select(i, ...)
                t[k] = v
        end
        return t
end

local function del(t)
        tables[#tables+1] = wipe(t)
end


local md, mx, xx, dd = 20*HOUR, 3*DAY+20*HOUR, { 86400, 3600, 60, 1 }, { L.d, L.h, L.m, L.s }
local function SecondsToTime(s)
        local p = s<md and s/md*.5 or s<mx and (s-md)/(mx-md)*.5+.5 or 1
        local i = s>=86400 and 1 or s>=3600 and 2 or 3
        return format( "|cff%.2x%.2x40%i%s %.2i%s|r",
                p<.5 and 64+p*382 or 255, p<.5 and 255 or 446-p*382,
                floor(s/xx[i]), dd[i], floor((s%xx[i])/xx[i+1]), dd[i+1] )
end

local function ColorCurMax(cur,max,isProfession)
        local pMax = isProfession and min(max/450,1) or 1
        local pCur = isProfession and 1-min(sqrt(max-cur)/10,1) or 2/sqrt(max-cur+4)
        return format( "|cff%.2x%.2x00%.3i|r / |cff%.2x%.2x00%.3i|r",
                pCur<.5 and 255 or 510-pCur*510, pCur<.5 and pCur*510 or 255, cur,
                pMax<.5 and 255 or 510-pMax*510, pMax<.5 and pMax*510 or 255, max )
end

local function SortSkills(a, b)
        if not a.isProfession == not b.isProfession then
                if a.primary == b.primary then
                        return a.displayName < b.displayName
                else    return a.primary
                end
        else return a.isProfession end
end

local function SortCooldowns(a, b)
        if a.group and a.group == b.group then
                return a.timeLeft > b.timeLeft
        else    return a.name < b.name end
end

local function SortShortcuts(a, b)
        if a.skill == b.skill then
                return a.name < b.name
        else    return a.skill < b.skill end
end

local function SortAltSkills(a, b)
        local primA = bit_band( skills[a], SECONDARY ) == 0
        local primB = bit_band( skills[b], SECONDARY ) == 0
        if primA == primB then
                return a < b
        else    return primA end
end

local function SortAlts(a, b)
        return a.name < b.name
end


local function UpdateText()
        f.block.icon = select(3, GetSpellInfo(char.skill or 0)) or "Interface\\Icons\\Achievement_Halloween_Witch_01"
        if not config.displayCDs then f.block.text = " " .. (char.skill or _G.TRADE_SKILLS) end
end

local orgAbandonSkill = _G.AbandonSkill
function AbandonSkill(...)
        if char.skill == GetSkillLineInfo(...) then char.skill=nil UpdateText() end
        return orgAbandonSkill(...)
end

local function CreateHL(frame)
        local hl = frame:CreateTexture()
        hl:SetTexture"Interface\\QuestFrame\\UI-QuestLogTitleHighlight"
        hl:SetBlendMode"ADD"
        hl:SetAlpha(0)
        return hl
end
local highlight = CreateHL(f)
local altLight = CreateHL(g)


local function Tradeskill_OnClick(button, click)
        if IsShiftKeyDown() and bit_band( skills[button.skill], LINK ) > 0 then
                f.getLink = true
                if TradeSkillFrame and TradeSkillFrame:IsShown() then
                        f.keepOpen = true
                        CloseTradeSkill()
                end
                f:RegisterEvent"TRADE_SKILL_SHOW"
        elseif click == "RightButton" then
                f.learnCD = true
                if TradeSkillFrame and TradeSkillFrame:IsShown() then
                        f.keepOpen = true
                        CloseTradeSkill()
                end
                f:RegisterEvent"TRADE_SKILL_SHOW"
        elseif click == "MiddleButton" then
                char.show[button.skill] = not char.show[button.skill]
                return Block_OnEnter(tipShown)
        elseif bit_band( skills[button.skill], PANEL ) > 0 then
                char.skill = button.skill
                UpdateText()
                button.fontLeft:SetTextColor(unpack(config.colors.selected))
                if f.selected and f.selected ~= button then f.selected.fontLeft:SetTextColor(unpack(config.colors.activeName)) end
                f.selected = button
        else return end
        CastSpellByName(button.skill)
end

local function Cooldown_OnClick(button, click)
        local group = watchedCDs[button.spellID].group
        if click == "MiddleButton" then
                local char = realm[button.owner]
                if group then -- remove group
                        for k, v in next, watchedCDs do
                                if v.group == group then char[k] = nil end
                        end
                else
                        char[button.spellID] = nil
                end
                Block_OnEnter(tipShown)
        elseif click == "LeftButton" and IsControlKeyDown() then
                f.alias, f.aliasID = button.fontLeft:GetText(), group or button.spellID
                StaticPopup_Show("SET_ALIAS", config.names[button.spellID] or group)
        else
                f.doTradeSkill = click == "LeftButton" and not IsModifierKeyDown()
                f.searchedSpellID = button.spellID
                if TradeSkillFrame and TradeSkillFrame:IsShown() then CloseTradeSkill() end
                f:RegisterEvent"TRADE_SKILL_SHOW"
                CastSpellByName(button.skill)
        end
end

local function Shortcut_OnClick(button, click)
        if IsControlKeyDown() then
                f.alias, f.aliasID = button.fontLeft:GetText(), button.spellID
                StaticPopup_Show("SET_ALIAS", config.names[button.spellID])
        elseif click == "MiddleButton" then
                char.shortcuts[button.spellID] = nil
                Block_OnEnter(tipshown)
        else
                f.doTradeSkill = click == "LeftButton" and not IsModifierKeyDown()
                f.searchedSpellID = button.spellID
                f.shortcut = true
                if TradeSkillFrame and TradeSkillFrame:IsShown() then CloseTradeSkill() end
                f:RegisterEvent"TRADE_SKILL_SHOW"
                CastSpellByName(button.skill)
        end
end

local function Link_OnClick(button, click)
        if not button.link then return end
        if IsModifierKeyDown() then
                if not ChatEdit_InsertLink(button.link) then
                        ChatFrameEditBox:Show()
                        ChatEdit_InsertLink(button.link)
                end
        elseif click == "LeftButton" then
                if button.skill == prevAltSkill and TradeSkillFrame and TradeSkillFrame:IsShown() then
                        return CloseTradeSkill()
                end
                prevAltSkill = button.skill
                SetItemRef(button.link:match"|H([^|]+)")
        end
end

local function ProfessionsBook_OnClick()
        ProfessionsBook:ShowFrame()
end


local function Alt_OnEnter(self)
        if self and self.light then
                altLight:SetAllPoints(self)
                altLight:SetAlpha(1)

                if config.hideTips then return end
                local hasLink = bit_band( skills[self.skill], LINK ) > 0
                if not( self.link or hasLink ) then return end

                tip:SetOwner(g, "ANCHOR_NONE")
                local showRight = f:GetCenter() > UIParent:GetWidth()/2 and "RIGHT" or "LEFT"
                local showBelow = select(2, g:GetCenter()) > UIParent:GetHeight()/2 and "TOP" or "BOTTOM"
                tip:SetPoint(showBelow..showRight, g, (showBelow == "TOP" and "BOTTOM" or "TOP")..showRight )

                tip:AddLine(L.Hints)
                if self.link then
                        tip:AddLine(L"|cffff8020Click|r to toggle panel.", .2,1,.2)
                        tip:AddLine(L"|cffff8020Shift+Click|r to link in chat.", .2,1,.2)
                elseif hasLink then
                        tip:AddLine(L"No link available. Open a character\ntradeskill panel to be able to access\nit from your other characters.", 1, .6, .2)
                end
                tip:Show()
        end
end

local function Alt_OnLeave(self)
        altLight:ClearAllPoints()
        if self and self.light then
                altLight:SetAlpha(0)
                tip:Hide()
        end
        if not MouseIsOver(f) and not MouseIsOver(g) then
                tipShown = nil
                f:Hide() g:Hide() tip:Hide()
        end
end

local function Menu_OnEnter(self)
        if self and self.light then
                highlight:SetAllPoints(self)
                highlight:SetAlpha(1)
                if config.hideTips or self.skill == "None" then return end
                tip:SetOwner(f, "ANCHOR_NONE")
                local showRight = f:GetCenter() > UIParent:GetWidth()/2 and "LEFT" or "RIGHT"
                local showBelow = select(2, f:GetCenter()) > UIParent:GetHeight()/2 and "TOP" or "BOTTOM"
                tip:SetPoint(showBelow..showRight, f, (showBelow == "TOP" and "BOTTOM" or "TOP")..showRight )
                tip:AddLine(L.Hints)
                if self.shortcut then
                        tip:AddLine(L"|cffff8020Click|r to craft.", .2,1,.2)
                        tip:AddLine(L"|cffff8020Right-Click|r to show in panel.", .2,1,.2)
                        tip:AddLine(L"|cffff8020Control+Click|r to set an alias.", .2, 1, .2)
                elseif self.spellID then
                        tip:AddDoubleLine(L"Belongs to:", self.owner, 1,1,1,1,1,1)
                        if self.owner == playerName and self.ready then
                                tip:AddLine(L"|cffff8020Click|r to craft.", .2,1,.2)
                                tip:AddLine(L"|cffff8020Right-Click|r to show CD in panel.", .2,1,.2)
                        elseif char.show[watchedCDs[self.spellID].skill] ~= nil then
                                tip:AddLine(L"|cffff8020Click|r to show CD in panel.", .2,1,.2)
                        end
                        tip:AddLine(L"|cffff8020Control+Click|r to set an alias.", .2, 1, .2)
                else
                        local hasPanel = bit_band( skills[self.skill], PANEL ) > 0
                        local hasLink = bit_band( skills[self.skill], LINK ) > 0
                        if hasPanel then tip:AddLine(L"|cffff8020Click|r to toggle panel.", .2,1,.2) end
                        if hasLink then tip:AddLine(L"|cffff8020Shift+Click|r to link in chat.", .2,1,.2) end
                        if hasPanel then tip:AddLine(L"|cffff8020Right-Click|r to scan for CDs.", .2,1,.2) end
                end
                tip:AddLine(L"|cffff8020Middle-Click|r to remove from list.", .2, 1, .2)
                tip:Show()
        end
end

local function Menu_OnLeave(self)
        highlight:ClearAllPoints()
        if self and self.light then
                highlight:SetAlpha(0)
                tip:Hide()
        end
        if not MouseIsOver(f) and ( InCombatLockdown() or not MouseIsOver(g) ) then
                tipShown = nil
                f:Hide() g:Hide() tip:Hide()
        end
end


local metatable = { __index = function(table, key)
        local button = CreateFrame("Button", nil, workingFrame)
        rawset(table, key, button)
        button:RegisterForClicks"AnyUp"
        button:SetHeight(BUTTON_HEIGHT)
        button:SetPoint("TOPLEFT", workingFrame, "TOPLEFT", GAP, BUTTON_HEIGHT * (1-key) - GAP)
        button:SetScript("OnEnter", workingFrame == f and Menu_OnEnter or Alt_OnEnter)
        button:SetScript("OnLeave", workingFrame == f and Menu_OnLeave or Alt_OnLeave)
        button.icon = button:CreateTexture(nil, "OVERLAY")
        button.icon:SetWidth(ICON_SIZE) button.icon:SetHeight(ICON_SIZE)
        button.icon:SetPoint("LEFT", button, "LEFT")
        button.fontLeft = button:CreateFontString(nil, "OVERLAY", "SystemFont_Shadow_Med1")
        button.fontLeft:SetPoint("LEFT", button, "LEFT", ICON_SIZE + TEXT_OFFSET, 0)
        button.fontRight = button:CreateFontString(nil, "OVERLAY", "SystemFont_Shadow_Med1")
        button.fontRight:SetPoint("RIGHT", button, "RIGHT")
        return button
end }

local buttons = setmetatable( {}, metatable )
local altButtons = setmetatable( {}, metatable )


local function AddEntry( buttons, icon, leftText, leftTextColor, rightText, rightTextColor, highlight )
        nbEntries = nbEntries + 1
        local button = buttons[nbEntries]
        button.icon:SetTexture(icon or "")
        if icon then button.icon:SetTexCoord(0.075, 0.925, 0.075, 0.925) end
        button.fontLeft:SetText(leftText or "")
        if leftTextColor then button.fontLeft:SetTextColor(unpack(leftTextColor)) end
        button.fontRight:SetText(rightText or "")
        if rightTextColor then button.fontRight:SetTextColor(unpack(rightTextColor)) end
        button.light = highlight
        button:Show()
        return button, ICON_SIZE + TEXT_OFFSET + (leftText and button.fontLeft:GetStringWidth() or 0), rightText and button.fontRight:GetStringWidth() or 0
end


local function UpdateCDs()
        local readyCDs = 0
        local temp = new()
        local dups = new()
        local now = time()

        for i, v in next, CDs do
                del(v)
                CDs[i] = nil
        end

        for char, data in next, realm do
                for spellID, expireTime in next, data do
                        if type(spellID)=="number" then
                                local cd = watchedCDs[spellID]
                                if cd then
                                        temp[#temp+1] = new(
                                                "name", cd.group and (config.aliases[cd.group] or cd.group) or config.aliases[spellID] or config.names[spellID] or GetSpellInfo(spellID),
                                                "timeLeft", expireTime-now,
                                                "char", char,
                                                "spellID", spellID,
                                                "skill", cd.skill,
                                                "group", cd.group)
                                end
                        end
                end
        end
        sort(temp, SortCooldowns)

        for i=1, #temp do
                local cd = temp[i]
                if not dups[cd.group] then
                        CDs[#CDs+1] = cd
                        if cd.timeLeft <= 0 then readyCDs = readyCDs + 1 end
                end
                if cd.group then dups[cd.group] = true end
        end

        del(temp)
        del(dups)

        if config.displayCDs then
        --      local perc = readyCDs/totalCDs
        --      f.block.text = format(" |cff%.2x%.2x00%i/%i|r CD%s", perc<.5 and 255 or 510-perc*510, perc<.5 and perc*510 or 255, readyCDs, totalCDs, totalCDs>0 and "s" or "")
                f.block.text = format(" %i/%i CD%s", readyCDs, #CDs, #CDs>1 and "s" or "")
        end
end

local function SetMainPoint()

end

local function ShowAlts()
        workingFrame = g
        nbEntries = 0
        altLight:SetVertexColor(unpack(config.colors.highlight))
        local firstEntry, button = true
        local itemLeftWidth, itemRightWidth, colRightWidth, colLeftWidth = 0, 0, 0, 0
        local list, subList = new(), new()

        for charName, charData in next, realm do
                if charName ~= playerName and charData.curSkills and next(charData.curSkills) then
                        list[#list+1] = new("name",charName,"curSkills",charData.curSkills,"maxSkills",charData.maxSkills,"links",charData.links)
                end
        end
        sort(list,SortAlts)
        for _, char in ipairs(list) do
                if firstEntry then firstEntry = false else AddEntry(altButtons) end
                AddEntry( altButtons, "", char.name, config.colors.header )
                for skill, value in next, char.maxSkills do
                        if value > 1 then subList[#subList+1] = skill end
                end
        --      sort(subList)
                sort(subList, SortAltSkills)
                for _, skill in ipairs(subList) do
                        local bitSkill = skills[skill]
                        if not config.primaryOnly or bit_band(bitSkill, SECONDARY) == 0 then
                                local hasLink = bit_band(bitSkill, LINK) > 0
                                local color = hasLink and config.colors.activeName or config.colors.infoName
                                local icon = select(3,GetSpellInfo(floor(bitSkill/SPELL_ID)))
                                local strValues = ColorCurMax( char.curSkills[skill], char.maxSkills[skill], true )

                                button, itemLeftWidth, itemRightWidth = AddEntry( altButtons,
                                        icon, skill, color, strValues, nil, hasLink)
                                button:SetScript("OnClick", Link_OnClick)
                                button.link = char.links and char.links[skill]
                                button.skill = skill
                                if itemLeftWidth > colLeftWidth then colLeftWidth = itemLeftWidth + GAP end
                                if itemRightWidth > colRightWidth then colRightWidth = itemRightWidth end
                        end
                end
                wipe(subList)
        end
        del(list)
        del(subList)
        g:Show()
        if _G.Skinner then _G.Skinner:applySkin(g) end
        if InCombatLockdown() then return end

        local maxWidth = colLeftWidth + colRightWidth
        for i=1, nbEntries do altButtons[i]:SetWidth(maxWidth) end
        g:SetWidth(maxWidth + GAP*2)
        g:SetHeight(BUTTON_HEIGHT * nbEntries + GAP*2)

        local horiz = f:GetCenter() > UIParent:GetWidth()/2 and "RIGHT" or "LEFT"
        local verti = select(2, f:GetCenter()) > UIParent:GetHeight()/2 and "TOP" or "BOTTOM"
        g:ClearAllPoints()
        g:SetPoint(verti..horiz, f, verti..(horiz=="LEFT"and"RIGHT"or"LEFT"))

        for i=nbEntries+1, #altButtons do altButtons[i]:Hide() end
        if nbEntries == 0 then g:Hide() end
end


Block_OnEnter = function(self)
        workingFrame = f
        highlight:SetVertexColor(unpack(config.colors.highlight))
        f:Show()
        if InCombatLockdown() then return else CloseDropDownMenus() end
        tipShown = tipShown or self
        Menu_OnEnter(tipShown)
        nbEntries = 0
        local char = realm[playerName] -- dirty fix

        local firstCombatSkill, button, selectedSkillExists = true
        local itemLeftWidth, itemRightWidth, colRightWidth, colLeftWidth, skillWidth, cdWidth = 0, 0, 0, 0

        UpdateCDs()
        local showCDs, showProfessions, showCombatSkills, showShortcuts = min(#CDs,1), 0, 0, next(char.shortcuts) and 1 or 0
        for k,v in next,f.skills do del(v) f.skills[k]=nil end

        ExpandSkillHeader(0)
        for i=1, GetNumSkillLines() do
                local displayName, skill, skillName, value, _, _, valueMax = GetSkillLineInfo(i)
                skillName = altNames[displayName] or displayName
                skill = skills[skillName]
                if skill then
                        local hasPanel, isProfession = bit_band(skill,PANEL)>0, bit_band(skill,PROFESSION)>0
                        if char.show[skillName] == nil then char.show[skillName] = hasPanel end
                        local show = (isProfession and not char.hideProfessions or not isProfession and not char.hideCombatSkills) and char.show[skillName]
                        if show then if isProfession then showProfessions=1 else showCombatSkills=1 end end
                        f.skills[#f.skills+1] = new(
                                "displayName",  displayName,
                                "skillName",    skillName,
                                "isProfession", isProfession and floor(skill/SPELL_ID),
                                "isCombatSkill",not isProfession,
                                "hasPanel",     hasPanel,
                                "color",        skillName==char.skill and config.colors.selected or hasPanel and config.colors.activeName or config.colors.infoName,
                                "strValue",     valueMax <= 1 and "" or ColorCurMax(value,valueMax,isProfession),
                                "show",         show,
                                "primary",      bit_band(skill,SECONDARY) == 0)
                        if isProfession then char.curSkills[skillName], char.maxSkills[skillName] = value, valueMax end
                end
        end
        local nbHeaders = showProfessions + showCombatSkills + showCDs + showShortcuts

        sort(f.skills, SortSkills)
        for i, v in ipairs(f.skills) do
                if v.show then
                        if nbEntries == 0 and showProfessions == 1 and nbHeaders > 1 then
                                AddEntry(buttons, "", _G.TRADE_SKILLS, config.colors.header)
                        end
                        if v.isCombatSkill and firstCombatSkill and nbHeaders > 1 then
                                firstCombatSkill = false
                                if nbEntries > 0 then AddEntry(buttons) end
                                AddEntry(buttons, "", L"Combat Skills", config.colors.header)
                        end
                        local texture = altIcons[v.skillName] or select(3,GetSpellInfo(v.isProfession)) or ""
                        button, itemLeftWidth, itemRightWidth = AddEntry( buttons,
                                texture, v.displayName, v.color, v.strValue,
                                nil, true)
                        button:SetScript("OnClick", Tradeskill_OnClick)
                        button.skill, button.spellID, button.shortcut = v.skillName
                        if v.color == config.colors.selected then f.selected = button end
                        if itemLeftWidth > colLeftWidth then colLeftWidth = itemLeftWidth end
                        if itemRightWidth > colRightWidth then colRightWidth = itemRightWidth end
                end
        end
        skillWidth = colLeftWidth + colRightWidth
        colRightWidth, colLeftWidth = 0, 0, 0, 0

        -- Cooldowns
        local dups = new()
        for index, data in ipairs(CDs) do
                local group = data.group
                if not group or not dups[group] then
                        if group then dups[group] = true end
                        if index == 1 then
                                if nbEntries>0 then AddEntry(buttons) end
                                if nbHeaders>1 then AddEntry(buttons, "", L.Cooldowns, config.colors.header) end
                        end
                        button, itemLeftWidth, itemRightWidth = AddEntry( buttons,
                                config.icons[group or data.spellID] or config.names[group] and select(3,GetSpellInfo(config.names[group])) or groupIcons[group], data.name,
                                data.char == playerName and config.colors.ownCD or config.colors.foreignCD,
                                data.timeLeft<=0 and L"|cff20ff20Ready!|r" or SecondsToTime(data.timeLeft),
                                nil, true )
                        button:SetScript("OnClick", Cooldown_OnClick)
                        button.skill, button.spellID, button.ready, button.owner, button.shortcut = data.skill, group and config.names[group] or data.spellID, data.timeLeft<=0, data.char
                        if itemLeftWidth > colLeftWidth then colLeftWidth = itemLeftWidth end
                        if itemRightWidth > colRightWidth then colRightWidth = itemRightWidth end
                end
        end
        del(dups)
        cdWidth = colLeftWidth + colRightWidth
        colLeftWidth = 0

        -- Shortcuts
        local shortcuts = new()
        for spellID, skill in next, char.shortcuts do
                shortcuts[#shortcuts+1] = new(
                        "skill", skill,
                        "name", config.aliases[spellID] or config.names[spellID],
                        "spellID", spellID)
        end
        sort(shortcuts, SortShortcuts)
        local firstShortcut = true
        for _, shortcut in ipairs(shortcuts) do
                if firstShortcut then
                        firstShortcut = false
                        if nbEntries>0 then AddEntry(buttons) end
                        if nbHeaders>1 then AddEntry(buttons, "", L.Shortcuts, config.colors.header) end
                end
                button, itemLeftWidth = AddEntry( buttons,
                        config.icons[shortcut.spellID], shortcut.name, config.colors.activeName, nil, nil, true )
                if itemLeftWidth > colLeftWidth then colLeftWidth = itemLeftWidth end
                button:SetScript("OnClick", Shortcut_OnClick)
                button.skill, button.spellID, button.ready, button.shortcut = shortcut.skill, shortcut.spellID, true, true
        end
        del(shortcuts)

        -- ProfessionsBook
        if ProfessionsBook then
                if nbEntries>0 then AddEntry(buttons) end
                if nbHeaders>1 then AddEntry(buttons, "", L"Miscellaneous", config.colors.header) end
                button, itemLeftWidth = AddEntry( buttons, "Interface\\Spellbook\\Spellbook-Icon", "ProfessionsBook", config.colors.activeName, nil, nil, true )
                button:SetScript("OnClick", ProfessionsBook_OnClick)
                button.skill, button.spellID, button.ready, button.shortcut = "None"
        end

        local maxWidth = math.max(skillWidth, cdWidth, colLeftWidth, itemLeftWidth) + GAP
        for i=1, nbEntries do buttons[i]:SetWidth(maxWidth) end
        f:SetWidth(maxWidth + GAP*2)
        f:SetHeight(BUTTON_HEIGHT * nbEntries + GAP*2)
        if _G.Skinner then _G.Skinner:applySkin(f) end

        local showBelow = select(2, tipShown:GetCenter()) > ( UIParent:GetHeight() / 2)
        f:ClearAllPoints()
        f:SetPoint(showBelow and "TOP" or "BOTTOM", tipShown, showBelow and "BOTTOM" or "TOP")

        for i=nbEntries+1, #buttons do buttons[i]:Hide() end
        if nbEntries == 0 then f:Hide() elseif not config.hideAlts then
                return InCombatLockdown() and g:Show() or ShowAlts()
        end
end


local function Block_OnClick(self, click)
        if click == "LeftButton" then
                if skills[char.skill] then CastSpellByName(char.skill) end
        elseif click == "RightButton" then
                f:Hide() tip:Hide() g:Hide()
                UIDropDownMenu_Initialize(dropdown, dropdown_init, "MENU")
                ToggleDropDownMenu(1, nil, dropdown, self, 0, 0)
        end
end


f.block = LibStub("LibDataBroker-1.1"):NewDataObject("|cFFFFB366Ara|r Tradeskills", {
        type = "data source",
        text = _G.TRADESKILLS,
        icon = "",
        OnEnter = Block_OnEnter,
        OnLeave = Menu_OnLeave,
        OnClick = Block_OnClick,
} )


function f:TRADE_SKILL_SHOW()
        self:UnregisterEvent"TRADE_SKILL_SHOW"
        if self.getLink then
                self.getLink = nil
                local link = GetTradeSkillListLink()
                if not ChatEdit_InsertLink(link) then
                        ChatFrameEditBox:Show()
                        ChatEdit_InsertLink(link)
                end
                if self.keepOpen then self.keepOpen = nil else CloseTradeSkill() end
                return
        end
        ExpandTradeSkillSubClass(0)
        SetTradeSkillSubClassFilter(0,1,1)
        SetTradeSkillInvSlotFilter(0,1,1)
        TradeSkillOnlyShowMakeable(false)
        if TradeSkillFrameAvailableFilterCheckButton then
                TradeSkillFrameAvailableFilterCheckButton:SetChecked(false)
        end
        for i=1, GetNumTradeSkills() do
                local itemName, itemType = GetTradeSkillInfo(i)
                if itemType ~= "header" then
                        local spellID = tonumber(strmatch(GetTradeSkillRecipeLink(i), "enchant:(%d+)"))
                        local cd = watchedCDs[spellID]
                        if spellID == self.searchedSpellID and (cd or self.shortcut) then
                                SelectTradeSkill(i)
                                TradeSkillFrame_SetSelection(i)
                                if self.doTradeSkill and not GetTradeSkillCooldown(i) then
                                        DoTradeSkill(i)
                                        CloseTradeSkill() -- TODO: don't close if it was open
                                else
                                        TradeSkillListScrollFrame:SetVerticalScroll(max(i-5,0)*TradeSkillSkill1:GetHeight())
                                end
                                self.searchedSpellID, self.doTradeSkill, self.shortcut = nil
                                break
                        end
                        if cd and self.learnCD then
                                local timeLeft = GetTradeSkillCooldown(i) or 0
                                if timeLeft>0 or config.addReadyCD then
                                        char[spellID] = time() + timeLeft
                                        if not cd.group then
                                                config.icons[spellID] = GetTradeSkillIcon(i)
                                                config.names[spellID] = itemName
                                        end
                                end
                        end
                end
        end
        if self.learnCD then
                if self.keepOpen then self.keepOpen = nil else CloseTradeSkill() end
                self.learnCD = nil
        end
        if tipShown then Block_OnEnter(tipShown) end
end


function f:SetAlias(alias)
        config.aliases[self.aliasID] = alias ~= "" and alias or nil
        if tipShown then Block_OnEnter(tipShown)end
end

function f:PLAYER_LOGOUT()
        for i=1, GetNumSkillLines() do
                local displayName, skill, skillName, value, _, _, valueMax = GetSkillLineInfo(i)
                skillName = altNames[displayName] or displayName
                skill = skills[skillName]
                if skill then
                        if bit_band(skill,PROFESSION) == 0 then break end
                        char.curSkills[skillName], char.maxSkills[skillName] = value, valueMax
                end
        end
end

------------------------------[[  options  ]]------------------------------

local function DisplayCD_OnUpdate(self, elapsed)
        self.timer = self.timer - elapsed
        if self.timer > 0 then return end
        self.timer = 60
        UpdateCDs()
end

local function SetOption(self, t, v, c) t[v] = not t[v] end
local function ColorPickerChange()      c[1], c[2], c[3] = ColorPickerFrame:GetColorRGB() end
local function ColorPickerCancel(prev)  c[1], c[2], c[3] = unpack(prev) end
local function OpenColorPicker(self, col)
        c = config.colors[col]
        ColorPickerFrame.func = ColorPickerChange
        ColorPickerFrame.cancelFunc = ColorPickerCancel
        ColorPickerFrame.previousValues = {unpack(c)}
        ColorPickerFrame:SetColorRGB( c[1], c[2], c[3] )
        ColorPickerFrame:Show()
end

local function RemoveChar(self, char)
        realm[char] = nil
        print( "|cFFFFB366Ara|cffffff00BrokerTradeskills: "..format(L"|cff8080ff%s|r has been removed.",char) )
end

function f:ADDON_LOADED(event, addon)
        if addon ~= "Ara_Broker_Tradeskills" then return end

        AraTradeskillsDB = AraTradeskillsDB or {icons={},names={},aliases={},colors={ header={1,1,1}, activeName={1,.82,0}, infoName={.6,.5,0}, selected={.9,0.45,.1}, highlight={1,.8,0}, ownCD={1,.82,0}, foreignCD={.6,.5,0} }}
        config = AraTradeskillsDB
        config.colors.activeVal, config.colors.infoVal = nil -- get rid of old config values
        if not config[realmName] then config[realmName] = {} end
        realm = config[realmName]
        if not realm[playerName] then realm[playerName] = {show={}} end
        char = realm[playerName]
        if not char.shortcuts then char.shortcuts = {} end -- r14
        if not char.curSkills then char.curSkills, char.maxSkills = {}, {} end
        if not char.links then char.links = {} end -- r25
        CDs = new()
        if config.displayCDs then
                e.timer = 0
                e:SetScript("OnUpdate", DisplayCD_OnUpdate)
        end

        options = {
                { text = format("|cFFFFB366Ara|r Broker Tradeskills (%s)", GetAddOnMetadata("Ara_Broker_Tradeskills", "Version")), isTitle = true },
                { text = _G.TRADE_SKILLS, submenu = "isProfession", scope=char, var="hideProfessions", inv=true },
                { text = L"Combat Skills", submenu = "isCombatSkill", scope=char, var="hideCombatSkills", inv=true },
                { text = L"Include |cff20ff20Ready!|r CDs when scanning", scope=config, var="addReadyCD" },
                { text = L"Show hints", scope=config, var="hideTips", inv=true },
                { text = L"Show alt. skills", scope=config, var="hideAlts", inv=true },
                { text = L"Show alt. primary skills only", scope=config, var="primaryOnly" },
                { text = L"Remove an alt.", submenu="alts" },
                { text = L"Display number of Ready CD", func=
                        function()
                                config.displayCDs = not config.displayCDs
                                e.timer = 0
                                e:SetScript("OnUpdate", config.displayCDs and DisplayCD_OnUpdate or nil)
                                UpdateText()
                        end, checked=function() return config.displayCDs end },
                { text = L.Colors, submenu = {
                        { text = L"Header", colorPick = "header" },
                        { text = L"Interactive skill name", colorPick = "activeName" },
                        { text = L"Last selected skill", colorPick = "selected" },
                        { text = L"Informative skill name", colorPick = "infoName" },
                        { text = L"Player CD", colorPick = "ownCD" },
                        { text = L"Other player CD", colorPick = "foreignCD" },
                        { text = L"Highlight", colorPick = "highlight" }, },
                },
        }
        dropdown_init = function(self, level)
                level = level or 1
                local options = level>1 and UIDROPDOWNMENU_MENU_VALUE or options
                if options == "alts" then
                        for char in next, realm do
                                if char ~= playerName then
                                        local info = UIDropDownMenu_CreateInfo()
                                        info.text, info.notCheckable = char, true
                                        info.func, info.arg1 = RemoveChar, char
                                        UIDropDownMenu_AddButton( info, level )
                                end
                        end
                        return
                end
                local custom = type(options)~="table"
                for i, v in ipairs(custom and f.skills or options) do
                        if not custom or v[options] then
                                local info = UIDropDownMenu_CreateInfo()
                                info.text = custom and format("|cff%.2x%.2x%.2x%s|r", v.color[1]*255, v.color[2]*255, v.color[3]*255, v.displayName) or v.text
                                info.isTitle, info.value, info.hasArrow = v.isTitle, v.submenu, v.submenu ~= nil
                                if custom then
                                        info.checked = char.show[v.skillName]
                                        info.func, info.arg1, info.arg2 = SetOption, char.show, v.skillName
                                elseif v.scope then
                                        info.checked = v.inv and not v.scope[v.var] or not v.inv and v.scope[v.var]
                                        info.func, info.arg1, info.arg2, info.tooltipText = SetOption, v.scope, v.var, v.inv
                                elseif v.colorPick then
                                        info.hasColorSwatch, info.notCheckable = true, true
                                        info.r, info.g, info.b = unpack(config.colors[v.colorPick])
                                        info.func, info.arg1 = OpenColorPicker, v.colorPick
                                else
                                        info.func, info.checked = v.func, v.checked
                                end
                                info.keepShownOnClick = info.func
                                UIDropDownMenu_AddButton( info, level )
                        end
                end
        end

        local popup = {
                text = L"Set an alias for \"%s\".\nLeave blank to reset.",
                OnAccept = function(self) AraSkills:SetAlias(self.wideEditBox:GetText()) end,
                OnShow = function(self) self.wideEditBox:SetText(AraSkills.alias) self.wideEditBox:SetFocus() end,
                EditBoxOnEnterPressed = function(self)
                        local parent = self:GetParent()
                        AraSkills:SetAlias(parent.wideEditBox:GetText())
                        parent:Hide()
                end,
        }
        for k, v in next, StaticPopupDialogs.SET_FRIENDNOTE do
                if not popup[k] then popup[k] = v end
        end
        StaticPopupDialogs.SET_ALIAS = popup
        tip = _G.GameTooltip

        self.skills = {}
        self:SetFrameStrata"TOOLTIP"
        self:SetClampedToScreen(true)
        self:EnableMouse(true)
        self:SetScript("OnEnter", Menu_OnEnter)
        self:SetScript("OnLeave", Menu_OnLeave)
        self:RegisterEvent"PLAYER_LOGOUT"

        g:SetFrameStrata"TOOLTIP"
        g:EnableMouse(true)
        g:SetScript("OnEnter", Alt_OnEnter)
        g:SetScript("OnLeave", Alt_OnLeave)

        local backdrop = { bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Tooltips\\UI-Tooltip-Border",
                tile=true, tileSize=12, edgeSize=12, insets = { left=2, right=2, top=2, bottom=2 } }
        self:SetBackdrop(backdrop)
        self:SetBackdropColor(0, 0, 0, .75)
        g:SetBackdrop(backdrop)
        g:SetBackdropColor(0, 0, 0, .75)

        if IsLoggedIn() then UpdateText() else
                f.PLAYER_ENTERING_WORLD = UpdateText
                f:RegisterEvent"PLAYER_ENTERING_WORLD"
        end
        self:UnregisterEvent(event)
        self.ADDON_LOADED = nil
end


e:RegisterEvent"TRADE_SKILL_SHOW"

e:SetScript("OnEvent", function()
        LoadAddOn"Blizzard_TradeSkillUI"

        local b = TradeSkillSkillIcon
        local TS_OnEnter = b:GetScript"OnEnter"
        local TS_OnLeave = b:GetScript"OnLeave"
        local TS_OnClick = b:GetScript"OnClick"

        local OrgSetItem = GameTooltip.SetTradeSkillItem
        local function NewSetItem(...)
                OrgSetItem(...)
                GameTooltip:AddLine("\n"..L"|cffff8040Click|r to create a shortcut.", .2, 1, .2)
                GameTooltip:Show()
        end

        b:SetScript("OnEnter", function(self, ...)
                GameTooltip.SetTradeSkillItem = NewSetItem
                TS_OnEnter(self, ...)
        end)
        b:SetScript("OnLeave", function(...)
                TS_OnLeave(...)
                GameTooltip.SetTradeSkillItem = OrgSetItem
        end)
        b:SetScript("OnClick", function(self,button,...)
                TS_OnClick(self,button,...)
                if not IsModifierKeyDown() and button == "LeftButton" then
                        local index = TradeSkillFrame.selectedSkill
                        local spellID = tonumber(strmatch(GetTradeSkillRecipeLink(index), "enchant:(%d+)"))
                        local skill = GetTradeSkillLine()
                        char.shortcuts[spellID] = altNames[skill] or skill
                        config.names[spellID] = GetTradeSkillInfo(index)
                        config.icons[spellID] = GetTradeSkillIcon(index)
                        print("|cFFFFB366Ara|cffffff00BrokerTradeskills: "..format(L"|cff8080ff%s|r|cffffff00 added to shortcuts.",config.names[spellID]))
                end
        end)

        local function OnEvent()
                if not IsTradeSkillLinked() then
                        local skill = GetTradeSkillLine()
                        char.links[ altNames[skill] or skill ] = GetTradeSkillListLink()
                end
        end
        e:SetScript("OnEvent", OnEvent)
        OnEvent()
end)

f:SetScript("OnEvent", function(self, event, ...) return self[event](self, event, ...) end)
f:RegisterEvent"ADDON_LOADED"

Compare with Previous | Blame