Mercurial > wow > reaction
view libs/ReBound-1.0/ReBound-1.0.lua @ 17:639282f3a0e0
More cleanup of main.lua, ReBound-1.0.lua
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Fri, 23 Mar 2007 19:28:30 +0000 |
parents | 2735edcf9ab7 |
children |
line wrap: on
line source
--[[ Name: ReBound-1.0 Revision: $Rev: 3 $ Author: Flick Website: Documentation: SVN: Description: A library to assist with click-binding License: MIT Dependencies: AceLibrary, AceEvent-2.0, AceLocale-2.2, AceOO-2.0, AceConsole-2.0 ]] local version_major, version_minor = "ReBound-1.0", "$Rev: 3 $" if not AceLibrary then error(version_major .. " requires AceLibrary.") end if not AceLibrary:IsNewVersion(version_major, version_minor) then return end for _, lib in pairs({ "AceEvent-2.0", "AceLocale-2.2", "AceOO-2.0", "AceConsole-2.0" }) do if not AceLibrary:HasInstance(lib) then error(version_major .. " requires "..lib) end end local L = AceLibrary("AceLocale-2.2"):new("ReBound") -- localization L:RegisterTranslations( "enUS", function() return { ["none"] = true, ["Click"] = true, ["Right-click"] = true, ["Shift-click"] = true, ["Shift-right-click"] = true, ["Right Click"] = true, ["to select for binding"] = true, ["to select for alternate (right-click) binding"] = true, ["to clear binding"] = true, ["to clear alternate (right-click) binding"] = true, ["Press a key to assign binding"] = true, ["is now unbound"] = true, } end ) local kbValidate = AceLibrary("AceConsole-2.0").keybindingValidateFunc local colorGreen = "|cff00ff00" local colorOrange = "|cffffcc00" local colorOff = "|r" local mouseButtonConvert = { LeftButton = "BUTTON1", RightButton = "BUTTON2", MiddleButton = "BUTTON3", Button4 = "BUTTON4", Button5 = "BUTTON5" } -- TODO: localize local keybindAbbreviations = { [KEY_BACKSPACE] = "BkSp", [KEY_BUTTON3] = "M-3", [KEY_BUTTON4] = "M-4", [KEY_BUTTON5] = "M-5", [KEY_DOWN] = "Down", [KEY_ESCAPE] = "Esc", [KEY_INSERT] = "Ins", [KEY_LEFT] = "Left", [KEY_MOUSEWHEELDOWN] = "M-Down", [KEY_MOUSEWHEELUP] = "M-Up", [KEY_NUMLOCK] = "NumLk", [KEY_NUMPAD0] = "Np0", [KEY_NUMPAD1] = "Np1", [KEY_NUMPAD2] = "Np2", [KEY_NUMPAD3] = "Np3", [KEY_NUMPAD4] = "Np4", [KEY_NUMPAD5] = "Np5", [KEY_NUMPAD6] = "Np6", [KEY_NUMPAD7] = "Np7", [KEY_NUMPAD8] = "Np8", [KEY_NUMPAD9] = "Np9", [KEY_NUMPADDECIMAL] = "Np.", [KEY_NUMPADDIVIDE] = "Np/", [KEY_NUMPADMINUS] = "Np-", [KEY_NUMPADMULTIPLY] = "Np*", [KEY_NUMPADPLUS] = "Np+", [KEY_PAGEDOWN] = "PgDn", [KEY_PAGEUP] = "PgUp", [KEY_PRINTSCREEN] = "PrScr", [KEY_RIGHT] = "Right", [KEY_SCROLLLOCK] = "ScrLk", [KEY_SPACE] = "Sp", [KEY_UP] = "Up", } local ReBound = AceLibrary("AceOO-2.0").Class("AceEvent-2.0") --[[ ReBound publishes the following events: -- temporary bindings (prior to SaveBindings() being called) REBOUND_BIND_TEMP (id, key, targetFrameName, mouseButton) REBOUND_UNBIND_TEMP (id, key) -- permanent bindings (fired all at once when SaveBindings() is called) REBOUND_BIND (id, key, targetFrameName, mouseButton) REBOUND_UNBIND (id, key) These events are published in response to click actions ONLY. This means that if a key is unbound from a click-binding frame, and bound to some other action, then REBOUND_UNBIND(id,key) will be fired. ]] --[[ Calls to new() which share ids will return an existing object with that id, similar to AceLocale-2.2. ]] ReBound.instances = { } local super_new = ReBound.new function ReBound:new( id ) local instances = self.instances instances[id] = instances[id] or super_new(self,id) return instances[id] end --[[ Class object constructor arguments: id : the ID that will be provided in events. This can be absolutely anything, but a string is recommended. ]] local super_init = ReBound.super.prototype.init function ReBound.prototype:init( id ) super_init(self) self.id = id self.frames = { } self.pending = { } self.bindings = { } end --[[ Arguments: key: A string representation of a key, suitable for passing to SetBinding. target: The frame with an OnClick handler to which the click-binding should be attached [button]: The mouse button to emulate. Default is "LeftButton". [silent]: boolean - whether to suppress messages about keys being unbound. Returns: nothing. Notes: This does not save the bindings. ]] function ReBound.prototype:SetBinding( key, target, button ) if not key then error("ReBound:SetBinding() requires a key argument.") end if not target then error("ReBound:SetBinding() requires a binding target argument") end if key and not kbValidate(key) then error("ReBound:SetBinding(): invalid key code "..tostring(key)) end button = button or "LeftButton" -- prevent setting a binding that's already set local current = { self:GetBinding(target,button) } for _, b in pairs(current) do if b == key then return end end -- clear the old binding for the key. This isn't strictly necessary, but it allows us to collect -- notification of the unbinding in one place (ClearBinding). self:ClearBinding( key, nil, nil, silent ) -- clear the old binding for the target and button (silently) self:ClearBinding( nil, target, button, true ) -- set the new binding SetBindingClick(key, target:GetName(), button) -- store the temporary binding as "pending" for later notification table.insert(self.pending, key) -- notify listeners, e.g. for displaying the setting self:TriggerEvent("REBOUND_BIND_TEMP", self.id, key, target:GetName(), button) end --[[ Arguments: [key]: A string representation of a key, suitable for passing to SetBinding. This can be nil if target is specified. [target]: The frame with a click keybinding to search for a key. [button]: The mouse button to emulate. Default is "LeftButton". Only used with [target]. [silent]: if true, omits printout. Returns: nothing. Notes: If key is provided, then the binding for that key is cleared. If key is not provided and target is provided, then all the bindings attached to the click-binding for that target are cleared. This does NOT save the bindings. Call SaveBindings() to commit the bindings to disk. ]] function ReBound.prototype:ClearBinding( key, target, button, silent ) if not target and not key then error("ReBound:ClearBinding() requires a key or click-binding target argument") end button = button or "LeftButton" local keys = key and { key } or { self:GetBinding(target,button) } for _, k in ipairs(keys) do -- Print a notification message if k and not silent then local action = GetBindingAction(k) if action then local name = GetBindingText(action,"BINDING_NAME_") local keyTxt = GetBindingText(k,"KEY_") -- make click-bindings look prettier local f, b = name:match("CLICK (.+)\:(.+)") if f then name = f if b ~= "LeftButton" then if b == "RightButton" then b = L["Right Click"] end name = f .."-"..b end end if name and #name > 0 then UIErrorsFrame:AddMessage(name.." ("..colorGreen..keyTxt..colorOff..") "..L["is now unbound"].."!") end end end SetBinding(k,nil) table.insert(self.pending,k) self:TriggerEvent("REBOUND_UNBIND_TEMP", self.id, k) end end --[[ Gets the keys currently click-bound to a frame. Arguments: target: target frame to query [button]: mouse button to emulate ("LeftButton", "RightButton") Returns: key1, key2, key3, etc, as strings. ]] function ReBound.prototype:GetBinding( target, button ) if not target then error("ReBound:GetBinding() requires a target frame argument") end button = button or "LeftButton" return GetBindingKey("CLICK "..target:GetName()..":"..button) end --[[ Gets the localized, abbreviated key cap text for the primary binding on a target. Abbreviations are more aggressive than the standard Blizzard abbreviation (which just shortens modifier keys) Arguments: target: target frame to query [abbrev]: boolean flag to abbreviate the result [button]: mouse button to emulate ("LeftButton", "RightButton") Returns: abbreviated, localized key label ]] function ReBound.prototype:GetBindingText( target, abbrev, button ) local key = self:GetBinding(target,button) local txt = key and GetBindingText(key, "KEY_", abbrev and 1) or "" if txt and abbrev then -- further abbreviate some key names txt = string.gsub(txt, "[^%-]+$", keybindAbbreviations) -- the above does not handle "num pad -" txt = string.gsub(txt, KEY_NUMPADMINUS, keybindAbbreviations) end return txt end --[[ Publishes permanent binding notification events. ]] local function PublishBindings(self) for _, key in ipairs(self.pending) do local action = GetBindingAction(key) local frame, button if action then frame, button = action:match("CLICK (.+)\:(.+)") end if frame == nil then self:TriggerEvent("REBOUND_UNBIND", self.id, key) else self:TriggerEvent("REBOUND_BIND", self.id, key, frame, button) end end self.pending = { } end --[[ Saves the bindings using the current scheme. Also publishes events indicating that the bindings have been saved/cleared permanently. ]] function ReBound.prototype:SaveBindings() SaveBindings(GetCurrentBindingSet()) -- will trigger an UPDATE_BINDINGS event. PublishBindings(self) end --[[ Reverts the bindings to the ones previously saved. Also publishes events indicating that the bindings have been reverted. ]] function ReBound.prototype:RevertBindings() LoadBindings(GetCurrentBindingSet()) -- should trigger an UPDATE_BINDINGS event. PublishBindings(self) end --[[ Clears all bindings associated with registered frames. This is useful, for example, when switching profiles and the keybinding data is stored in the profile. ]] function ReBound.prototype:ClearRegisteredBindings() for f, _ in pairs(self.frames) do self:ClearBinding(nil,f,"LeftButton",true) self:ClearBinding(nil,f,"RightButton",true) end end --[[ Registers a target frame by creating a click-binding frame and putting that frame in the list of registered frames, which can then be all shown/hidden as one unit. Arguments: target = the frame whose OnClick handler should be the target of keybinding Returns: A clickbinder frame. ]] function ReBound.prototype:Register( target ) local f = self:CreateClickBindingFrame(target) self.frames[target] = f return f end --[[ Unregisters a target frame by removing it from the internal list. Does nothing to the clickbinding frame. Arguments: target = the frame whose OnClick handler should no longer be managed. do NOT pass the clickbinding frame. Returns: nothing. ]] function ReBound.prototype:Unregister( target ) self.frames[target] = nil end --[[ Unregisters all registered frames. ]] function ReBound.prototype:UnregisterAll() self.frames = { } end --[[ Shows all the registered click binding frames. ]] function ReBound.prototype:ShowRegisteredFrames() if InCombatLockdown() then -- can't set bindings while in combat, so don't bother showing them UIErrorsFrame:AddMessage(ERR_NOT_IN_COMBAT) else for _, f in pairs(self.frames) do f:Show() end end end --[[ Hides all the registered click binding frames. ]] function ReBound.prototype:HideRegisteredFrames() -- because these frames aren't protected, there's no restriction -- on hiding them while in combat. for _, f in pairs(self.frames) do f:Hide() end end -- click binding frame implementation functions local function ShowTooltip1( self ) local target = self:GetParent() GameTooltip:ClearLines() GameTooltip:SetOwner(self,"ANCHOR_TOPRIGHT") -- line 1: button name and current binding GameTooltip:AddDoubleLine(target:GetName(), colorGreen.."("..(self.ReBound:GetBinding(target,"LeftButton") or L["none"])..")"..colorOff) -- line 2: current right-click binding (if any) local binding2 = self.ReBound:GetBinding(target,"RightButton") if binding2 then GameTooltip:AddDoubleLine(L["Right-click"]..":", colorGreen.."("..binding2..")"..colorOff) end -- line 3: instructions GameTooltip:AddLine(colorGreen..L["Click"]..colorOff.." "..L["to select for binding"]) GameTooltip:AddLine(colorGreen..L["Shift-click"]..colorOff.." "..L["to clear binding"]) GameTooltip:AddLine("") GameTooltip:AddLine(colorOrange..L["Right-click"]..colorOff.." "..L["to select for alternate (right-click) binding"]) GameTooltip:AddLine(colorOrange..L["Shift-right-click"]..colorOff.." "..L["to clear alternate (right-click) binding"]) GameTooltip:Show() end local function ShowTooltip2( self ) if GameTooltip:IsOwned(self) then local target = self:GetParent() GameTooltip:ClearLines() GameTooltip:SetOwner(self) local clickSuffix = self.selectedButton == "RightButton" and (" ("..L["Right-click"]..")") or "" -- line 1: button name and binding to be set GameTooltip:AddDoubleLine(target:GetName()..clickSuffix, colorGreen.."("..(self.ReBound:GetBinding(target,self.selectedButton) or L["none"])..")"..colorOff) -- line 2: instructions GameTooltip:AddLine(colorGreen..L["Press a key to assign binding"]..colorOff) GameTooltip:Show() end end local function OnClick( self, button ) if button == "LeftButton" or button == "RightButton" then if IsShiftKeyDown() then self.ReBound:ClearBinding( nil, self:GetParent(), button ) self.selectedButton = nil self:EnableKeyboard(false) ShowTooltip1(self) else self.selectedButton = button self:EnableKeyboard(true) ShowTooltip2(self) end elseif self.selectedButton then self.ReBound:SetBinding( mouseButtonConvert[button], self:GetParent(), self.selectedButton ) self.selectedButton = nil self:EnableKeyboard(false) ShowTooltip1(self) end end local function OnEnter( self ) -- clear current binding button self.selectedButton = nil -- show tooltip 1 ShowTooltip1(self) end local function OnLeave( self ) -- disable keyboard input, if it was enabled self:EnableKeyboard(false) -- hide tooltip if GameTooltip:IsOwned(self) then GameTooltip:Hide() end end local function OnKeyDown( self, key ) if key == nil or key == "UNKNOWN" or key == "SHIFT" or key == "CTRL" or key == "ALT" then return end if IsShiftKeyDown() then key = "SHIFT-"..key end if IsControlKeyDown() then key = "CTRL-"..key end if IsAltKeyDown() then key = "ALT-"..key end if key ~= "ESCAPE" then self.ReBound:SetBinding( key, self:GetParent(), self.selectedButton ) end self:EnableKeyboard(false) self.selectedButton = nil ShowTooltip1(self) end --[[ Creates a click-binding frame attached to the target frame, which can be used for point-and-click keybind assignments. The frame is initially hidden by default. It is not registered with ReBound for automatic show/hide: use Register() for that. Arguments: target - the frame whose OnClick handler should be the target of keybinding Returns: A clickbinder frame. ]] function ReBound.prototype:CreateClickBindingFrame( target ) local f = CreateFrame("Button", nil, target) f.ReBound = self f:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square") f:SetToplevel(1) f:SetFrameStrata("DIALOG") f:RegisterForClicks("AnyUp") f:SetScript("OnClick", OnClick) f:SetScript("OnEnter", OnEnter) f:SetScript("OnLeave", OnLeave) f:SetScript("OnKeyDown", OnKeyDown) f:SetAllPoints(target) f:Hide() return f end -- library setup local function activate( self, oldLib, oldDeactivate ) -- copy the list of active instances self.instances = { } if oldLib and oldLib.instances then for k,v in pairs(oldLib.instances) do self.instances[k] = v end end if oldDeactivate then oldDeactivate(oldLib) end end AceLibrary:Register(ReBound, version_major, version_minor, activate) ReBound = nil