flickerstreak@10: --[[ flickerstreak@10: Name: ReBound-1.0 flickerstreak@10: Revision: $Rev: 1 $ flickerstreak@10: Author: Flick flickerstreak@10: Website: flickerstreak@10: Documentation: flickerstreak@10: SVN: flickerstreak@10: Description: Library for point-and-click key binding interfaces flickerstreak@10: License: MIT flickerstreak@10: Dependencies: AceLibrary, AceEvent-2.0, AceLocale-2.2 flickerstreak@10: ]] flickerstreak@10: flickerstreak@10: flickerstreak@10: local version_major, version_minor = "ReBound-1.0", "$Rev: 1 $" flickerstreak@10: flickerstreak@10: if not AceLibrary then error(version_major .. " requires AceLibrary.") end flickerstreak@10: if not AceLibrary:IsNewVersion(version_major, version_minor) then return end flickerstreak@10: if not AceLibrary:HasInstance("AceEvent-2.0") then error(version_major .. " requires AceEvent-2.0.") end flickerstreak@10: if not AceLibrary:HasInstance("AceLocale-2.2") then error(version_major .. " requires AceLocale-2.2.") end flickerstreak@10: flickerstreak@10: local L = AceLibrary("AceLocale-2.2"):new("ReBound") flickerstreak@10: flickerstreak@10: local colorGreen = "|cff00ff00" flickerstreak@10: local colorOff = "|r" flickerstreak@10: flickerstreak@10: local mouseButtonConvert = { flickerstreak@10: MiddleButton = "BUTTON3", flickerstreak@10: Button4 = "BUTTON4", flickerstreak@10: Button5 = "BUTTON5" flickerstreak@10: } flickerstreak@10: flickerstreak@10: -- localization flickerstreak@10: L:RegisterTranslations( "enUS", function() flickerstreak@10: return { flickerstreak@10: ["none"] = true, flickerstreak@10: ["Right-click"] = true, flickerstreak@10: ["Click to select for binding"] = true, flickerstreak@10: ["Shift-click to clear binding"] = true, flickerstreak@10: ["Press a key to assign binding"] = true, flickerstreak@10: ["is now unbound"] = true, flickerstreak@10: } flickerstreak@10: end ) flickerstreak@10: flickerstreak@10: flickerstreak@10: flickerstreak@10: flickerstreak@10: local ReBound = { } flickerstreak@10: flickerstreak@10: flickerstreak@10: --[[ flickerstreak@10: Arguments: flickerstreak@10: key: A string representation of a key, suitable for passing to SetBinding. flickerstreak@10: target: The frame with an OnClick handler to attach a click-binding to flickerstreak@10: [button]: The mouse button to emulate. Default is "LeftButton". flickerstreak@10: flickerstreak@10: Returns: flickerstreak@10: nothing. flickerstreak@10: flickerstreak@10: Notes: flickerstreak@10: This does not save the bindings. flickerstreak@10: ]] flickerstreak@10: function ReBound:SetBinding( key, target, button ) flickerstreak@10: if not key then self:error("ReBound:SetBinding() requires a key argument.") end flickerstreak@10: if not target then self:error("ReBound:SetBinding() requires a binding target argument") end flickerstreak@10: button = button or "LeftButton" flickerstreak@10: flickerstreak@10: -- prevent setting a binding that's already set flickerstreak@10: local current = { self:GetBinding(target,button) } flickerstreak@10: for _, b in pairs(current) do flickerstreak@10: if b == key then flickerstreak@10: return flickerstreak@10: end flickerstreak@10: end flickerstreak@10: flickerstreak@10: -- clear the old binding for the key. This isn't strictly necessary, but it allows us to collect flickerstreak@10: -- notification of the unbinding in one place (ClearBinding). flickerstreak@10: self:ClearBinding( key ) flickerstreak@10: flickerstreak@10: -- clear the old binding for the target and button (silently) flickerstreak@10: self:ClearBinding( nil, target, button, true ) flickerstreak@10: flickerstreak@10: -- set the new binding flickerstreak@10: SetBindingClick(key, target:GetName(), button) flickerstreak@10: flickerstreak@10: -- notify listeners, e.g. for storing the setting flickerstreak@10: self.event:TriggerEvent("REBOUND_BIND", key, target:GetName(), button) flickerstreak@10: end flickerstreak@10: flickerstreak@10: flickerstreak@10: --[[ flickerstreak@10: Arguments: flickerstreak@10: [key]: A string representation of a key, suitable for passing to SetBinding. This can be nil if target is specified. flickerstreak@10: [target]: The frame with a click keybinding to search for a key. flickerstreak@10: [button]: The mouse button to emulate. Default is "LeftButton". Only used with [target]. flickerstreak@10: [silent]: if true, omits printout. flickerstreak@10: flickerstreak@10: Returns: flickerstreak@10: nothing. flickerstreak@10: flickerstreak@10: Notes: flickerstreak@10: If key is provided, then the binding for that key is cleared. If key is not provided and target is provided, then flickerstreak@10: all the bindings attached to the click-binding for that target are cleared. flickerstreak@10: flickerstreak@10: This does NOT save the bindings. Call SaveBindings() to commit the bindings to disk. flickerstreak@10: ]] flickerstreak@10: function ReBound:ClearBinding( key, target, button, silent ) flickerstreak@10: if not target and not key then self:error("ReBound:ClearBinding() requires a key or click-binding target argument") end flickerstreak@10: button = button or "LeftButton" flickerstreak@10: flickerstreak@10: local keys = key and { key } or { self:GetBinding(target,button) } flickerstreak@10: for _, k in ipairs(keys) do flickerstreak@10: -- Print a notification message flickerstreak@10: if k and not silent then flickerstreak@10: local action = GetBindingAction(k) flickerstreak@10: if action then flickerstreak@10: local name = GetBindingText(action,"BINDING_NAME_") flickerstreak@10: local keyTxt = GetBindingText(k,"KEY_") flickerstreak@10: -- make click-bindings look prettier flickerstreak@10: local f, b = name:match("CLICK (.+)\:(.+)") flickerstreak@10: if f then flickerstreak@10: name = f flickerstreak@10: if b ~= "LeftButton" then flickerstreak@10: if b == "RightButton" then b = "Right Click" end flickerstreak@10: name = f .."-"..b flickerstreak@10: end flickerstreak@10: end flickerstreak@10: if name and #name > 0 then flickerstreak@10: UIErrorsFrame:AddMessage(name.." ("..colorGreen..keyTxt..colorOff..") "..L["is now unbound"].."!") flickerstreak@10: end flickerstreak@10: end flickerstreak@10: end flickerstreak@10: SetBinding(k,nil) flickerstreak@10: self.event:TriggerEvent("REBOUND_UNBIND", k) flickerstreak@10: end flickerstreak@10: end flickerstreak@10: flickerstreak@10: flickerstreak@10: --[[ flickerstreak@10: Gets the keys bound to clicking a frame. flickerstreak@10: flickerstreak@10: Arguments: flickerstreak@10: target: target frame to query flickerstreak@10: [button]: mouse button to emulate ("LeftButton", "RightButton") flickerstreak@10: flickerstreak@10: Returns: flickerstreak@10: key1, key2, key3, etc, as strings. flickerstreak@10: ]] flickerstreak@10: function ReBound:GetBinding( target, button ) flickerstreak@10: if not target then self:error("ReBound:GetBinding() requires a target frame argument") end flickerstreak@10: button = button or "LeftButton" flickerstreak@10: return GetBindingKey("CLICK "..target:GetName()..":"..button) flickerstreak@10: end flickerstreak@10: flickerstreak@10: flickerstreak@10: --[[ flickerstreak@10: Registers a target frame by creating a click-binding frame and putting that frame in the list of flickerstreak@10: registered frames, which can then be all shown/hidden as one unit. flickerstreak@10: flickerstreak@10: Arguments: flickerstreak@10: target = the frame whose OnClick handler should be the target of keybinding flickerstreak@10: flickerstreak@10: Returns: flickerstreak@10: A clickbinder frame. flickerstreak@10: ]] flickerstreak@10: function ReBound:Register( target ) flickerstreak@10: local f = self:CreateClickBindingFrame(target) flickerstreak@10: self.frames[target] = f flickerstreak@10: return f flickerstreak@10: end flickerstreak@10: flickerstreak@10: flickerstreak@10: --[[ flickerstreak@10: Unregisters a target frame by removing it from the internal list. Does nothing to the clickbinding frame. flickerstreak@10: flickerstreak@10: Arguments: flickerstreak@10: target = the frame whose OnClick handler should no longer be the target of keybinding flickerstreak@10: flickerstreak@10: Returns: flickerstreak@10: nothing. flickerstreak@10: ]] flickerstreak@10: function ReBound:Unregister( target ) flickerstreak@10: self.frames[target] = nil flickerstreak@10: end flickerstreak@10: flickerstreak@10: flickerstreak@10: --[[ flickerstreak@10: Shows all the registered click binding frames. flickerstreak@10: ]] flickerstreak@10: function ReBound:ShowAll() flickerstreak@10: if InCombatLockdown() then flickerstreak@10: -- can't set bindings while in combat, so don't bother showing them flickerstreak@10: UIErrorsFrame:AddMessage(ERR_NOT_IN_COMBAT) flickerstreak@10: else flickerstreak@10: for _, f in pairs(self.frames) do flickerstreak@10: f:Show() flickerstreak@10: end flickerstreak@10: end flickerstreak@10: end flickerstreak@10: flickerstreak@10: flickerstreak@10: --[[ flickerstreak@10: Hides all the registered click binding frames. flickerstreak@10: ]] flickerstreak@10: function ReBound:HideAll() flickerstreak@10: for _, f in pairs(self.frames) do flickerstreak@10: f:Hide() flickerstreak@10: end flickerstreak@10: end flickerstreak@10: flickerstreak@10: -- click binding frame implementation functions flickerstreak@10: local function ShowTooltip1( self ) flickerstreak@10: local target = self:GetParent() flickerstreak@10: flickerstreak@10: GameTooltip:ClearLines() flickerstreak@10: GameTooltip:SetOwner(self,"ANCHOR_TOPRIGHT") flickerstreak@10: -- line 1: button name and current binding flickerstreak@10: GameTooltip:AddDoubleLine(target:GetName(), colorGreen.."("..(self.ReBound:GetBinding(target,"LeftButton") or L["none"])..")"..colorOff) flickerstreak@10: -- line 2: current right-click binding (if any) flickerstreak@10: local binding2 = self.ReBound:GetBinding(target,"RightButton") flickerstreak@10: if binding2 then flickerstreak@10: GameTooltip:AddDoubleLine(L["Right-click"]..":", colorGreen.."("..binding2..")"..colorOff) flickerstreak@10: end flickerstreak@10: -- line 3: instructions flickerstreak@10: GameTooltip:AddLine(L["Click to select for binding"]) flickerstreak@10: GameTooltip:AddLine(L["Shift-click to clear binding"]) flickerstreak@10: GameTooltip:Show() flickerstreak@10: end flickerstreak@10: flickerstreak@10: local function ShowTooltip2( self ) flickerstreak@10: if GameTooltip:IsOwned(self) then flickerstreak@10: local target = self:GetParent() flickerstreak@10: GameTooltip:ClearLines() flickerstreak@10: GameTooltip:SetOwner(self) flickerstreak@10: local clickSuffix = self.selectedButton == "RightButton" and (" ("..L["Right-click"]..")") or "" flickerstreak@10: -- line 1: button name and binding to be set flickerstreak@10: GameTooltip:AddDoubleLine(target:GetName()..clickSuffix, colorGreen.."("..(self.ReBound:GetBinding(target,self.selectedButton) or L["none"])..")"..colorOff) flickerstreak@10: -- line 2: instructions flickerstreak@10: GameTooltip:AddLine(colorGreen..L["Press a key to assign binding"]..colorOff) flickerstreak@10: GameTooltip:Show() flickerstreak@10: end flickerstreak@10: end flickerstreak@10: flickerstreak@10: local function OnClick( self, button ) flickerstreak@10: if button == "LeftButton" or button == "RightButton" then flickerstreak@10: if IsShiftKeyDown() then flickerstreak@10: self.ReBound:ClearBinding( nil, self:GetParent(), button ) flickerstreak@10: self.selectedButton = nil flickerstreak@10: self:EnableKeyboard(false) flickerstreak@10: ShowTooltip1(self) flickerstreak@10: else flickerstreak@10: self.selectedButton = button flickerstreak@10: self:EnableKeyboard(true) flickerstreak@10: ShowTooltip2(self) flickerstreak@10: end flickerstreak@10: elseif self.selectedButton then flickerstreak@10: self.ReBound:SetBinding( mouseButtonConvert[button], self:GetParent(), self.selectedButton ) flickerstreak@10: self.selectedButton = nil flickerstreak@10: self:EnableKeyboard(false) flickerstreak@10: ShowTooltip1(self) flickerstreak@10: end flickerstreak@10: end flickerstreak@10: flickerstreak@10: local function OnEnter( self ) flickerstreak@10: -- clear current binding button flickerstreak@10: self.selectedButton = nil flickerstreak@10: -- show tooltip 1 flickerstreak@10: ShowTooltip1(self) flickerstreak@10: end flickerstreak@10: flickerstreak@10: local function OnLeave( self ) flickerstreak@10: -- disable keyboard input, if it was enabled flickerstreak@10: self:EnableKeyboard(false) flickerstreak@10: -- hide tooltip flickerstreak@10: if GameTooltip:IsOwned(self) then flickerstreak@10: GameTooltip:Hide() flickerstreak@10: end flickerstreak@10: end flickerstreak@10: flickerstreak@10: local function OnKeyDown( self, key ) flickerstreak@10: if key == nil or key == "UNKNOWN" or key == "SHIFT" or key == "CTRL" or key == "ALT" then flickerstreak@10: return flickerstreak@10: end flickerstreak@10: if IsShiftKeyDown() then key = "SHIFT-"..key end flickerstreak@10: if IsControlKeyDown() then key = "CTRL-"..key end flickerstreak@10: if IsAltKeyDown() then key = "ALT-"..key end flickerstreak@10: flickerstreak@10: if key ~= "ESCAPE" then flickerstreak@10: self.ReBound:SetBinding( key, self:GetParent(), self.selectedButton ) flickerstreak@10: end flickerstreak@10: flickerstreak@10: self:EnableKeyboard(false) flickerstreak@10: self.selectedButton = nil flickerstreak@10: ShowTooltip1(self) flickerstreak@10: end flickerstreak@10: flickerstreak@10: --[[ flickerstreak@10: Creates a click-binding frame attached to the target frame, which can be used for point-and-click keybind assignments. The flickerstreak@10: frame is initially hidden by default. It is not registered with ReBound for automatic show/hide: use Register() for that. flickerstreak@10: flickerstreak@10: Arguments: flickerstreak@10: target - the frame whose OnClick handler should be the target of keybinding flickerstreak@10: flickerstreak@10: Returns: flickerstreak@10: A clickbinder frame. flickerstreak@10: ]] flickerstreak@10: function ReBound:CreateClickBindingFrame( target ) flickerstreak@10: local f = CreateFrame("Button", nil, target) flickerstreak@10: f.ReBound = self flickerstreak@10: f:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square") flickerstreak@10: f:SetToplevel(1) flickerstreak@10: f:SetFrameStrata("DIALOG") flickerstreak@10: f:RegisterForClicks("AnyUp") flickerstreak@10: f:SetScript("OnClick", OnClick) flickerstreak@10: f:SetScript("OnEnter", OnEnter) flickerstreak@10: f:SetScript("OnLeave", OnLeave) flickerstreak@10: f:SetScript("OnKeyDown", OnKeyDown) flickerstreak@10: f:SetAllPoints(target) flickerstreak@10: f:Hide() flickerstreak@10: return f flickerstreak@10: end flickerstreak@10: flickerstreak@10: flickerstreak@10: flickerstreak@10: -- library setup flickerstreak@10: flickerstreak@10: local function activate( self, oldLib, oldDeactivate ) flickerstreak@10: -- set initial values flickerstreak@10: self.mode = oldLib and oldLib.mode or "char" flickerstreak@10: self.frames = { } flickerstreak@10: flickerstreak@10: self.event = AceLibrary("AceOO-2.0").Class("AceEvent-2.0"):new() flickerstreak@10: self.event:RegisterEvent("PLAYER_REGEN_DISABLED", function() self:HideAll() end) flickerstreak@10: flickerstreak@10: if oldDeactivate then flickerstreak@10: oldDeactivate(oldLib) flickerstreak@10: end flickerstreak@10: end flickerstreak@10: flickerstreak@10: local function deactivate( self ) flickerstreak@10: self.event:UnregisterEvent("PLAYER_REGEN_DISABLED") flickerstreak@10: end flickerstreak@10: flickerstreak@10: AceLibrary:Register(ReBound, version_major, version_minor, activate, deactivate) flickerstreak@10: ReBound = nil