--[[ Name: LibKeyBound-1.0 Revision: $Rev: 95 $ Author(s): Gello, Maul, Toadkiller, Tuller Website: http://www.wowace.com/wiki/LibKeyBound-1.0 Documentation: http://www.wowace.com/wiki/LibKeyBound-1.0 SVN: http://svn.wowace.com/wowace/trunk/LibKeyBound-1.0 Description: An intuitive keybindings system: mouseover frame, click keys or buttons. Dependencies: CallbackHandler-1.0 --]] local MAJOR = 'LibKeyBound-1.0' local MINOR = tonumber(("$Revision: 95 $"):match("(%d+)")) + 90000 --[[ LibKeyBound-1.0 ClickBinder by Gello and TrinityBinder by Maul -> keyBound by Tuller -> LibKeyBound library by Toadkiller Functions needed to implement button:GetHotkey() - returns the current hotkey assigned to the given button Functions to implement if using a custom keybindings system: button:SetKey(key) - binds the given key to the given button button:FreeKey(key) - unbinds the given key from all other buttons button:ClearBindings() - removes all keys bound to the given button button:GetBindings() - returns a string listing all bindings of the given button button:GetActionName() - what we're binding to, used for printing --]] local LibKeyBound, oldminor = LibStub:NewLibrary(MAJOR, MINOR) if not LibKeyBound then return end -- no upgrade needed local _G = _G local NUM_MOUSE_BUTTONS = 31 -- CallbackHandler LibKeyBound.events = LibKeyBound.events or _G.LibStub('CallbackHandler-1.0'):New(LibKeyBound) local L = LibKeyBoundLocale10 LibKeyBound.L = L -- ToDo delete global LibKeyBoundLocale10 at some point LibKeyBound.Binder = LibKeyBound.Binder or {} -- #NODOC function LibKeyBound:Initialize() do local f = CreateFrame('Frame', 'KeyboundDialog', UIParent) f:SetFrameStrata('DIALOG') f:SetToplevel(true) f:EnableMouse(true) f:SetClampedToScreen(true) f:SetWidth(360) f:SetHeight(140) f:SetBackdrop{ bgFile='Interface\\DialogFrame\\UI-DialogBox-Background' , edgeFile='Interface\\DialogFrame\\UI-DialogBox-Border', tile = true, insets = {left = 11, right = 12, top = 12, bottom = 11}, tileSize = 32, edgeSize = 32, } f:SetPoint('TOP', 0, -24) f:Hide() f:SetScript('OnShow', function() PlaySound('igMainMenuOption') end) f:SetScript('OnHide', function() PlaySound('gsTitleOptionExit') end) local tr = f:CreateTitleRegion() tr:SetAllPoints(f) local header = f:CreateTexture(nil, 'ARTWORK') header:SetTexture('Interface\\DialogFrame\\UI-DialogBox-Header') header:SetWidth(256); header:SetHeight(64) header:SetPoint('TOP', 0, 12) local title = f:CreateFontString('ARTWORK') title:SetFontObject('GameFontNormal') title:SetPoint('TOP', header, 'TOP', 0, -14) title:SetText(L.BindingMode) local desc = f:CreateFontString('ARTWORK') desc:SetFontObject('GameFontHighlight') desc:SetJustifyV('TOP') desc:SetJustifyH('LEFT') desc:SetPoint('TOPLEFT', 18, -32) desc:SetPoint('BOTTOMRIGHT', -18, 48) desc:SetText(format(L.BindingsHelp, GetBindingText('ESCAPE', 'KEY_'))) -- Per character bindings checkbox local perChar = CreateFrame('CheckButton', 'KeyboundDialogCheck', f, 'OptionsCheckButtonTemplate') _G[perChar:GetName() .. 'Text']:SetText(CHARACTER_SPECIFIC_KEYBINDINGS) perChar:SetScript('OnShow', function(self) self:SetChecked(GetCurrentBindingSet() == 2) end) local current perChar:SetScript('OnClick', function(self) current = (perChar:GetChecked() and 2) or 1 LoadBindings(current) end) -- Okay bindings checkbox local okayBindings = CreateFrame('CheckButton', 'KeyboundDialogOkay', f, 'OptionsButtonTemplate') getglobal(okayBindings:GetName() .. 'Text'):SetText(OKAY) okayBindings:SetScript('OnClick', function(self) current = (perChar:GetChecked() and 2) or 1 if InCombatLockdown() then self:RegisterEvent('PLAYER_REGEN_ENABLED') else SaveBindings(current) LibKeyBound:Deactivate() end end) okayBindings:SetScript('OnHide', function(self) current = (perChar:GetChecked() and 2) or 1 if InCombatLockdown() then self:RegisterEvent('PLAYER_REGEN_ENABLED') else SaveBindings(current) end end) okayBindings:SetScript('OnEvent', function(self, event) SaveBindings(current) self:UnregisterEvent(event) LibKeyBound:Deactivate() end) -- Cancel bindings checkbox local cancelBindings = CreateFrame('CheckButton', 'KeyboundDialogCancel', f, 'OptionsButtonTemplate') getglobal(cancelBindings:GetName() .. 'Text'):SetText(CANCEL) cancelBindings:SetScript('OnClick', function(self) if InCombatLockdown() then self:RegisterEvent('PLAYER_REGEN_ENABLED') else LoadBindings(GetCurrentBindingSet()) LibKeyBound:Deactivate() end end) cancelBindings:SetScript('OnEvent', function(self, event) LoadBindings(GetCurrentBindingSet()) self:UnregisterEvent(event) LibKeyBound:Deactivate() end) --position buttons perChar:SetPoint('BOTTOMLEFT', 14, 32) cancelBindings:SetPoint('BOTTOMRIGHT', -14, 14) okayBindings:SetPoint('RIGHT', cancelBindings, 'LEFT') self.dialog = f end SlashCmdList['LibKeyBoundSlashCOMMAND'] = function() self:Toggle() end SLASH_LibKeyBoundSlashCOMMAND1 = '/libkeybound' SLASH_LibKeyBoundSlashCOMMAND2 = '/kb' SLASH_LibKeyBoundSlashCOMMAND3 = '/lkb' LibKeyBound.initialized = true end -- Default color to indicate bindable frames in your mod. LibKeyBound.colorKeyBoundMode = LibKeyBound.colorKeyBoundMode or { 0, 1, 1, 0.5 } --[[ LibKeyBound:SetColorKeyBoundMode([r][, g][, b][, a]) --]] --[[ Arguments: number - red, default 0 number - green, default 0 number - blue, default 0 number - alpha, default 1 Example: if (MyMod.keyBoundMode) then overlayFrame:SetBackdropColor(LibKeyBound:GetColorKeyBoundMode()) end ... local r, g, b, a = LibKeyBound:GetColorKeyBoundMode() Notes: * Returns the color to use on your participating buttons during KeyBound Mode * Values are unpacked and ready to use as color arguments --]] function LibKeyBound:SetColorKeyBoundMode(r, g, b, a) r, g, b, a = r or 0, g or 0, b or 0, a or 1 LibKeyBound.colorKeyBoundMode[1] = r LibKeyBound.colorKeyBoundMode[2] = g LibKeyBound.colorKeyBoundMode[3] = b LibKeyBound.colorKeyBoundMode[4] = a LibKeyBound.events:Fire('LIBKEYBOUND_MODE_COLOR_CHANGED') end --[[ Returns: * number - red * number - green * number - blue * number - alpha Example: if (MyMod.keyBoundMode) then overlayFrame:SetBackdropColor(LibKeyBound:GetColorKeyBoundMode()) end ... local r, g, b, a = LibKeyBound:GetColorKeyBoundMode() Notes: * Returns the color to use on your participating buttons during KeyBound Mode * Values are unpacked and ready to use as color arguments --]] function LibKeyBound:GetColorKeyBoundMode() return unpack(LibKeyBound.colorKeyBoundMode) end function LibKeyBound:PLAYER_REGEN_ENABLED() if self.enabled then UIErrorsFrame:AddMessage(L.CombatBindingsEnabled, 1, 0.3, 0.3, 1, UIERRORS_HOLD_TIME) self.dialog:Hide() end end function LibKeyBound:PLAYER_REGEN_DISABLED() if self.enabled then self:Set(nil) UIErrorsFrame:AddMessage(L.CombatBindingsDisabled, 1, 0.3, 0.3, 1, UIERRORS_HOLD_TIME) self.dialog:Show() end end --[[ Notes: * Switches KeyBound Mode between on and off Example: local LibKeyBound = LibStub('LibKeyBound-1.0') LibKeyBound:Toggle() --]] function LibKeyBound:Toggle() if (LibKeyBound:IsShown()) then LibKeyBound:Deactivate() else LibKeyBound:Activate() end end --[[ Notes: * Switches KeyBound Mode to on Example: local LibKeyBound = LibStub('LibKeyBound-1.0') LibKeyBound:Activate() --]] function LibKeyBound:Activate() if not self:IsShown() then if InCombatLockdown() then UIErrorsFrame:AddMessage(L.CannotBindInCombat, 1, 0.3, 0.3, 1, UIERRORS_HOLD_TIME) else self.enabled = true if not self.frame then self.frame = LibKeyBound.Binder:Create() end self:Set(nil) self.dialog:Show() self.events:Fire('LIBKEYBOUND_ENABLED') end end end --[[ Notes: * Switches KeyBound Mode to off Example: local LibKeyBound = LibStub('LibKeyBound-1.0') LibKeyBound:Deactivate() --]] function LibKeyBound:Deactivate() if self:IsShown() then self.enabled = nil self:Set(nil) self.dialog:Hide() self.events:Fire('LIBKEYBOUND_DISABLED') end end --[[ Returns: boolean - true if KeyBound Mode is currently on Example: local LibKeyBound = LibStub('LibKeyBound-1.0') local isKeyBoundMode = LibKeyBound:IsShown() if (isKeyBoundMode) then -- Do something else -- Do another thing end Notes: * Is KeyBound Mode currently on --]] function LibKeyBound:IsShown() return self.enabled end --[[ Arguments: table - the button frame Example: local button = this LibKeyBound:Set(button) Notes: * Sets up button for keybinding * Call this in your OnEnter script for the button * Current bindings are shown in the tooltip * Primary binding is shown in green in the button text --]] function LibKeyBound:Set(button) local bindFrame = self.frame if button and self:IsShown() and not InCombatLockdown() then bindFrame.button = button bindFrame:SetAllPoints(button) bindFrame.text:SetFontObject('GameFontNormalLarge') bindFrame.text:SetText(button:GetHotkey()) if bindFrame.text:GetStringWidth() > bindFrame:GetWidth() then bindFrame.text:SetFontObject('GameFontNormal') end bindFrame:Show() bindFrame:OnEnter() elseif bindFrame then bindFrame.button = nil bindFrame:ClearAllPoints() bindFrame:Hide() end end --[[ Arguments: string - the keyString to shorten Returns: string - the shortened displayString Example: local key1 = GetBindingKey(button:GetName()) local displayKey = LibKeyBound:ToShortKey(key1) return displayKey Notes: * Shortens the key text (returned from GetBindingKey etc.) * Result is suitable for display on a button * Can be used for your button:GetHotkey() return value --]] function LibKeyBound:ToShortKey(key) if key then key = key:upper() key = key:gsub(' ', '') key = key:gsub('ALT%-', L['Alt']) key = key:gsub('CTRL%-', L['Ctrl']) key = key:gsub('SHIFT%-', L['Shift']) key = key:gsub('NUMPAD', L['NumPad']) key = key:gsub('PLUS', '%+') key = key:gsub('MINUS', '%-') key = key:gsub('MULTIPLY', '%*') key = key:gsub('DIVIDE', '%/') key = key:gsub('BACKSPACE', L['Backspace']) for i = 1, NUM_MOUSE_BUTTONS do key = key:gsub('BUTTON' .. i, L['Button' .. i]) end key = key:gsub('CAPSLOCK', L['Capslock']) key = key:gsub('CLEAR', L['Clear']) key = key:gsub('DELETE', L['Delete']) key = key:gsub('END', L['End']) key = key:gsub('HOME', L['Home']) key = key:gsub('INSERT', L['Insert']) key = key:gsub('MOUSEWHEELDOWN', L['Mouse Wheel Down']) key = key:gsub('MOUSEWHEELUP', L['Mouse Wheel Up']) key = key:gsub('NUMLOCK', L['Num Lock']) key = key:gsub('PAGEDOWN', L['Page Down']) key = key:gsub('PAGEUP', L['Page Up']) key = key:gsub('SCROLLLOCK', L['Scroll Lock']) key = key:gsub('SPACEBAR', L['Spacebar']) key = key:gsub('TAB', L['Tab']) key = key:gsub('DOWNARROW', L['Down Arrow']) key = key:gsub('LEFTARROW', L['Left Arrow']) key = key:gsub('RIGHTARROW', L['Right Arrow']) key = key:gsub('UPARROW', L['Up Arrow']) return key end end --[[ Binder Widget ]]-- function LibKeyBound.Binder:Create() local binder = CreateFrame('Button') binder:RegisterForClicks('anyUp') binder:SetFrameStrata('DIALOG') binder:EnableKeyboard(true) binder:EnableMouseWheel(true) for k,v in pairs(self) do binder[k] = v end local bg = binder:CreateTexture() bg:SetTexture(0, 0, 0, 0.5) bg:SetAllPoints(binder) local text = binder:CreateFontString('OVERLAY') text:SetFontObject('GameFontNormalLarge') text:SetTextColor(0, 1, 0) text:SetAllPoints(binder) binder.text = text binder:SetScript('OnClick', self.OnKeyDown) binder:SetScript('OnKeyDown', self.OnKeyDown) binder:SetScript('OnMouseWheel', self.OnMouseWheel) binder:SetScript('OnEnter', self.OnEnter) binder:SetScript('OnLeave', self.OnLeave) binder:SetScript('OnHide', self.OnHide) binder:Hide() return binder end function LibKeyBound.Binder:OnHide() LibKeyBound:Set(nil) end function LibKeyBound.Binder:OnKeyDown(key) local button = self.button if not button then return end if (key == 'UNKNOWN' or key == 'LSHIFT' or key == 'RSHIFT' or key == 'LCTRL' or key == 'RCTRL' or key == 'LALT' or key == 'RALT') then return end local screenshotKey = GetBindingKey('SCREENSHOT') if screenshotKey and key == screenshotKey then Screenshot() return end local openChatKey = GetBindingKey('OPENCHAT') if openChatKey and key == openChatKey then ChatFrameEditBox:Show() return end if key == 'ESCAPE' then self:ClearBindings(button) LibKeyBound:Set(button) return end -- dont bind unmodified left or right button if (key == 'LeftButton' or key == 'RightButton') and not IsModifierKeyDown() then return end --handle mouse button substitutions if key == 'LeftButton' then key = 'BUTTON1' elseif key == 'RightButton' then key = 'BUTTON2' elseif key == 'MiddleButton' then key = 'BUTTON3' elseif key:match('^Button%d+$') then key = key:upper() end --apply modifiers if IsModifierKeyDown() then if IsShiftKeyDown() then key = 'SHIFT-' .. key end if IsControlKeyDown() then key = 'CTRL-' .. key end if IsAltKeyDown() then key = 'ALT-' .. key end end if button:IsMouseOver() then self:SetKey(button, key) LibKeyBound:Set(button) end end function LibKeyBound.Binder:OnMouseWheel(arg1) if arg1 > 0 then self:OnKeyDown('MOUSEWHEELUP') else self:OnKeyDown('MOUSEWHEELDOWN') end end function LibKeyBound.Binder:OnEnter() local button = self.button if button and not InCombatLockdown() then if self:GetRight() >= (GetScreenWidth() / 2) then GameTooltip:SetOwner(self, 'ANCHOR_LEFT') else GameTooltip:SetOwner(self, 'ANCHOR_RIGHT') end if button.GetActionName then GameTooltip:SetText(button:GetActionName(), 1, 1, 1) else GameTooltip:SetText(button:GetName(), 1, 1, 1) end local bindings = self:GetBindings(button) if bindings then GameTooltip:AddLine(bindings, 0, 1, 0) GameTooltip:AddLine(L.ClearTip) else GameTooltip:AddLine(L.NoKeysBoundTip, 0, 1, 0) end GameTooltip:Show() else GameTooltip:Hide() end end function LibKeyBound.Binder:OnLeave() LibKeyBound:Set(nil) GameTooltip:Hide() end --[[ Update Functions ]]-- function LibKeyBound.Binder:ToBinding(button) return format('CLICK %s:LeftButton', button:GetName()) end function LibKeyBound.Binder:FreeKey(button, key) local msg if button.FreeKey then local action = button:FreeKey(key) if button:FreeKey(key) then msg = format(L.UnboundKey, GetBindingText(key, 'KEY_'), action) end else local action = GetBindingAction(key) if action and action ~= '' and action ~= self:ToBinding(button) then msg = format(L.UnboundKey, GetBindingText(key, 'KEY_'), action) end end if msg then UIErrorsFrame:AddMessage(msg, 1, 0.82, 0, 1, UIERRORS_HOLD_TIME) end end function LibKeyBound.Binder:SetKey(button, key) if InCombatLockdown() then UIErrorsFrame:AddMessage(L.CannotBindInCombat, 1, 0.3, 0.3, 1, UIERRORS_HOLD_TIME) else self:FreeKey(button, key) if button.SetKey then button:SetKey(key) else SetBindingClick(key, button:GetName(), 'LeftButton') end local msg if button.GetActionName then msg = format(L.BoundKey, GetBindingText(key, 'KEY_'), button:GetActionName()) else msg = format(L.BoundKey, GetBindingText(key, 'KEY_'), button:GetName()) end UIErrorsFrame:AddMessage(msg, 1, 1, 1, 1, UIERRORS_HOLD_TIME) end end function LibKeyBound.Binder:ClearBindings(button) if InCombatLockdown() then UIErrorsFrame:AddMessage(L.CannotBindInCombat, 1, 0.3, 0.3, 1, UIERRORS_HOLD_TIME) else if button.ClearBindings then button:ClearBindings() else local binding = self:ToBinding(button) while (GetBindingKey(binding)) do SetBinding(GetBindingKey(binding), nil) end end local msg if button.GetActionName then msg = format(L.ClearedBindings, button:GetActionName()) else msg = format(L.ClearedBindings, button:GetName()) end UIErrorsFrame:AddMessage(msg, 1, 1, 1, 1, UIERRORS_HOLD_TIME) end end function LibKeyBound.Binder:GetBindings(button) if button.GetBindings then return button:GetBindings() end local keys local binding = self:ToBinding(button) for i = 1, select('#', GetBindingKey(binding)) do local hotKey = select(i, GetBindingKey(binding)) if keys then keys = keys .. ', ' .. GetBindingText(hotKey, 'KEY_') else keys = GetBindingText(hotKey, 'KEY_') end end return keys end LibKeyBound.EventButton = LibKeyBound.EventButton or CreateFrame('Frame') do local EventButton = LibKeyBound.EventButton EventButton:UnregisterAllEvents() EventButton:SetScript('OnEvent', function(self, event, addon) if (event == 'PLAYER_REGEN_DISABLED') then LibKeyBound:PLAYER_REGEN_DISABLED() elseif (event == 'PLAYER_REGEN_ENABLED') then LibKeyBound:PLAYER_REGEN_ENABLED() elseif (event == 'PLAYER_LOGIN' and not LibKeyBound.initialized) then LibKeyBound:Initialize() EventButton:UnregisterEvent('PLAYER_LOGIN') end end) if IsLoggedIn() and not LibKeyBound.initialized then LibKeyBound:Initialize() elseif not LibKeyBound.initialized then EventButton:RegisterEvent('PLAYER_LOGIN') end EventButton:RegisterEvent('PLAYER_REGEN_ENABLED') EventButton:RegisterEvent('PLAYER_REGEN_DISABLED') end