Quantcast
--[[-------------------------------------------------------------------------
-- BindConfig.lua
--
-- This file contains the definitions of the binding configuration panel.
--
-- Events registered:
--   None
-------------------------------------------------------------------------]]--

local addonName, addon = ...
local L = addon.L

local MAX_ROWS = 12

function CliqueConfig:ShowWithSpellBook()
    self:ClearAllPoints()
    self:SetParent(SpellBookFrame)
    self:SetPoint("LEFT", SpellBookFrame, "RIGHT", 55, 0)
    self:Show()
end

function CliqueConfig:OnShow()
    if not self.initialized then
        self:SetupGUI()
        self:HijackSpellbook()
        self.initialized = true
    end

    -- Hide the alertTab if the spellbook isn't shown
    if SpellButton1:IsVisible() then
        self.bindAlert:Show()
    else
        self.bindAlert:Hide()
    end

    CliqueSpellTab:SetChecked(true)
    self:UpdateList()
    self:EnableSpellbookButtons()
    self:UpdateAlert()
end

function CliqueConfig:OnHide()
    self:ClearAllPoints()
    self:SetParent(UIParent)
    HideUIPanel(self)
    CliqueSpellTab:SetChecked(false)
    self:UpdateAlert()
end

function CliqueConfig:SetupTalents()
    if addon.compatRelease then
        self.talents = {}
        for i = 1, GetNumSpecializations() do
            local id, name = GetSpecializationInfo(i)
            self.talents[i] = name
        end
    else
        self.talents = {L["Default"]}
    end
end

function CliqueConfig:SetupGUI()
    self:SetupTalents()

    self.rows = {}
    for i = 1, MAX_ROWS do
        self.rows[i] = CreateFrame("Button", "CliqueRow" .. i, self.page1, "CliqueRowTemplate")
    end

    self.rows[1]:ClearAllPoints()
    self.rows[1]:SetPoint("TOPLEFT", "CliqueConfigPage1Column1", "BOTTOMLEFT", 0, -3)
    self.rows[1]:SetPoint("RIGHT", CliqueConfigPage1Column2, "RIGHT", 0, 0)

    for i = 2, MAX_ROWS do
        self.rows[i]:ClearAllPoints()
        self.rows[i]:SetPoint("TOPLEFT", self.rows[i - 1], "BOTTOMLEFT")
        self.rows[i]:SetPoint("RIGHT", CliqueConfigPage1Column2, "RIGHT", 0, 0)
    end

	self.TitleText:SetText(L["Clique Binding Configuration"])

    self.dialog = _G["CliqueDialog"]
    self.dialog.title = self.dialog.TitleText
    self.dialog:SetUserPlaced(false)
    self.dialog:ClearAllPoints()
    self.dialog:SetPoint("CENTER", self, "CENTER", 30, 0)

    self.dialog.title:SetText(L["Set binding"])
    self.dialog.button_accept:SetText(L["Accept"])

    self.dialog.button_binding:SetText(L["Set binding"])
    local desc = L["In order to specify a binding, move your mouse over the button labelled 'Set binding' and either click with your mouse or press a key on your keyboard. You can modify the binding by holding down a combination of the alt, control and shift keys on your keyboard."]
    self.dialog.desc:SetText(desc)

    self.alert = _G["CliqueTabAlert"]

    self.bindAlert.text:SetText(L["You are in Clique binding mode"])

    self.close = _G[self:GetName() .. "CloseButton"]
    self.close:SetScript("OnClick", function()
        HideUIPanel(CliqueConfig)
    end)

    self.page1.column1:SetText(L["Action"])
    self.page1.column2:SetText(L["Binding"])

    -- Set columns up to handle sorting
    self.page1.column1.sortType = "name"
    self.page1.column2.sortType = "key"
    self.page1.sortType = self.page1.column2.sortType

    self.page1.button_spell:SetText(L["Bind spell"])
    self.page1.button_other:SetText(L["Bind other"])
    self.page1.button_options:SetText(L["Options"])

    self.page2.button_binding:SetText(L["Set binding"])
    self.page2.button_save:SetText(L["Save"])
    self.page2.button_cancel:SetText(L["Cancel"])
    local desc = L["You can use this page to create a custom macro to be run when activating a binding on a unit. When creating this macro you should keep in mind that you will need to specify the target of any actions in the macro by using the 'mouseover' unit, which is the unit you are clicking on. For example, you can do any of the following:\n\n/cast [target=mouseover] Regrowth\n/cast [@mouseover] Regrowth\n/cast [@mouseovertarget] Taunt\n\nHover over the 'Set binding' button below and either click or press a key with any modifiers you would like included. Then edit the box below to contain the macro you would like to have run when this binding is activated."]

    self.page2.desc:SetText(desc)
    self.page2.editbox = CliqueScrollFrameEditBox

    self.page1:Show()
