Quantcast

Revamp of attribute calculation/setting

James Whitehead II [11-22-10 - 19:13]
Revamp of attribute calculation/setting

The logic seems to be a bit more straightforward and it seems that the
ooc/friend/enemy layering is working properly. The major changes are:

  * Clique will only calculate new attributes when you change bindings
  * Clique will apply the attributes when bindings change
  * If you have ooc bindings, then they will swap on enter/leave combat
  * Otherwise, they will not change, so there's no overhead from that

out-of-combat bindings should now correctly be applied over any other
type of binding with the same key. If this isn't the case, please report
it to me.
Filename
BindConfig.lua
Clique.lua
FrameOptionsPanel.lua
TestSuite.lua
Utils.lua
diff --git a/BindConfig.lua b/BindConfig.lua
index 227a3fb..280d668 100755
--- a/BindConfig.lua
+++ b/BindConfig.lua
@@ -284,9 +284,7 @@ function CliqueConfig:Button_OnClick(button)
             self.page2.binding.key = key
             self.page2.binding.macrotext = macrotext
             self.page2.binding = nil
-            if not InCombatLockdown() then
-                addon:UpdateEverything()
-            end
+            addon:FireMessage("BINDINGS_CHANGED")
         else
             local succ, err = addon:AddBinding{
                 key = key,
@@ -466,8 +464,7 @@ function CliqueConfig:AcceptSetBinding()
         dialog.binding = nil
         -- Do not forget to update the attributes as well
         self:UpdateList()
-        addon:UpdateAttributes()
-        addon:UpdateGlobalAttributes()
+        addon:FireMessage("BINDINGS_CHANGED")
     else
         local succ, err = addon:AddBinding{
             key = key,
@@ -503,8 +500,7 @@ local function toggleSet(binding, set, ...)

         UIDropDownMenu_Refresh(UIDROPDOWNMENU_OPEN_MENU, nil, UIDROPDOWNMENU_MENU_LEVEL)
         CliqueConfig:UpdateList()
-        addon:UpdateAttributes()
-        addon:UpdateGlobalAttributes()
+        addon:FireMessage("BINDINGS_CHANGED")
     end
 end

diff --git a/Clique.lua b/Clique.lua
index cb9a1df..19bd319 100755
--- a/Clique.lua
+++ b/Clique.lua
@@ -69,14 +69,14 @@ function addon:Initialize()
     self.header:Execute([[
         blacklist = table.new()
     ]])
-    self:UpdateBlacklist()

     -- This snippet is executed from the SecureHandlerEnterLeaveTemplate
     -- _onenter and _onleave attributes. The 'self' attribute will contain
     -- the unit frame itself.
     self.header:SetAttribute("clickcast_onenter", [===[
         local header = self:GetParent():GetFrameRef("clickcast_header")
-        header:RunFor(self, header:GetAttribute("setup_onenter"))
+        local inCombat = header:GetAttribute("inCombat")
+        header:RunFor(self, header:GetAttribute("setup_onenter"), inCombat)
     ]===])

     -- This snippet is executed from the SecureHandlerEnterLeaveTemplate
@@ -84,7 +84,8 @@ function addon:Initialize()
     -- the unit frame itself.
     self.header:SetAttribute("clickcast_onleave", [===[
         local header = self:GetParent():GetFrameRef("clickcast_header")
-        header:RunFor(self, header:GetAttribute("setup_onleave"))
+        local inCombat = header:GetAttribute("inCombat")
+        header:RunFor(self, header:GetAttribute("setup_onleave"), inCombat)
     ]===])

     local setup, remove = self:GetClickAttributes()
@@ -103,6 +104,8 @@ function addon:Initialize()
         button:SetAttribute("clickcast_onenter", self:GetAttribute("clickcast_onenter"))
         button:SetAttribute("clickcast_onleave", self:GetAttribute("clickcast_onleave"))
         ccframes[button] = true
+
+        local inCombat = self:GetAttribute("inCombat")
         self:RunFor(button, self:GetAttribute("setup_clicks"))
     ]===])

@@ -178,12 +181,18 @@ function addon:Initialize()
     self:RegisterEvent("PLAYER_REGEN_ENABLED", "LeavingCombat")
     self:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED", "TalentGroupChanged")