end

function CliqueConfig:Column_OnClick(frame, button)
    self.page1.sortType = frame.sortType
    self:UpdateList()
end

function CliqueConfig:HijackSpellbook()
    self.spellbookButtons = {}

    for idx = 1, 12 do
        local parent = _G["SpellButton" .. idx]
        local button = CreateFrame("Button", "CliqueSpellbookButton" .. idx, parent, "CliqueSpellbookButtonTemplate")
        button.spellbutton = parent
        button:EnableKeyboard(false)
        button:EnableMouseWheel(true)
        button:RegisterForClicks("AnyDown")
        button:SetID(parent:GetID())
        self.spellbookButtons[idx] = button
    end

    SpellBookFrame:HookScript("OnShow", function(frame)
        self:EnableSpellbookButtons()
    end)
    SpellBookFrame:HookScript("OnHide", function(frame)
        self:EnableSpellbookButtons()
    end)

    self:EnableSpellbookButtons()
end

function CliqueConfig:EnableSpellbookButtons()
    local enabled;

    if self.page1:IsVisible() and SpellBookFrame:IsVisible() then
        enabled = true
    end

    if self.spellbookButtons then
        for idx, button in ipairs(self.spellbookButtons) do
            if enabled and addon:APIIsTrue(button.spellbutton:IsEnabled()) then
                button:Show()
            else
                button:Hide()
            end
        end
    end
end

-- Spellbook button functions
function CliqueConfig:Spellbook_EnableKeyboard(button, motion)
    button:EnableKeyboard(true)
end

function CliqueConfig:Spellbook_DisableKeyboard(button, motion)
    button:EnableKeyboard(false)
end

function CliqueConfig:Spellbook_OnBinding(button, key)
    if key == "ESCAPE" then
        HideUIPanel(CliqueConfig)
        return
    end

    local slot = SpellBook_GetSpellBookSlot(button:GetParent());
    local name, spellSubName, subtype = GetSpellBookItemName(slot, SpellBookFrame.bookType)
    local texture = GetSpellBookItemTexture(slot, SpellBookFrame.bookType)

    if spellSubName == "" then
        spellSubName = nil
    end

    local key = addon:GetCapturedKey(key)
    if not key then
        return
    end

    local succ, err = addon:AddBinding{
        key = key,
        type = "spell",
        spell = name,
        spellSubName = spellSubName,
        icon = texture
    }

    CliqueConfig:UpdateList()
end

function CliqueConfig:Button_OnClick(button)
    -- Click handler for "Bind spell" button
    if button == self.page1.button_spell then
        ShowUIPanel(SpellBookFrame)
        CliqueConfig:ShowWithSpellBook()

    -- Click handler for "Bind other" button
    elseif button == self.page1.button_other then
        local config = CliqueConfig
        local menu = {
            {
                text = L["Select a binding type"],
                isTitle = true,
                notCheckable = true,
            },
            {
                text = L["Target clicked unit"],
                func = function()
                    self:SetupCaptureDialog("target")
                end,
                notCheckable = true,
            },
            {
                text = L["Open unit menu"],
                func = function()
                    self:SetupCaptureDialog("menu")
                end,
                notCheckable = true,
            },
            {
                text = L["Run custom macro"],
                func = function()
                    config.page1:Hide()
                    config.page2.bindType = "macro"
                    -- Clear out the entries
                    config.page2.bindText:SetText(L["No binding set"])
                    config.page2.editbox:SetText("")
                    config.page2.button_save:Disable()
                    config.page2:Show()
                end,
                notCheckable = true,
            },
        }
        UIDropDownMenu_SetAnchor(self.dropdown, 0, 0, "BOTTOMLEFT", self.page1.button_other, "TOP")
        EasyMenu(menu, self.dropdown, nil, 0, 0, nil, nil)

    -- Click handler for "Edit" button
    elseif button == self.page1.button_options then
        local menu = {
            {
                text = L["Select an options category"],
                isTitle = true,
                notCheckable = true,
            },
            {
                text = L["Clique general options"],
                func = function()
                    HideUIPanel(SpellBookFrame)
                    HideUIPanel(CliqueConfig)
                    InterfaceOptionsFrame_OpenToCategory(addon.optpanels["GENERAL"])
                end,
                notCheckable = true,
            },
            {
                text = L["Frame blacklist"],
                func = function()
                    HideUIPanel(SpellBookFrame)
                    HideUIPanel(CliqueConfig)
                    InterfaceOptionsFrame_OpenToCategory(addon.optpanels["BLACKLIST"])
                end,
                notCheckable = true,
            },
            {
                text = L["Blizzard frame integration options"],
                func = function()
                    HideUIPanel(SpellBookFrame)
                    HideUIPanel(CliqueConfig)
                    InterfaceOptionsFrame_OpenToCategory(addon.optpanels["BLIZZFRAMES"])
                end,
                notCheckable = true,
            },
        }
        UIDropDownMenu_SetAnchor(self.dropdown, 0, 0, "BOTTOMLEFT", self.page1.button_options, "TOP")
        EasyMenu(menu, self.dropdown, nil, 0, 0, nil, nil)
    elseif button == self.page2.button_save then
        -- Check the input
        local key = self.page2.key
        local macrotext = self.page2.editbox:GetText()

        if self.page2.binding then
            self.page2.binding.key = key
            self.page2.binding.macrotext = macrotext
            self.page2.binding = nil
            addon:FireMessage("BINDINGS_CHANGED")
        else
            local succ, err = addon:AddBinding{
                key = key,
                type = "macro",
                macrotext = macrotext,
            }
        end
        self:UpdateList()
        self.page2:Hide()
        self.page1:Show()
    elseif button == self.page2.button_cancel then
        self.page2.binding = nil
        self.page2:Hide()
        self.page1:Show()
    end