+    -- Register for Clique-based messages for settings updates, etc.
+    self:RegisterMessage("BINDINGS_CHANGED")
+    self:RegisterMessage("BLACKLIST_CHANGED")
+
     -- Handle combat watching so we can change ooc based on party combat status
     addon:UpdateCombatWatch()

     -- Trigger a 'TalentGroupChanged' so we end up on the right profile
     addon:TalentGroupChanged()
-    addon:UpdateEverything()
+
+    self:FireMessage("BLACKLIST_CHANGED")
+    self:FireMessage("BINDINGS_CHANGED")
 end

 -- These tables are a queue for frame registration/unregistration
@@ -269,12 +278,12 @@ end

 function addon:OnProfileChanged(event, db, newProfile)
     self.bindings = db.profile.bindings
-    self:UpdateEverything()
+    self:FireMessage("BINDINGS_CHANGED")
 end

-local function ATTR(prefix, attr, suffix, value)
-    local fmt = [[button:SetAttribute("%s%s%s%s%s", %q)]]
-    return fmt:format(prefix, #prefix > 0 and "-" or "", attr, tonumber(suffix) and "" or "-", suffix, value)
+local function ATTR(indent, prefix, attr, suffix, value)
+    local fmt = [[%sbutton:SetAttribute("%s%s%s%s%s", %q)]]
+    return fmt:format(indent, prefix, #prefix > 0 and "-" or "", attr, tonumber(suffix) and "" or "-", suffix, value)
 end

 local function REMATTR(prefix, attr, suffix, value)
@@ -284,7 +293,6 @@ end

 -- A sort function that determines in what order bindings should be applied.
 -- This function should be treated with care, it can drastically change behavior
-
 local function ApplicationOrder(a, b)
     local acnt, bcnt = 0, 0
     for k,v in pairs(a.sets) do acnt = acnt + 1 end
@@ -305,21 +313,36 @@ local function ApplicationOrder(a, b)
     end
 end

--- This function will create an attribute that when run for a given frame
--- will set the correct set of SAB attributes.
+local function shouldApply(global, entry)
+    if global and (entry.sets.hovercast or entry.sets.global) then
+        return true
+    elseif not global and (not entry.sets.hovercast and not entry.sets.global) then
+        return true
+    end
+end
+
+-- This function takes a single argument indicating if the attributes being
+-- computed are for the special 'global' button used by Clique.  It then
+-- computes the set of attributes necessary for the player's bindings to be
+-- active on all the appropriate frames. The logic here is quite delicate but
+-- also rather well commented.
+
 function addon:GetClickAttributes(global)
     local bits = {
+        "local inCombat = self:GetAttribute('inCombat')",
         "local setupbutton = self:GetFrameRef('cliquesetup_button')",
         "local button = setupbutton or self",
     }

     local rembits = {
+        "local inCombat = self:GetAttribute('inCombat')",
         "local setupbutton = self:GetFrameRef('cliquesetup_button')",
         "local button = setupbutton or self",
     }

-    -- Global attributes are never blacklisted
-    if not global then
+    -- Check to see if the frame being setup is blacklisted. Do not perform
+    -- this check on the global frame.
+    if not global then
         bits[#bits + 1] = "local name = button:GetName()"
         bits[#bits + 1] = "if blacklist[name] then return end"

@@ -327,17 +350,48 @@ function addon:GetClickAttributes(global)
         rembits[#rembits + 1] = "if blacklist[name] then return end"
     end

+    -- Sort the bindings so they are applied in order
     table.sort(self.bindings, ApplicationOrder)

+    -- Build a small table of ooc keys that are 'taken' sowe can check for
+    -- masking conflicts with the friend/enemy sets.
+    local oocKeys = {}
+    for idx, entry in ipairs(self.bindings) do
+        if shouldApply(global, entry) and entry.sets.ooc then
+            oocKeys[entry.key] = true
+        end
+    end
+
     for idx, entry in ipairs(self.bindings) do
-        if self:ShouldSetBinding(entry, global) then
+        -- Global (i.e. 'hovercast' and 'global') bindings are only applied
+        -- on the global frame, and not on any others. Additionally, any
+        -- non-global bindings are only applied on non-global frames. handle
+        -- this logic here.
+
+        if shouldApply(global, entry) then
+            -- Check to see if this is a 'friend' or an 'enemy' binding, and
+            -- check if it would mask an 'ooc' binding with the same key. If
+            -- so, we need to add code that prevents this from happening, by
+            -- stopping the friend/enemy binding from being applied when the
+            -- player is out of combat.
+
+            local indent = ""
+            local oocmask = oocKeys[entry.key]
+
+            if oocmask and not entry.sets.ooc then
+                bits[#bits + 1] = "if inCombat then"
+                indent = indent .. "  "
+            elseif entry.sets.ooc then
+                bits[#bits + 1] = "if not inCombat then"
+                indent = indent .. "  "
+            end
+
             local prefix, suffix = addon:GetBindingPrefixSuffix(entry, global)

             -- Set up help/harm bindings. The button value will be either a number,
             -- in the case of mouse buttons, otherwise it will be a string of
             -- characters. Harmbuttons work alongside modifiers, so we need to include
             -- then in the remapping.
-
             if entry.sets.friend then
                 if global then
                     -- A modified binding that uses friend/enemy must have the unmodified
@@ -347,11 +401,11 @@ function addon:GetClickAttributes(global)
                     -- NOTE: This will not work with useOwnerUnit and usesuffix frames
                     -- such as pet frames that use the owner's parent. This is a problem
                     -- with the way the 'mouseover' unit resolves in these cases.
-                    bits[#bits + 1] = ATTR(prefix, "unit", suffix, "mouseover")
+                    bits[#bits + 1] = ATTR(indent, prefix, "unit", suffix, "mouseover")
                     rembits[#rembits + 1] = REMATTR(prefix, "unit", suffix)
                 end
                 local newbutton = "friend" .. suffix
-                bits[#bits + 1] = ATTR(prefix, "helpbutton", suffix, newbutton)
+                bits[#bits + 1] = ATTR(indent, prefix, "helpbutton", suffix, newbutton)
                 rembits[#rembits + 1] = REMATTR(prefix, "helpbutton", suffix)
                 suffix = newbutton
             elseif entry.sets.enemy then
@@ -363,11 +417,11 @@ function addon:GetClickAttributes(global)
                     -- NOTE: This will not work with useOwnerUnit and usesuffix frames
                     -- such as pet frames that use the owner's parent. This is a problem
                     -- with the way the 'mouseover' unit resolves in these cases.
-                    bits[#bits + 1] = ATTR(prefix, "unit", suffix, "mouseover")
+                    bits[#bits + 1] = ATTR(indent, prefix, "unit", suffix, "mouseover")
                     rembits[#rembits + 1] = REMATTR(prefix, "unit", suffix)
                 end
                 local newbutton = "enemy" .. suffix
-                bits[#bits + 1] = ATTR(prefix, "harmbutton", suffix, newbutton)
+                bits[#bits + 1] = ATTR(indent, prefix, "harmbutton", suffix, newbutton)
                 rembits[#rembits + 1] = REMATTR(prefix, "harmbutton", suffix)
                 suffix = newbutton
             end
@@ -376,27 +430,36 @@ function addon:GetClickAttributes(global)
             -- 'hovercast' binding set, we need to specify the unit on which to take
             -- the action. In this case, that's just mouseover.
             if global and entry.sets.hovercast then
-                bits[#bits + 1] = ATTR(prefix, "unit", suffix, "mouseover")
+                bits[#bits + 1] = ATTR(indent, prefix, "unit", suffix, "mouseover")
                 rembits[#rembits + 1] = REMATTR(prefix, "unit", suffix)
             end

             -- Build any needed SetAttribute() calls
             if entry.type == "target" or entry.type == "menu" then
-                bits[#bits + 1] = ATTR(prefix, "type", suffix, entry.type)
+                bits[#bits + 1] = ATTR(indent, prefix, "type", suffix, entry.type)
                 rembits[#rembits + 1] = REMATTR(prefix, "type", suffix)
             elseif entry.type == "spell" then
-                bits[#bits + 1] = ATTR(prefix, "type", suffix, entry.type)
-                bits[#bits + 1] = ATTR(prefix, "spell", suffix, entry.spell)
+                bits[#bits + 1] = ATTR(indent, prefix, "type", suffix, entry.type)
+                bits[#bits + 1] = ATTR(indent, prefix, "spell", suffix, entry.spell)
                 rembits[#rembits + 1] = REMATTR(prefix, "type", suffix)
                 rembits[#rembits + 1] = REMATTR(prefix, "spell", suffix)
             elseif entry.type == "macro" then
-                bits[#bits + 1] = ATTR(prefix, "type", suffix, entry.type)
-                bits[#bits + 1] = ATTR(prefix, "macrotext", suffix, entry.macrotext)
+                bits[#bits + 1] = ATTR(indent, prefix, "type", suffix, entry.type)
+                bits[#bits + 1] = ATTR(indent, prefix, "macrotext", suffix, entry.macrotext)
                 rembits[#rembits + 1] = REMATTR(prefix, "type", suffix)
                 rembits[#rembits + 1] = REMATTR(prefix, "macrotext", suffix)
             else
                 error(string.format("Invalid action type: '%s'", entry.type))
             end
+
+            -- Finish the conditional statements started above
+            if oocmask and not entry.sets.ooc then
+                bits[#bits + 1] = "end"
+                indent = indent:sub(1, -3)
+            elseif entry.sets.ooc then
+                bits[#bits + 1] = "end"
+                indent = indent:sub(1, -3)
+            end
         end
     end

@@ -406,15 +469,22 @@ end
 local B_SET = [[self:SetBindingClick(true, %q, self, %q);]]
 local B_CLR = [[self:ClearBinding(%q);]]

--- This function will create two attributes, the first being a "setup keybindings"
--- script and the second being a "clear keybindings" script.
+-- This function takes a single argument, indicating whether the attributes
+-- should be built for the special global button or not, and returns an
+-- attribute that can set the appropriate attributes, and one that can clear
 function addon:GetBindingAttributes(global)
-    local set = {
-    }
-    local clr = {
-    }
+    local set, clr
+
+    -- If this is not the global button, include some logic that solves issues
+    -- when the frame disappears or the frame loses focus without the OnLeave
+    -- event firing.
+    --
+    -- TODO: In the future, this should be done via OnHide or other ways as well

-    if not global then
+    if global then
+        set = {}
+        clr = {}
+    else
         set = {
             "local button = self",
             "local name = button:GetName()",
@@ -430,34 +500,44 @@ function addon:GetBindingAttributes(global)
         }
     end

+    -- This function is greatly simplified in that regardless of whether or
+    -- not bindings mask one another, they still need to be set as binding
+    -- clicks on the frame. Simply make a list of the keys that need to be
+    -- bound, and bind them.
+
+    local unique = {}
+
     for idx, entry in ipairs(self.bindings) do
-        if self:ShouldSetBinding(entry, global) and entry.key then
-            local buttonNum = entry.key:match("BUTTON(%d+)$")
-
-            -- A mouse button cannot be bound as an onenter/onleave binding over
-            -- frames. This is due to a Blizzard limitation of the way clicks
-            -- are handled. In order to get this functionality, the button should
-            -- be bound as both 'default' and whatever global set you'd like, so
-            -- you can use it in both situations.
-
-            if buttonNum then
-                if global and entry.sets.hovercast or entry.sets.global then
-                    -- Do NOT allow re-bindings of unmodified left/right-click
-                    if entry.key ~= "BUTTON1" and entry.key ~= "BUTTON2" then
-                        local prefix, suffix = addon:GetBindingPrefixSuffix(entry, global)
-                        local key = entry.key
-
-                        set[#set + 1] = B_SET:format(key, suffix)
+        if shouldApply(global, entry) then
+            if global then
+                -- Allow for the re-binding of clicks and keys, except for
+                -- unmodified left/right-click
+                if entry.key ~= "BUTTON1" and entry.key ~= "BUTTON2" then
+                    local prefix, suffix = addon:GetBindingPrefixSuffix(entry, global)
+                    local key = self:ConvertSpecialKeys(entry)
+
+                    local attr = B_SET:format(key, suffix)
+                    if not unique[attr] then
+                        set[#set + 1] = attr
                         clr[#clr + 1] = B_CLR:format(key)
+                        unique[attr] = true
                     end
                 end
             else
-                -- This is a key binding, so we need a binding for it
-                local prefix, suffix = self:GetBindingPrefixSuffix(entry, global)
-                local key = self:ConvertSpecialKeys(entry)
-
-                set[#set + 1] = B_SET:format(key, suffix)
-                clr[#clr + 1] = B_CLR:format(key)
+                local buttonNum = entry.key:match("BUTTON(%d+)$")
+                if not buttonNum then
+                    -- Only apply key-based binding clicks, let the raw
+                    -- attributes handle the others
+                    local prefix, suffix = addon:GetBindingPrefixSuffix(entry, global)
+                    local key = self:ConvertSpecialKeys(entry)
+
+                    local attr = B_SET:format(key, suffix)
+                    if not unique[attr] then
+                        set[#set + 1] = attr
+                        clr[#clr + 1] = B_CLR:format(key)
+                        unique[attr] = true
+                    end
+                end
             end
         end
     end
@@ -502,7 +582,7 @@ function addon:AddBinding(entry)
     end

     table.insert(self.bindings, entry)
-    self:UpdateAttributes()
+    self:FireMessage("BINDINGS_CHANGED")
     return true
 end

@@ -539,9 +619,7 @@ function addon:DeleteBinding(entry)
         end
     end

-    -- Update the attributes
-    self:UpdateAttributes()
-    self:UpdateGlobalAttributes()
+    self:FireMessage("BINDINGS_CHANGED")
 end

 function addon:ClearAttributes()
@@ -556,19 +634,15 @@ function addon:ClearAttributes()
         self.header:SetFrameRef("cliquesetup_button", button)
         self.header:Execute(self.header:GetAttribute("remove_clicks"), button)
     end
+
+    -- Clear global attributes
+    local globutton = self.globutton
+    globutton:Execute(globutton.remove)
+    globutton:Execute(globutton.clearbinds)
 end

+-- Recompute all attributes, so they can later be applied.
 function addon:UpdateAttributes()
-    if InCombatLockdown() then
-        error("panic: Clique:UpdateAttributes() called during combat")
-    end
-
-    -- Update global attributes
-    self:UpdateGlobalAttributes()
-
-    -- Clear any of the previously set attributes
-    self:ClearAttributes()
-
     local setup, remove = self:GetClickAttributes()
     self.header:SetAttribute("setup_clicks", setup)
     self.header:SetAttribute("remove_clicks", remove)
@@ -577,42 +651,35 @@ function addon:UpdateAttributes()
     self.header:SetAttribute("setup_onenter", set)
     self.header:SetAttribute("setup_onleave", clr)

+    local globutton = self.globutton
+    globutton.setup, globutton.remove = self:GetClickAttributes(true)
+    globutton.setbinds, globutton.clearbinds = self:GetBindingAttributes(true)
+end
+
+function addon:ApplyAttributes()
+    -- Handle all of the securely registered frames
     self.header:Execute([[
         for button, enabled in pairs(ccframes) do
             self:RunFor(button, self:GetAttribute("setup_clicks"))
         end
     ]])

+    -- Now any compat frames that used the old method
     for button, enabled in pairs(self.ccframes) do
         -- Unwrap any existing enter/leave scripts
-        addon.header:UnwrapScript(button, "OnEnter")
-        addon.header:UnwrapScript(button, "OnLeave")
-        addon.header:WrapScript(button, "OnEnter", addon.header:GetAttribute("setup_onenter"))
-        addon.header:WrapScript(button, "OnLeave", addon.header:GetAttribute("setup_onleave"))
+        self.header:UnwrapScript(button, "OnEnter")
+        self.header:UnwrapScript(button, "OnLeave")
+        self.header:WrapScript(button, "OnEnter", addon.header:GetAttribute("setup_onenter"))
+        self.header:WrapScript(button, "OnLeave", addon.header:GetAttribute("setup_onleave"))

         -- Perform the setup of click bindings
         self.header:SetFrameRef("cliquesetup_button", button)
         self.header:Execute(self.header:GetAttribute("setup_clicks"), button)
     end
-end
-
-function addon:ClearGlobalAttributes()
-    local globutton = self.globutton
-    globutton:Execute(globutton.remove)
-    globutton:Execute(globutton.clearbinds)
-end
-
--- Update the global click attributes
-function addon:UpdateGlobalAttributes()
-    local globutton = self.globutton

-    self:ClearGlobalAttributes()
-
-    -- Get the override binding attributes for the global click frame
-    globutton.setup, globutton.remove = self:GetClickAttributes(true)
-    globutton.setbinds, globutton.clearbinds = self:GetBindingAttributes(true)
-    globutton:Execute(globutton.setup)
-    globutton:Execute(globutton.setbinds)
+    -- Update the global button attributes
+    self.globutton:Execute(self.globutton.setup)
+    self.globutton:Execute(self.globutton.setbinds)
 end

 function addon:TalentGroupChanged()
@@ -633,7 +700,7 @@ function addon:TalentGroupChanged()
         end
     end

-    self:UpdateEverything()
+    self:FireMessage("BINDINGS_CHANGED")
 end

 function addon:UpdateCombatWatch()
@@ -660,55 +727,78 @@ function addon:UpdateBlacklist()
 end

 function addon:EnteringCombat()
-    addon:UpdateAttributes()
-    addon:UpdateGlobalAttributes()
+    -- If there are no 'ooc' bindings, then no need to re-apply
+    if not self.has_ooc then
+        return
+    end
+
+    -- Check to see if we're already in combat, so we don't re-apply
+    if not self.header:GetAttribute("inCombat") then
+        -- Apply attributes, indicating we need the 'combat' set
+        self.header:SetAttribute("inCombat", true)
+        self.globutton:SetAttribute("inCombat", true)
+        addon:ApplyAttributes()
+    end
 end

 function addon:LeavingCombat()
-    self.partyincombat = false
+    -- Process any frames in the registration queue
+    for idx, button in ipairs(self.regqueue) do
+        self:RegisterFrame(button)
+    end
+    if next(self.regqueue) then table.wipe(self.regqueue) end

-    -- Sanity check
-    if not InCombatLockdown() then
+    -- Process any frames in the unregistration queue
+    for idx, button in ipairs(self.unregqueue) do
+        self:UnregisterFrame(button)
+    end
+    if next(self.regqueue) then table.wipe(self.regqueue) end

-        -- Process any frames in the registration queue
-        for idx, button in ipairs(self.regqueue) do
-            self:RegisterFrame(button)
-        end
-        if next(self.regqueue) then table.wipe(self.regqueue) end
+    -- Process any frames in the clickregister queue
+    for idx, button in ipairs(self.regclickqueue) do
+        self:UpdateRegisteredClicks(button)
+    end
+    if next(self.regclickqueue) then table.wipe(self.regclickqueue) end

-        -- Process any frames in the unregistration queue
-        for idx, button in ipairs(self.unregqueue) do
-            self:UnregisterFrame(button)
+    -- Only apply attributes if we have an 'ooc' binding set
+    if self.has_ooc then
+        if self.partyincombat then
+            self.partyincombat = false
         end
-        if next(self.regqueue) then table.wipe(self.regqueue) end

-        -- Process any frames in the clickregister queue
-        for idx, button in ipairs(self.regclickqueue) do
-            self:UpdateRegisteredClicks(button)
-        end
-        if next(self.regclickqueue) then table.wipe(self.regclickqueue) end
-    end
+        -- Clear previously set attributes
+        self:ClearAttributes()

-    self:UpdateAttributes()
-    self:UpdateGlobalAttributes()
+        -- Apply attributes, indicating we want the 'ooc' set
+        self.header:SetAttribute("inCombat", false)
+        self.globutton:SetAttribute("inCombat", false)
+        self:ApplyAttributes()
+    end
 end

 function addon:CheckPartyCombat(event, unit)
     if InCombatLockdown() or not unit then return end
+    if not self.has_ooc then
+        -- No change required if no ooc bindings
+        return
+    end
+
     if self.settings.fastooc then
         if UnitInParty(unit) or UnitInRaid(unit) then
             if UnitAffectingCombat(unit) == 1 then
                 -- Trigger pre-combat switch for fastooc
                 self.partyincombat = true
                 self.combattrigger = UnitGUID(unit)
-                addon:UpdateAttributes()
-                addon:UpdateGlobalAttributes()
+                self.header:SetAttribute("inCombat", true)
+                self.globutton:SetAttribute("inCombat", true)
+                addon:ApplyAttributes()
             elseif self.partyincombat then
                 -- The unit is out of combat, so try to clear our flag
                 if self.combattrigger == UnitGUID(unit) then
                     self.partyincombat = false
-                    addon:UpdateAttributes()
-                    addon:UpdateGlobalAttributes()
+                    self.header:SetAttribute("inCombat", false)
+                    self.globutton:SetAttribute("inCombat", false)
+                    addon:ApplyAttributes()
                 end
             end
         end
@@ -753,13 +843,56 @@ function addon:UpdateRegisteredClicks(button)
     end
 end

-function addon:UpdateEverything()
-    -- Update all running attributes and windows (block)
-    addon:UpdateAttributes()
-    addon:UpdateGlobalAttributes()
-    addon:UpdateRegisteredClicks()
-    addon:UpdateOptionsPanel()
+-- Handler function for message indicating that a change as occurred
+-- with the configured bindings. This is the only place that the
+-- bindings should be re-computed. If this handler is called during
+-- combat than execution should be deferred until the user exits
+-- combat.
+function addon:BINDINGS_CHANGED()
+    if InCombatLockdown() then
+        self:Defer("BINDINGS_CHANGED")
+        return
+    end
+
+    -- Clear any existing attributes
+    self:ClearAttributes()
+
+    -- Very simple optimisation. If the player has no 'ooc' bindings
+    -- set, then attributes can be applied once and then only updated
+    -- when the bindings list is changed.
+    local has_ooc = false
+    for idx, entry in ipairs(self.bindings) do
+        if entry.sets.ooc then
+             has_ooc = true
+            break
+        end
+    end
+
+    self.has_ooc = has_ooc
+
+    -- Update all click/binding attributes
+    self:UpdateAttributes()
+
+    -- Update the bindings list, if open
     CliqueConfig:UpdateList()
+
+    -- Update the actual attributes on all frames
+    self:ApplyAttributes()
+end
+
+function addon:BLACKLIST_CHANGED()
+    if InCombatLockdown() then
+        self:Defer("BLACKLIST_CHANGED")
+        return
+    end
+
+    -- Update the registered clicks, to catch any unblacklisted frames
+    self:UpdateRegisteredClicks()
+    -- Update the options panel
+    self:UpdateOptionsPanel()
+
+    -- Update the actual attributes on all frames
+    self:ApplyAttributes()
 end

 SLASH_CLIQUE1 = "/clique"
diff --git a/FrameOptionsPanel.lua b/FrameOptionsPanel.lua
index 3bc2104..1c37150 100644
--- a/FrameOptionsPanel.lua
+++ b/FrameOptionsPanel.lua
@@ -170,9 +170,7 @@ function panel.okay()
         end
     end

-    addon:ClearAttributes()
-    addon:UpdateBlacklist()
-    addon:UpdateAttributes()
+    addon:FireMessage("BLACKLIST_CHANGED")
 end

 function panel.refresh()
diff --git a/TestSuite.lua b/TestSuite.lua
index bbd1f5f..79581f7 100644
--- a/TestSuite.lua
+++ b/TestSuite.lua
@@ -50,7 +50,6 @@ end)
 local function makeprofile(bindings)

     addon:ClearAttributes()
-    addon:ClearGlobalAttributes()

     local tempName = "Temp" .. GetTime()
     local suiteName = addonName .. "TestSuite"
@@ -64,7 +63,7 @@ local function makeprofile(bindings)
         table.insert(addon.bindings, v)
     end

-    addon:UpdateEverything()
+    addon:FireMessage("BINDINGS_CHANGED")
 end

 local function makebind(key)
diff --git a/Utils.lua b/Utils.lua
index 52aca79..5931214 100644
--- a/Utils.lua
+++ b/Utils.lua
@@ -306,39 +306,3 @@ function addon:GetBindingPrefixSuffix(binding, global)

     return prefix, suffix
 end
-
-
--- This function examines the current state of the game
-function addon:ShouldSetBinding(binding, global)
-    local apply = false
-
-    -- Check for global bindings first in isolation
-    if binding.sets.hovercast or binding.sets.global then
-        if global then
-            return true
-        end
-    elseif global then
-        return false
-    end
-
-    if binding.sets.enemy or binding.sets.friend then
-        apply = true
-    end
-
-    if binding.sets.ooc then
-        if UnitAffectingCombat("player") or addon.partyincombat then
-            apply = false
-        else
-            apply = true
-        end
-    end
-
-    if binding.sets.default then
-        apply = true
-    end
-
-    return apply
-end
-
-function addon:ShouldSetBindingOnFrame(binding, frame)
-end