end

local memoizeBindings = setmetatable({}, {__index = function(t, k, v)
    local binbits = addon:GetBinaryBindingKey(k)
    rawset(t, k, binbits)
    return binbits
end})

local compareFunctions;
compareFunctions = {
    name = function(a, b)
        local texta = addon:GetBindingActionText(a.type, a)
        local textb = addon:GetBindingActionText(b.type, b)
        if texta == textb then
            return compareFunctions.key(a, b)
        end
        return texta < textb
    end,
    key = function(a, b)
        local keya = addon:GetBindingKey(a)
        local keyb = addon:GetBindingKey(b)
        if keya == keyb then
            return memoizeBindings[a] < memoizeBindings[b]
        elseif not keya or not keyb then
            return false
        else
            return keya < keyb
        end
    end,
    binding = function(a, b)
        local mem = memoizeBindings
		if mem[a] == mem[b] then
			return compareFunctions.name(a, b)
		else
			return mem[a] < mem[b]
		end
    end,
}

-- Mapping between binding entry and index in profile
function CliqueConfig:UpdateList()
    local page = self.page1
    local binds = addon.bindings

    -- GUI not created yet
    if not self.initialized then
        return
    elseif not self:IsVisible() then
        return
    end

    -- Sort the bindings
    local sort = {}
    for idx, entry in pairs(binds) do
        sort[#sort + 1] = entry
    end

    if page.sortType then
        table.sort(sort, compareFunctions[page.sortType])
    else
        table.sort(sort, compareFunctions.key)
    end

    -- Enable or disable the scroll bar
    if #sort > MAX_ROWS - 1 then
        -- Set up the scrollbar for the item list
        page.slider:SetMinMaxValues(0, #sort - MAX_ROWS)

        -- Adjust and show
        if not page.slider:IsShown() then
            -- Adjust column positions
            for idx, row in ipairs(self.rows) do
                row.bind:SetWidth(90)
            end
            page.slider:SetValue(0)
            page.slider:Show()
        end
    elseif page.slider:IsShown() then
        -- Move column positions back and hide the slider
        for idx, row in ipairs(self.rows) do
            row.bind:SetWidth(105)
        end
        page.slider:Hide()
    end

    -- Update the rows in the list
    local offset = page.slider:GetValue() or 0
    for idx, row in ipairs(self.rows) do
        local offsetIndex = offset + idx
        if sort[offsetIndex] then
            local bind = sort[offsetIndex]
            row.icon:SetTexture(addon:GetBindingIcon(bind))
            row.name:SetText(addon:GetBindingActionText(bind.type, bind))
            row.info:SetText(addon:GetBindingInfoText(bind))
            row.bind:SetText(addon:GetBindingKeyComboText(bind))
            row.binding = bind
            row:Show()
        else
            row:Hide()
        end
    end
end

function CliqueConfig:ClearEditPage()
end

function CliqueConfig:ShowEditPage()
    self:ClearEditPage()
    self.page1:Hide()
    self.page3:Show()
end

function CliqueConfig:Save_OnClick(button, down)
end

function CliqueConfig:Cancel_OnClick(button, down)
    self:ClearEditPage()
    self.page3:Hide()
    self.page1:Show()
end

function CliqueConfig:SetupCaptureDialog(type, binding)
    self.dialog.bindType = type
    self.dialog.binding = binding

    if not binding then
        local actionText = addon:GetBindingActionText(type, binding)
        self.dialog.title:SetText(L["Set binding: %s"]:format(actionText))
    else
        -- This is a change to an existing binding
        local actionText = addon:GetBindingActionText(type, binding)
        self.dialog.title:SetText(L["Change binding: %s"]:format(actionText))
    end

    self.dialog.bindText:SetText("")
    self.dialog:Show()
end

function CliqueConfig:BindingButton_OnClick(button, key)
    local dialog = CliqueDialog
    dialog.key = addon:GetCapturedKey(key)
    if dialog.key then
        CliqueDialog.bindText:SetText(addon:GetBindingKeyComboText(dialog.key))
    end
end

function CliqueConfig:MacroBindingButton_OnClick(button, key)
    local key = addon:GetCapturedKey(key)
    if key then
        self.page2.key = key
        self.page2.bindText:SetText(addon:GetBindingKeyComboText(key))
        self.page2.button_save:Enable()
    else
        self.page2.bindText:SetText(L["No binding set"])
        self.page2.button_save:Disable()
    end
end

function CliqueConfig:AcceptSetBinding()
    local dialog = CliqueDialog
    local key = dialog.key

    if dialog.binding then
        -- This was a CHANGE binding instead of a SET binding
        dialog.binding.key = key
        dialog.binding = nil
        -- Do not forget to update the attributes as well
        self:UpdateList()
        addon:FireMessage("BINDINGS_CHANGED")
    else
        local succ, err = addon:AddBinding{
            key = key,
            type = dialog.bindType,
        }
        if succ then
            self:UpdateList()
        end
    end
    dialog:Hide()
end

local function toggleSet(binding, set, ...)
    local exclude = {}
    for i = 1, select("#", ...) do
        local item = select(i, ...)
        table.insert(exclude, item)
    end

    return function()
        if not binding.sets then
            binding.sets = {}
        end
        if binding.sets[set] then
            binding.sets[set] = nil
        else
            binding.sets[set] = true
        end

        for idx, exclset in ipairs(exclude) do
            binding.sets[exclset] = nil
        end

        UIDropDownMenu_Refresh(UIDROPDOWNMENU_OPEN_MENU, nil, UIDROPDOWNMENU_MENU_LEVEL)
        CliqueConfig:UpdateList()
        addon:FireMessage("BINDINGS_CHANGED")
    end
end

function CliqueConfig:Row_OnClick(frame, button)
    local binding = frame.binding
    local actionText = addon:GetBindingActionText(binding.type, binding)

    local menu = {
        {
            text = L["Configure binding: '%s'"]:format(actionText:sub(1,15)),
            notCheckable = true,
            isTitle = true,
        },
        {
            text = L["Change binding"],
            func = function()
                local binding = frame.binding
                self:SetupCaptureDialog(binding.type, binding)
            end,
            notCheckable = true,
        },
        {
            text = L["Delete binding"],
            func = function()
                addon:DeleteBinding(frame.binding)
                self:UpdateList()
            end,
            notCheckable = true,
        },
    }

    if binding.type == "spell" and binding.spellSubName then
        -- Enable a 'Remove Rank' option
        table.insert(menu, {
            text = L["Remove spell rank"],
            func = function()
                local binding = frame.binding
                binding.spellSubName = null
                self:UpdateList()
                addon:FireMessage("BINDINGS_CHANGED")
            end,
            notCheckable = true,
        })
    end

    if binding.type == "macro" then
        -- Replace 'Change Binding' with 'Edit macro'
        menu[2] = {
            text = L["Edit macro"],
            func = function()
                self.page2.bindType = "macro"
                local bindText = addon:GetBindingKeyComboText(binding)
                self.page2.bindText:SetText(bindText)
                self.page2.binding = binding
                self.page2.key = binding.key
                self.page2.editbox:SetText(binding.macrotext)
                self.page2.button_save:Enable()
                self.page1:Hide()
                self.page2:Show()
            end,
            notCheckable = true,
        }
    end

    local submenu = {
        text = L["Enable/Disable binding-sets"],
        hasArrow = true,
        notCheckable = true,
        menuList = {},
    }
    table.insert(menu, submenu)

    table.insert(submenu.menuList, {
        text = L["Default"],
        checked = function() return binding.sets["default"] end,
        func = toggleSet(binding, "default"),
        tooltipTitle = L["Clique: 'default' binding-set"],
        tooltipText = L["A binding that belongs to the 'default' binding-set will always be active on your unit frames, unless you override it with another binding."],
        keepShownOnClick = true,
    })

    table.insert(submenu.menuList, {
        text = L["Friend"],
        checked = function() return binding.sets["friend"] end,
        func = toggleSet(binding, "friend"),
        tooltipTitle = L["Clique: 'friend' binding-set"],
        tooltipText = L["A binding that belongs to the 'frield' binding-set will only be active when clicking on unit frames that display friendly units, i.e. those you can heal and assist. If you click on a unit that you cannot heal or assist, nothing will happen."],
        keepShownOnClick = true,
    })

    table.insert(submenu.menuList, {
        text = L["Enemy"],
        checked = function() return binding.sets["enemy"] end,
        func = toggleSet(binding, "enemy"),
        tooltipTitle = L["Clique: 'enemy' binding-set"],
        tooltipText = L["A binding that belongs to the 'enemy' binding-set will always be active when clicking on unit frames that display enemy units, i.e. those you can attack. If you click on a unit that you cannot attack, nothing will happen."],
        keepShownOnClick = true,
    })

    table.insert(submenu.menuList, {
        text = L["Out-of-combat (ONLY)"],
        checked = function() return binding.sets["ooc"] end,
        func = toggleSet(binding, "ooc"),
        tooltipTitle = L["Clique: 'ooc' binding-set"],
        tooltipText = L["A binding that belongs to the 'ooc' binding-set will only be active when the player is out-of-combat, regardless of the other binding-sets this binding belongs to. As soon as the player enters combat, these bindings will no longer be active, so be careful when choosing this binding-set for any spells you use frequently."],
        keepShownOnClick = true,
    })

    -- Add some buttons for the talent specs
    for i = 1, #self.talents do
        local key = "spec" .. i
        table.insert(submenu.menuList, {
            text = L["Talents: %s"]:format(self.talents[i]),
            checked = function() return binding.sets[key] end,
            func = toggleSet(binding, key),
            tooltipTitle = L["Clique: talent set %d binding-set"].format(i),
            tooltipText = L["A binding that belongs to this binding-set is only active when the player has the given talent specialization active"],
            keepShownOnClick = true,
        })
    end

    table.insert(submenu.menuList, {
        text = L["Hovercast bindings (target required)"],
        checked = function() return binding.sets["hovercast"] end,
        func = toggleSet(binding, "hovercast", "global"),
        tooltipTitle = L["Clique: 'hovercast' binding-set"],
        tooltipText = L["A binding that belongs to the 'hovercast' binding-set is active whenever the mouse is over a unit frame, or a character in the 3D world. This allows you to use 'hovercasting', where you hover over a unit in the world and press a key to cast a spell on them. THese bindings are also active over unit frames."],
        keepShownOnClick = true,
    })

    table.insert(submenu.menuList, {
        text = L["Global bindings (no target)"],
        checked = function() return binding.sets["global"] end,
        func = toggleSet(binding, "global", "hovercast"),
        tooltipTitle = L["Clique: 'global' binding-set"],
        tooltipText = L["A binding that belongs to the 'global' binding-set is always active. If the spell requires a target, you will be given the 'casting hand', otherwise the spell will be cast. If the spell is an AOE spell, then you will be given the ground targeting circle."],
        keepShownOnClick = true,
    })

    EasyMenu(menu, self.dropdown, "cursor", 0, 0, nil, nil)
end

function CliqueConfig:SpellTab_OnClick(frame)
    if self:IsVisible() then
        HideUIPanel(CliqueConfig)
    elseif SpellBookFrame:IsVisible() then
        self:ShowWithSpellBook()
    else
        ShowUIPanel(CliqueConfig)
    end
end

function CliqueConfig:UpdateAlert(type)
    local alert = CliqueTabAlert
    if not addon.settings.alerthidden and SpellBookFrame:IsVisible() and CliqueConfig:IsVisible() then
        alert.type = type
        alert.text:SetText(L["When both the Clique binding configuration window and the spellbook are open, you can set new bindings simply by performing them on the spell icon in your spellbook. Simply move your mouse over a spell and then click or press a key on your keyboard along with any combination of the alt, control, and shift keys. The new binding will be added to your binding configuration."])
        alert:Show()
    else
        alert:Hide()
    end
end