--[[ Author: Alternator (Massiner of Nathrezim) Copyright 2010 Notes: ]] local Util = BFUtil; local Const = BFConst; local UILib = BFUILib; local CustomAction = BFCustomAction; local Button = BFButton; local Bar = BFBar; local EventFull = BFEventFrames["Full"]; local MiscFrame = BFEventFrames["Misc"]; local Delay = BFEventFrames["Delay"]; local ConfigureLayer = BFConfigureLayer; local DestroyBarOverlay = BFDestroyBarOverlay; --This will get the currently applicable locale, or allocate it if needed (note that locales other than enUS will need the metatable set) BFLocales[GetLocale()] = BFLocales[GetLocale()] or {}; local Locale = BFLocales[GetLocale()]; if (GetLocale() ~= "enUS") then setmetatable(Locale, BFLocales["enUS"]); end Util.ActiveButtons = {}; Util.InactiveButtons = {}; Util.ActiveMacros = {}; Util.ActiveSpells = {}; Util.ActiveItems = {}; Util.ActiveBonusActions = {}; Util.RangeTimerButtons = {}; Util.FlashButtons = {}; Util.ActiveBars = {}; Util.InactiveBars = {}; Util.ActiveTabs = {}; Util.InactiveTabs = {}; Util.SpellIndex = {}; Util.SpellMana = {}; Util.NewSpellIndex = {}; Util.GlowSpells = {}; Util.PetSpellIndex = {}; Util.NewPetSpellIndex = {}; Util.BagItemNameIndex = {}; Util.BagItemIdIndex = {}; Util.BagItemNameId = {}; Util.InvItemNameIndex = {}; Util.InvItemIdIndex = {}; Util.InvItemNameId = {}; Util.GridHidden = true; Util.LowStrata = true; Util.BlizBarWrappers = {}; Util.BlizEnabledBars = {}; Util.CallbackFunctions = {}; Util.CallbackArgs = {}; Util.ButtonWidgetMap = {}; Util.UpdateMacroEventCount = 0; Util.MacroCheckDelayComplete = false; Util.ForceOffCastOnKeyDown = false; Util.MountUselessIndexToIndex = {}; --One quick override function local G_PickupSpellBookItem = PickupSpellBookItem; local function PickupSpellBookItem(NameRank, Book) local Index, Alt_Book = Util.LookupSpellIndex(NameRank); if (Index) then return G_PickupSpellBookItem(Index, Alt_Book); elseif (Book) then return G_PickupSpellBookItem(NameRank, Book); end return G_PickupSpellBookItem(NameRank); end --Dirty fix for classic, will keep it as is until i know what the hell does HasOverrideActionBar... function HasOverrideActionBar() return false; end --[[ Make sure that the saved data is kept inline with the version being run --]] function Util.UpdateSavedData() ---- FIX for MACS, if character data hasn't loaded but is otherwise available in the global save ---- local CharRealm = UnitName("player"); CharRealm = CharRealm.."-"..GetRealmName(); if (ButtonForgeSave == nil and ButtonForgeGlobalBackup ~= nil and ButtonForgeGlobalBackup[CharRealm] ~= nil) then ButtonForgeSave = ButtonForgeGlobalBackup[CharRealm]; end ------The following section updates the per character saved data------ --Need to allocate save structure if (not ButtonForgeSave or ButtonForgeSave["Version"] == nil) then --Swap v0.9.0 / v0.9.1 / v0.9.2 users to the new save structure ButtonForgeSave = {}; ButtonForgeSave["ConfigureMode"] = true; ButtonForgeSave["AdvancedMode"] = false; ButtonForgeSave["RightClickSelfCast"] = false; ButtonForgeSave["Version"] = Const.Version; ButtonForgeSave["VersionMinor"] = Const.VersionMinor; ButtonForgeSave.Bars = {}; ButtonForgeSave["AddonName"] = "Button Forge"; end --v0.9.3 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 3) then for i = 1, #ButtonForgeSave.Bars do ButtonForgeSave.Bars[i]["HBonusBar"] = true; end ButtonForgeSave["VersionMinor"] = 3; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.3", .5, 1, 0, 1); end --v0.9.12 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 12) then for i = 1, #ButtonForgeSave.Bars do ButtonForgeSave.Bars[i]["MacroText"] = true; ButtonForgeSave.Bars[i]["KeyBindText"] = true; end ButtonForgeSave["VersionMinor"] = 12; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.12", .5, 1, 0, 1); end --v0.9.13 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 13) then for i = 1, #ButtonForgeSave.Bars do ButtonForgeSave.Bars[i]["Enabled"] = true; ButtonForgeSave.Bars[i]["ButtonGap"] = 6; end ButtonForgeSave["VersionMinor"] = 13; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.13", .5, 1, 0, 1); end --v0.9.17 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 17) then for i = 1, #ButtonForgeSave.Bars do ButtonForgeSave.Bars[i]["GUI"] = true; ButtonForgeSave.Bars[i]["Alpha"] = 1; end ButtonForgeSave["VersionMinor"] = 17; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.17", .5, 1, 0, 1); end --v0.9.22 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 22) then for i = 1, #ButtonForgeSave.Bars do if (ButtonForgeSave.Bars[i]["VDriver"] == "[bonusbar:5] show; hide") then ButtonForgeSave.Bars[i]["VDriver"] = "[overridebar][vehicleui] show; hide"; end end ButtonForgeSave["VersionMinor"] = 22; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.22", .5, 1, 0, 1); end --v0.9.25 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 25) then ButtonForgeSave["VersionMinor"] = 25; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.25", .5, 1, 0, 1); end -- v0.9.34 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 34) then for i = 1, #ButtonForgeSave.Bars do Util.UpdateMounts602(ButtonForgeSave.Bars[i].Buttons); end ButtonForgeSave["VersionMinor"] = 34; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.34", .5, 1, 0, 1); end -- v0.9.36 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 36) then if (ButtonForgeSave.UndoProfileBars ~= nil) then for i = 1, #ButtonForgeSave.UndoProfileBars do Util.UpdateMounts602(ButtonForgeSave.UndoProfileBars[i].Buttons); end end ButtonForgeSave["VersionMinor"] = 36; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.36", .5, 1, 0, 1); end -- v0.9.41 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 41) then for i = 1, #ButtonForgeSave.Bars do Util.UpdateMounts700(ButtonForgeSave.Bars[i].Buttons); end if (ButtonForgeSave.UndoProfileBars ~= nil) then for i = 1, #ButtonForgeSave.UndoProfileBars do Util.UpdateMounts700(ButtonForgeSave.UndoProfileBars[i].Buttons); end end ButtonForgeSave["VersionMinor"] = 41; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.41", .5, 1, 0, 1); end -- v0.9.42 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 42) then for i = 1, #ButtonForgeSave.Bars do ButtonForgeSave.Bars[i].HSpec3 = false; ButtonForgeSave.Bars[i].HSpec4 = false; end if (ButtonForgeSave.UndoProfileBars ~= nil) then for i = 1, #ButtonForgeSave.UndoProfileBars do ButtonForgeSave.UndoProfileBars[i].HSpec3 = false; ButtonForgeSave.UndoProfileBars[i].HSpec4 = false; end end ButtonForgeSave["VersionMinor"] = 42; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.42", .5, 1, 0, 1); end -- v0.9.44 update if (ButtonForgeSave["Version"] == 0.9 and ButtonForgeSave["VersionMinor"] < 44) then for i = 1, #ButtonForgeSave.Bars do Util.RemoveCancelPossession700(ButtonForgeSave.Bars[i].Buttons); end if (ButtonForgeSave.UndoProfileBars ~= nil) then for i = 1, #ButtonForgeSave.UndoProfileBars do Util.RemoveCancelPossession700(ButtonForgeSave.UndoProfileBars[i].Buttons); end end ButtonForgeSave["VersionMinor"] = 44; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v0.9.44", .5, 1, 0, 1); end --Bring v up to the latest version if (ButtonForgeSave["Version"] < Const.Version) then ButtonForgeSave["Version"] = Const.Version; ButtonForgeSave["VersionMinor"] = Const.VersionMinor; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v"..Const.Version.."."..Const.VersionMinor, .5, 1, 0, 1); elseif (ButtonForgeSave["Version"] == Const.Version and ButtonForgeSave["VersionMinor"] < Const.VersionMinor) then ButtonForgeSave["VersionMinor"] = Const.VersionMinor; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UpgradedChatMsg").."v"..Const.Version.."."..Const.VersionMinor, .5, 1, 0, 1); end -----This section updates the global button forge data (introduced at 0.9.16) if (not ButtonForgeGlobalSettings) or (ButtonForgeGlobalSettings == nil) or (ButtonForgeGlobalSettings["Version"] == nil) then ButtonForgeGlobalSettings = {}; ButtonForgeGlobalSettings["Version"] = 0.9; ButtonForgeGlobalSettings["VersionMinor"] = 16; ButtonForgeGlobalSettings["MacroCheckDelay"] = 3; ButtonForgeGlobalSettings["RemoveMissingMacros"] = true; end --v0.9.30 update (to global settings) if (ButtonForgeGlobalSettings["Version"] == 0.9 and ButtonForgeGlobalSettings["VersionMinor"] < 30) then ButtonForgeGlobalSettings["ForceOffCastOnKeyDown"] = false; ButtonForgeGlobalSettings["VersionMinor"] = 30; end --v0.9.31 update (to global profiles) if (ButtonForgeGlobalSettings["Version"] == 0.9 and ButtonForgeGlobalSettings["VersionMinor"] < 31) then ButtonForgeGlobalProfiles = {}; ButtonForgeGlobalSettings["VersionMinor"] = 31; end --pre v0.9.36 Safety process if (not ButtonForgeGlobalProfiles) or (ButtonForgeGlobalProfiles == nil) then ButtonForgeGlobalProfiles = {}; end --v0.9.36 update if (ButtonForgeGlobalSettings["Version"] == 0.9 and ButtonForgeGlobalSettings["VersionMinor"] < 36) then for k,v in pairs(ButtonForgeGlobalProfiles) do for i = 1, #v.Bars do Util.UpdateMounts602(v.Bars[i].Buttons); end end ButtonForgeGlobalSettings["VersionMinor"] = 36; end --v0.9.38 update if (ButtonForgeGlobalBackup == nil) then ButtonForgeGlobalBackup = {}; end -- v0.9.41 if (ButtonForgeGlobalSettings["Version"] == 0.9 and ButtonForgeGlobalSettings["VersionMinor"] < 41) then for k, v in pairs(ButtonForgeGlobalProfiles) do for i = 1, #v.Bars do Util.UpdateMounts700(v.Bars[i].Buttons); end end ButtonForgeGlobalSettings["VersionMinor"] = 41; end -- v0.9.42 if (ButtonForgeGlobalSettings["Version"] == 0.9 and ButtonForgeGlobalSettings["VersionMinor"] < 42) then for k, v in pairs(ButtonForgeGlobalProfiles) do for i = 1, #v.Bars do v.Bars[i].HSpec3 = false; v.Bars[i].HSpec4 = false; end end ButtonForgeGlobalSettings["VersionMinor"] = 42; end -- v0.9.44 if (ButtonForgeGlobalSettings["Version"] == 0.9 and ButtonForgeGlobalSettings["VersionMinor"] < 44) then ButtonForgeGlobalSettings["UseCollectionsFavoriteMountButton"] = not AreDangerousScriptsAllowed(); ButtonForgeGlobalSettings["VersionMinor"] = 44; for k, v in pairs(ButtonForgeGlobalProfiles) do for i = 1, #v.Bars do Util.RemoveCancelPossession700(v.Bars[i].Buttons); end end end --Bring the global settings up to the latest version if (ButtonForgeGlobalSettings["Version"] < Const.Version) then ButtonForgeGlobalSettings["Version"] = Const.Version; ButtonForgeGlobalSettings["VersionMinor"] = Const.VersionMinor; elseif (ButtonForgeGlobalSettings["Version"] == Const.Version and ButtonForgeGlobalSettings["VersionMinor"] < Const.VersionMinor) then ButtonForgeGlobalSettings["VersionMinor"] = Const.VersionMinor; end end function Util.UpdateMounts602(Buttons) Util.UpdateMounts700(Buttons) for j = 1, #Buttons do if (Buttons[j]["Mode"] == "companion") then -- Either fix the mapping, or clear the mount local MountID = Util.GetMountIDFromName(Buttons[j]["CompanionName"]); Buttons[j]["Mode"] = nil; Buttons[j]["CompanionId"] = nil; Buttons[j]["CompanionType"] = nil; Buttons[j]["CompanionIndex"] = nil; Buttons[j]["CompanionName"] = nil; Buttons[j]["CompanionSpellName"] = nil; if (Index) then Buttons[j]["Mode"] = "mount"; Buttons[j]["MountID"] = MountID; end end end end function Util.UpdateMounts700(Buttons) for j = 1, #Buttons do if (Buttons[j]["Mode"] == "mount") then local MountIndex = Buttons[j]["MountIndex"]; local MountName = Buttons[j]["MountName"]; Buttons[j]["MountID"] = nil; Buttons[j]["MountSpellID"] = nil; Buttons[j]["MountName"] = nil; Buttons[j]["MountIndex"] = nil; if (MountIndex == 0) then Buttons[j]["MountID"] = Const.SUMMON_RANDOM_FAVORITE_MOUNT_ID; else local MountID = Util.GetMountIDFromName(MountName); if (MountID) then Buttons[j]["MountID"] = MountID; else Buttons[j]["Mode"] = nil; end end end end end function Util.RemoveCancelPossession700(Buttons) for j = 1, #Buttons do if (Buttons[j]["Mode"] == "customaction" and Buttons[j]["CustomActionName"] == "possesscancel") then Buttons[j]["Mode"] = nil; Buttons[j]["CustomActionName"] = nil; end end end --[[ Load the bars and buttons from the saved addon values --]] function Util.Load() local CharRealm = UnitName("player"); CharRealm = CharRealm.."-"..GetRealmName(); ButtonForgeGlobalBackup[CharRealm] = ButtonForgeSave; Util.ForceOffCastOnKeyDown = ButtonForgeGlobalSettings["ForceOffCastOnKeyDown"]; if (not Util.ForceOffCastOnKeyDown) then hooksecurefunc("SetCVar", MiscFrame.SetCVarCalled); end if (ButtonForgeSave.ConfigureMode) then ConfigureLayer:Show(); end if (ButtonForgeSave.AdvancedMode) then UILib.ToggleAdvancedTools(); end --if (Util.LBFMasterGroup and ButtonForgeSave["SkinID"]) then -- Util.LBFMasterGroup:Skin(ButtonForgeSave.SkinID, ButtonForgeSave.Gloss, ButtonForgeSave.Backdrop, ButtonForgeSave.Colors); --end for i = 1, #ButtonForgeSave.Bars do Util.NewBar(0, 0, ButtonForgeSave.Bars[i]); end UILib.ToggleRightClickSelfCast(ButtonForgeSave["RightClickSelfCast"] or false); Util.Loaded = true; Util.StartMacroCheckDelay(); Util.RefreshOnUpdateFunction(); collectgarbage("collect"); Util.CallbackEvent("INITIALISED"); end -- Grabbed from the Lua wiki function Util.deepcopy(orig) local orig_type = type(orig) local copy if orig_type == 'table' then copy = {} for orig_key, orig_value in next, orig, nil do copy[Util.deepcopy(orig_key)] = Util.deepcopy(orig_value) end -- setmetatable(copy, Util.deepcopy(getmetatable(orig))) -- I don't need this specifically for ButtonForge else -- number, string, boolean, etc copy = orig end return copy end --[[ Load the bars and buttons from a profile --]] function Util.LoadProfile(ProfileName) if (InCombatLockdown()) then Util.SlashShowMessageByLine(Util.GetLocaleString("ActionFailedCombatLockdown")); return; end if (ButtonForgeGlobalProfiles[string.upper(ProfileName)] == nil) then Util.SlashShowMessageByLine(Util.GetLocaleString("ProfileNotFound")); return; end -- 1. Store the current configuration as the revert configuration for this character, in case the players wishes to go back ButtonForgeSave.UndoProfileBars = Util.deepcopy(ButtonForgeSave.Bars); -- 2. Deallocate the current UI for i = #Util.ActiveBars, 1, -1 do Util.DeallocateBar(Util.ActiveBars[i]); end -- 3. Apply the profile as the new bars/buttons for this character ButtonForgeSave.Bars = Util.deepcopy(ButtonForgeGlobalProfiles[string.upper(ProfileName)].Bars); -- 4. Attach the Bars back to the UI for i = 1, #ButtonForgeSave.Bars do Util.NewBar(0, 0, ButtonForgeSave.Bars[i]); end Util.RefreshCompanions(); Util.RefreshMacros(); Util.RefreshEquipmentSets(); Util.RefreshSpells(); Util.RefreshGridStatus(true); Util.RefreshBarStrata(true); Util.RefreshBarGUIStatus(); DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("LoadedProfile"), .5, 1, 0, 1); end --[[ Load the bars and buttons from a profile --]] function Util.LoadProfileTemplate(ProfileName) if (InCombatLockdown()) then Util.SlashShowMessageByLine(Util.GetLocaleString("ActionFailedCombatLockdown")); return; end if (ButtonForgeGlobalProfiles[string.upper(ProfileName)] == nil) then Util.SlashShowMessageByLine(Util.GetLocaleString("ProfileNotFound")); return; end -- 1. Store the current configuration as the revert configuration for this character, in case the players wishes to go back ButtonForgeSave.UndoProfileBars = Util.deepcopy(ButtonForgeSave.Bars); -- 2. Deallocate the current UI for i = #Util.ActiveBars, 1, -1 do Util.DeallocateBar(Util.ActiveBars[i]); end -- 3. Apply the profile as the new bars/buttons for this character ButtonForgeSave.Bars = Util.deepcopy(ButtonForgeGlobalProfiles[string.upper(ProfileName)].Bars); -- 4. Attach the Bars back to the UI for i = 1, #ButtonForgeSave.Bars do Util.NewBar(0, 0, ButtonForgeSave.Bars[i]); end -- 5. Blank all the buttons - this is programatically the easiest way to handle it for i = 1, #Util.ActiveButtons do if (Util.ActiveButtons[i].Mode ~= "bonusaction") then Util.ActiveButtons[i]:SetCommandFromTriplet(); end end Util.RefreshGridStatus(true); Util.RefreshBarStrata(true); Util.RefreshBarGUIStatus(); DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("LoadedProfileTemplate"), .5, 1, 0, 1); end --[[ Save the bars and buttons to a profile, NB ProfileName is case insenstive for the purposes of saving loading deleting --]] function Util.SaveProfile(ProfileName) local NewProfile = {}; NewProfile.Name = ProfileName; -- To capture the case sensitive version -- Record this extra info in case it is useful later, probably wont be but could help keep profiles organised with a future update?? NewProfile.Icon = "INV_Misc_QuestionMark"; --"Interface/Icons/".. for when/if a gui gets setup it will be good to assign an icon NewProfile.Date = date("%c"); NewProfile.Realm = GetRealmName(); NewProfile.Char = UnitName("player"); NewProfile.Class = UnitClass("player"); NewProfile.Bars = Util.deepcopy(ButtonForgeSave.Bars) ButtonForgeGlobalProfiles[string.upper(ProfileName)] = NewProfile; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("SavedProfile"), .5, 1, 0, 1); end --[[ -- Lazy, I should've factored this with the save profile function --]] function Util.UndoProfile() if (InCombatLockdown()) then Util.SlashShowMessageByLine(Util.GetLocaleString("ActionFailedCombatLockdown")); return; end if (ButtonForgeSave.UndoProfileBars == nil) then return; end for i = #Util.ActiveBars, 1, -1 do Util.DeallocateBar(Util.ActiveBars[i]); end ButtonForgeSave.Bars = Util.deepcopy(ButtonForgeSave.UndoProfileBars); for i = 1, #ButtonForgeSave.Bars do Util.NewBar(0, 0, ButtonForgeSave.Bars[i]); end Util.RefreshCompanions(); Util.RefreshMacros(); Util.RefreshEquipmentSets(); Util.RefreshSpells(); Util.RefreshGridStatus(true); Util.RefreshBarStrata(true); Util.RefreshBarGUIStatus(); DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("UndoneProfile"), .5, 1, 0, 1); end function Util.DeleteProfile(ProfileName) ProfileName = string.upper(ProfileName); if (ButtonForgeGlobalProfiles[ProfileName]) then ButtonForgeGlobalProfiles[ProfileName] = nil; DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("DeletedProfile"), .5, 1, 0, 1); end -- Some day I may add comfirmations, at least perhaps for the /slash commands... perhaps also a session log?? But when?! end --[[ Save Button Facade settings if present --]] function Util:ButtonFacadeCallback(SkinID, Gloss, Backdrop, Group, Button, Colors) -- If no group is specified, save the data as the root add-on skin. -- This will allow the ButtonFacade GUI to display it correctly. return; -- This may longer be necessary --[[if not Group then ButtonForgeSave["SkinID"] = SkinID; ButtonForgeSave["Gloss"] = Gloss; ButtonForgeSave["Backdrop"] = Backdrop; ButtonForgeSave["Colors"] = Colors; else --not presently implemented by Button Forge -- ButtonForgeSave[Group]["SkinID"] = SkinID; ButtonForgeSave[Group]["Gloss"] = Gloss; ButtonForgeSave[Group]["Backdrop"] = Backdrop; ButtonForgeSave[Group]["Colors"] = Colors; end]] end --[[ To allow auto-alignment to also consider the blizzard multibars, this function will create wrappers to go on these bars which will also be considered when dragging a Button Forge bar... Since this is possibly a compatibility point, it can be hard disabled by a const param, it will also disable in the presence of Bartender which I know it doesn't function properly with --]] function Util.CreateBlizzardBarWrappers() if (IsAddOnLoaded("Bartender4") or Const.DisableAutoAlignAgainstDefaultBars) then return; end Util.BlizBarWrappers[1] = CreateFrame("FRAME", nil, UIParent); Util.BlizBarWrappers[1]:SetPoint("TOPLEFT", MultiBarBottomLeftButton1, "TOPLEFT", -Const.I, Const.I + 1); Util.BlizBarWrappers[1]:SetPoint("BOTTOMRIGHT", MultiBarBottomLeftButton12, "BOTTOMRIGHT", Const.I, -Const.I + 1); Util.BlizBarWrappers[2] = CreateFrame("FRAME", nil, UIParent); Util.BlizBarWrappers[2]:SetPoint("TOPLEFT", MultiBarBottomRightButton1, "TOPLEFT", -Const.I, Const.I + 1); Util.BlizBarWrappers[2]:SetPoint("BOTTOMRIGHT", MultiBarBottomRightButton12, "BOTTOMRIGHT", Const.I, -Const.I + 1); Util.BlizBarWrappers[3] = CreateFrame("FRAME", nil, UIParent); Util.BlizBarWrappers[3]:SetPoint("TOPLEFT", MultiBarRightButton1, "TOPLEFT", -Const.I, Const.I + 1); Util.BlizBarWrappers[3]:SetPoint("BOTTOMRIGHT", MultiBarRightButton12, "BOTTOMRIGHT", Const.I, -Const.I + 1); Util.BlizBarWrappers[4] = CreateFrame("FRAME", nil, UIParent); Util.BlizBarWrappers[4]:SetPoint("TOPLEFT", MultiBarLeftButton1, "TOPLEFT", -Const.I, Const.I + 1); Util.BlizBarWrappers[4]:SetPoint("BOTTOMRIGHT", MultiBarLeftButton12, "BOTTOMRIGHT", Const.I, -Const.I + 1); end function Util.UpdateBlizzardEnabledBarsMap() Util.BlizEnabledBars = {GetActionBarToggles()}; end --[[ Button Allocation Functions --]] function Util.NewButton(Parent, ButtonSave, ButtonLocked, TooltipOn, MacroText, KeyBindText) if (InCombatLockdown()) then return; end local NewButton; if (#(Util.InactiveButtons) > 0) then NewButton = table.remove(Util.InactiveButtons); NewButton:Configure(Parent, ButtonSave, ButtonLocked, TooltipOn, MacroText, KeyBindText); else NewButton = Button.New(Parent, ButtonSave, ButtonLocked, TooltipOn, MacroText, KeyBindText); Util.ButtonWidgetMap[NewButton.Widget] = NewButton; end table.insert(Util.ActiveButtons, NewButton); Util.CallbackEvent("BUTTON_ALLOCATED", NewButton.Widget:GetName()); return NewButton; end function Util.DeallocateButton(Value) if (InCombatLockdown()) then return; end Value:Deallocate(); table.remove(Util.ActiveButtons, Util.FindInTable(Util.ActiveButtons, Value)); table.insert(Util.InactiveButtons, Value); Util.CallbackEvent("BUTTON_DEALLOCATED", Value.Widget:GetName()); end function Util.DetachButton(Value) if (InCombatLockdown()) then return; end Value:Detach(); table.remove(Util.ActiveButtons, Util.FindInTable(Util.ActiveButtons, Value)); table.insert(Util.InactiveButtons, Value); Util.CallbackEvent("BUTTON_DEALLOCATED", Value.Widget:GetName()); end --[[ Bar Allocation Functions --]] function Util.NewBarSave() local Save = {}; Save["Left"] = 0; Save["Top"] = 0; Save["Scale"] = 1; Save["Order"] = #Util.ActiveBars; Save["Label"] = nil; Save["Rows"] = Const.DefaultRows; Save["Cols"] = Const.DefaultCols; Save["VDriver"] = nil; Save["HVehicle"] = true; Save["HSpec1"] = false; Save["HSpec2"] = false; Save["HSpec3"] = false; Save["HSpec4"] = false; Save["HBonusBar"] = true; Save["GridAlwaysOn"] = true; Save["ButtonsLocked"] = false; Save["TooltipsOn"] = true; Save["MacroText"] = true; Save["KeyBindText"] = true; Save["ButtonGap"] = 6; Save["Enabled"] = true; Save["BonusBar"] = false; Save["GUI"] = true; Save["Alpha"] = 1; Save["Buttons"] = {}; return Save; end function Util.NewBar(Left, Top, BarSave) if (InCombatLockdown()) then return; end local NewBar; if (not BarSave) then BarSave = Util.NewBarSave(); BarSave["Left"] = Left; BarSave["Top"] = Top; if (ButtonForgeSave.Bars == nil) then ButtonForgeSave.Bars = {}; end; table.insert(ButtonForgeSave.Bars, BarSave); PlaySound(177, "Master"); end if (#(Util.InactiveBars) > 0) then NewBar = table.remove(Util.InactiveBars); NewBar:Configure(BarSave); else NewBar = Bar.New(BarSave); end table.insert(Util.ActiveBars, NewBar); if (NewBar.Cols * NewBar.Rows == 0) then --Failed to allocate buttons, get rid of the bar NewBar:Deallocate(); return nil; else Util.RefreshTab(NewBar.ControlFrame:GetLeft(), NewBar.ControlFrame:GetTop()); return NewBar; end end function Util.NewBonusBar(Left, Top) if (InCombatLockdown()) then return; end local BarSave = Util.NewBarSave(); BarSave["BonusBar"] = true; BarSave["Left"] = Left; BarSave["Top"] = Top; BarSave["Rows"] = 1; BarSave["Cols"] = 13; BarSave["HBonusBar"] = false; BarSave["HVehicle"] = false; BarSave["VDriver"] = "[overridebar][vehicleui] show; hide"; BarSave["ButtonsLocked"] = true; BarSave["GridAlwaysOn"] = false; if (ButtonForgeSave.Bars == nil) then ButtonForgeSave.Bars = {}; end; table.insert(ButtonForgeSave.Bars, BarSave); PlaySound(177, "Master"); return Util.NewBar(Left, Top, BarSave); end function Util.DeallocateBar(Value) if (InCombatLockdown()) then return; end Value:Deallocate(); --Note that deallocating a bar will call a function that changes the bars state (primarily it removes all buttons, and changes it's order... both of which change the save state data) table.remove(Util.ActiveBars, Util.FindInTable(Util.ActiveBars, Value)); table.remove(ButtonForgeSave.Bars, Util.FindInTable(ButtonForgeSave.Bars, Value.BarSave)); table.insert(Util.InactiveBars, Value); local Left, Top = Value.ControlFrame:GetLeft(), Value.ControlFrame:GetTop(); Util.RefreshTab(Left, Top); PlaySound(6523, "Master"); end function Util.DetachBar(Value) if (InCombatLockdown()) then return; end Value:Detach(); table.remove(Util.ActiveBars, Util.FindInTable(Util.ActiveBars, Value)); table.insert(Util.InactiveBars, Value); local Left, Top = Value.ControlFrame:GetLeft(), Value.ControlFrame:GetTop(); Util.RefreshTab(Left, Top); end function Util.GetButtonFrameName(Label) Label = Label or ""; local Name = Const.BarNaming.."Bar_"..(Label or ""); if (not _G[Name.."_ButtonFrame"] and Label ~= "") then return Name.."_ButtonFrame"; end while true do if (not _G[Name..Const.BarSeq.."_ButtonFrame"]) then return Name..Const.BarSeq.."_ButtonFrame"; end Const.BarSeq = Const.BarSeq + 1; end end --[[ Bar Management Functions --]] function Util.ReorderBar(Bar, NewPosition) local CurrentPosition = Bar.BarSave["Order"]; local Order; if (CurrentPosition > NewPosition) then for i = 1, #Util.ActiveBars do Order = Util.ActiveBars[i].BarSave["Order"]; if (Order < CurrentPosition and Order >= NewPosition) then Util.ActiveBars[i]:SetOrder(Order + 1); end end elseif (CurrentPosition < NewPosition) then for i = 1, #Util.ActiveBars do Order = Util.ActiveBars[i].BarSave["Order"]; if (Order > CurrentPosition and Order <= NewPosition) then Util.ActiveBars[i]:SetOrder(Order - 1); end end end Bar:SetOrder(NewPosition); end function Util.DockCoords(Left, Top, ExcludeBar) local CLeft, CTop, CDist, CBar = 0, 0, 100000000, nil; --This is an arbitrary number that will be big enough here local Dist, X, Y; local Bars = Util.ActiveBars; for i = 1, #Bars do X = Bars[i].ControlFrame:GetLeft();--BarSave["Left"]; Y = Bars[i].ControlFrame:GetTop();--BarSave["Top"]; Dist = ((Left - X) ^ 2)+ ((Top - Y) ^ 2); if (Dist < CDist and Bars[i] ~= ExcludeBar) then CLeft = X; CTop = Y; CDist = Dist; CBar = Bars[i]; end end return CLeft, CTop, CDist, CBar; end function Util.FindClosestPoint(Coord, Points, Offsets, ExcludeBar) local MatchedBar, MatchedPoint, Shift, MatchedCoord = nil, 0, 0, 0; local Calc, CalcCoord, MinCalc = 0, 0, 10000000; local Bars = Util.ActiveBars; for i = 1, #Bars do if (Bars[i] ~= ExcludeBar) then for j = 1, #Points do CalcCoord = Bars[i].ControlFrame[Points[j]](Bars[i].ControlFrame) + Offsets[j]; --basically translates down to things like Bars[i]:GetLeft() + Offset[j]; if Points[j] is "GetLeft" Calc = CalcCoord - Coord; Calc = Calc * Calc; if (Calc < MinCalc - 0.1) then MinCalc = Calc; MatchedCoord = CalcCoord; Shift = CalcCoord - Coord; MatchedBar = Bars[i].ControlFrame; MatchedPoint = j; end end end end Bars = Util.BlizBarWrappers; local EnabledBars = Util.BlizEnabledBars; for i = 1, #Bars do if (EnabledBars[i]) then for j = 1, #Points do CalcCoord = Bars[i][Points[j]](Bars[i]); if (not CalcCoord) then Util.BlizEnabledBars[i] = false; break; else CalcCoord = CalcCoord + Offsets[j]; --basically translates down to things like Bars[i]:GetLeft() + Offset[j]; if Points[j] is "GetLeft" Calc = CalcCoord - Coord; Calc = Calc * Calc; if (Calc < MinCalc - 0.1) then MinCalc = Calc; MatchedCoord = CalcCoord; Shift = CalcCoord - Coord; MatchedBar = Bars[i]; MatchedPoint = j; end end end end end return MatchedBar, MatchedPoint, MinCalc, Shift, MatchedCoord; end --[[ While the Configure overlay is shown make sure that all bars are visible (unless in combat), this function also cleansup when the overlay is hidden again --]] function Util.VDriverOverride() if (InCombatLockdown()) then return; end local Bars = Util.ActiveBars; if (ConfigureLayer:IsShown() or DestroyBarOverlay:IsShown()) then for i = 1, #Bars do Bars[i]:SetTempShowVD(); end else for i = 1, #Bars do Bars[i]:ClearTempShowVD(); end end end --[[ Since the Spellbook has an annoying tendancy to cover bars this function will raise (or relower) the buttons to so they can be clicked Note: It will do this for everything but items - since that could quickly get annoying when moving items around (there isn't really a perfect solution, so best to not go overboard) Also it doesn't do a combat lockdown check, it is expected that this has been done up the chain (the bars will auto drop to the low strata if combat begins!) --]] function Util.RefreshBarStrata(ForceUpdate) local LowStrata = Util.InCombat or DestroyBarOverlay:IsShown() or (GetCursorInfo() == "item" and not IsShiftKeyDown()) or not Util.CursorAction; if (LowStrata ~= Util.LowStrata or ForceUpdate) then Util.LowStrata = LowStrata; local Bars = Util.ActiveBars; if (LowStrata) then for i = 1, #Bars do Bars[i].ButtonFrame:SetFrameStrata("LOW"); Bars[i]:SetOrder(); --without a param this will cause a refresh (just in case moving the strata causes the level to change) end else for i = 1, #Bars do Bars[i].ButtonFrame:SetFrameStrata("DIALOG"); Bars[i]:SetOrder(); --without a param this will cause a refresh (just in case moving the strata causes the level to change) end end end end function Util.RefreshGridStatus(ForceUpdate) local Hide = Util.InCombat or not (Util.CursorAction or ConfigureLayer:IsShown() or DestroyBarOverlay:IsShown()); if (Hide ~= Util.GridHidden or ForceUpdate) then Util.GridHidden = Hide; local Bars = Util.ActiveBars; if (Hide) then for i = 1, #Bars do if (not Bars[i].BarSave["GridAlwaysOn"]) then Bars[i]:GridHide(); end end else for i = 1, #Bars do if (not Bars[i].BarSave["GridAlwaysOn"]) then Bars[i]:GridShow(); end end end end end function Util.RefreshBarGUIStatus() local BarGUIForceOn = (not Util.InCombat) and (ConfigureLayer:IsShown() or DestroyBarOverlay:IsShown() or (IsShiftKeyDown() and Util.CursorAction)); local Bars = Util.ActiveBars; if (BarGUIForceOn) then for i = 1, #Bars do Bars[i]:GUIOn(); end else for i = 1, #Bars do if (not Bars[i].BarSave["GUI"]) then Bars[i]:GUIOff(); end end end end function Util.SetControlFrameAlphas(Alpha) local Bars = Util.ActiveBars; for i = 1, #Bars do Bars[i].ControlFrame:SetAlpha(Alpha); end end function Util.RefreshTab(Left, Top) local Count = 0; local Bar; local StackedBar; local Label; for i = 1, #Util.ActiveBars do Bar = Util.ActiveBars[i]; if (math.abs(Bar.ControlFrame:GetLeft() - Left) < 0.01 and math.abs(Bar.ControlFrame:GetTop() - Top) < 0.01) then Count = Count + 1; StackedBar = Bar; end end if (Count == 0) then return; end if (Count == 1) then --Set it's label back in and dealloc the tabframe StackedBar.Tabbed = false; Label = StackedBar.LabelFrame; Label:ClearAllPoints(); Label:SetPoint("TOPLEFT", StackedBar.TopIconsFrame, "TOPLEFT", Const.BarInset, -Const.BarEdge); --Const.MiniIconSize + Const.MiniIconGap +Const.BarEdge, -Const.BarEdge); Label:SetBackdropColor(0, 0, 0, 1); Label:SetAlpha(1); Label:EnableMouse(false); Label:SetScript("OnMouseDown", nil); Label:SetScript("OnEnter", nil); Label:SetScript("OnLeave", nil); Util.DeallocateTab(Left, Top); StackedBar:ReflowUI(); return; end local Offset = 0; local HighestBar; local TabFrame = Util.GetTabFrame(Left, Top); for i = 1, #Util.ActiveBars do Bar = Util.ActiveBars[i]; if (math.abs(Bar.ControlFrame:GetLeft() - Left) < 0.01 and math.abs(Bar.ControlFrame:GetTop() - Top) < 0.01) then if (not HighestBar) then --anchor tabframe HighestBar = Bar; TabFrame:ClearAllPoints(); TabFrame:SetPoint("TOPLEFT", Bar.ControlFrame, "TOPLEFT", 0, Const.MiniIconSize); elseif (HighestBar.BarSave["Order"] < Bar.BarSave["Order"]) then HighestBar = Bar; end Bar.Tabbed = true; Label = Bar.LabelFrame; Label:ClearAllPoints(); Label:SetPoint("TOPLEFT", TabFrame, "TOPLEFT", Offset, 0); Label:SetBackdropColor(0, 0, 0, 1); Label:SetAlpha(.5); Label:EnableMouse(true); Label:SetScript("OnMouseDown", Bar.SendToFront); Label:SetScript("OnEnter", Bar.LabelOnEnter); Label:SetScript("OnLeave", Bar.LabelOnLeave); if (Label:GetWidth() > 4.5) then Offset = Offset + Label:GetWidth(); end Bar:ReflowUI(); end end TabFrame:SetSize(Offset, Const.MiniIconSize); Label = HighestBar.LabelFrame; Label:SetAlpha(1); --Label:SetScript("OnMouseDown", nil); Label:SetScript("OnEnter", nil); Label:SetScript("OnLeave", nil); end function Util.DeallocateTab(Left, Top) if (Util.ActiveTabs[Left.." "..Top]) then table.insert(Util.InactiveTabs, Util.ActiveTabs[Left.." "..Top]); Util.ActiveTabs[Left.." "..Top] = nil; end end function Util.GetTabFrame(Left, Top) if (not Util.ActiveTabs[Left.." "..Top]) then if (#Util.InactiveTabs > 0) then Util.ActiveTabs[Left.." "..Top] = table.remove(Util.InactiveTabs); else Util.ActiveTabs[Left.." "..Top] = CreateFrame("FRAME", nil, ConfigureLayer); Util.ActiveTabs[Left.." "..Top]:SetSize(1, 1); Util.ActiveTabs[Left.." "..Top]:SetClampedToScreen(true); end end return Util.ActiveTabs[Left.." "..Top]; end function Util.BarHasButton(Bar, Command, Data, Subvalue) local BCommand, BData, BSubvalue for i = 1, #Bar.Buttons do BCommand, BData, BSubvalue = Bar.Buttons[i]:GetCursor(); if (Command == BCommand and Data == BData and Subvalue == BSubvalue) then return true; end end return false; end --[[ Helper functions --]] function Util.FindInTable(Table, Value, Start) for i = Start or 1, #Table do if (Table[i] == Value) then return i; end end return nil; end function Util.GetLocaleString(Value) return Locale[Value]; end function Util.GetLocaleEnabledDisabled(Value) if (Value) then return Locale["Enabled"]; else return Locale["Disabled"]; end end function Util.CastBool(Value) return Locale.BoolTable[strlower(Value or '')]; end function Util.ProcessSlashCommandParams(Command, Params) Params = Params or ""; if (Const.SlashCommands[Command].params == "bool") then local Bool = Util.CastBool(Params, "^%s*(%w*)%s*$"); if (Bool == nil) then DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("SlashParamsInvalid")..Command.." "..Params, .5, 1, 0, 1); return; end return {Bool}; else local Values = {string.match(Params, Const.SlashCommands[Command].params)}; if (Values[1] == nil) then DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("SlashParamsInvalid")..Command.." "..Params, .5, 1, 0, 1); return; end return Values; end end --[[Unused function for allowing a more... ]]-- local SlashRemainingMessage = ''; function Util.SlashShowRemainingMessage() local Display, Remainder = string.match(SlashRemainingMessage, "^("..strrep(".-\n", Const.SlashNumLines-1)..".-)\n(.-)$"); if (Display) then SlashRemainingMessage = Remainder; else Display = SlashRemainingMessage; SlashRemainingMessage = ''; end DEFAULT_CHAT_FRAME:AddMessage(' '..Display, .5, 1, 0, 1); end function Util.SlashShowMessageByLine(Message) for Line in string.gmatch(Message, '([^\n]*)') do DEFAULT_CHAT_FRAME:AddMessage(Line, .5, 1, 0, 1); end end SLASH_BUTTONFORGE1 = Util.GetLocaleString("SlashButtonForge1"); -- = "/buttonforge"; --these two identifiers probably shouldn't change, but if need be they can be?! SLASH_BUTTONFORGE2 = Util.GetLocaleString("SlashButtonForge2"); -- = "/bufo"; SlashCmdList["BUTTONFORGE"] = function(msg, editbox) local FirstCommand; local PreparedCommands = {}; local Command, Params; local Count = 0; Params = ''; for Token, Space in string.gmatch(msg, '([^%s]+)([%s]*)') do if (Const.SlashCommands[strlower(Token)]) then if (Command) then if (FirstCommand == nil) then FirstCommand = Command; end; Count = Count + 1; --PreparedCommands["Count"] = Count; PreparedCommands[Command] = Util.ProcessSlashCommandParams(Command, Params); if (PreparedCommands[Command] == nil) then return; end end Command = strlower(Token); Params = ''; elseif (string.match(Token, '^-') == '-') then DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("SlashCommandNotRecognised")..Token, .5, 1, 0, 1); return; else Params = Params..Token..Space; end end if (Command) then Count = Count + 1; --PreparedCommands["Count"] = Count; PreparedCommands[Command] = Util.ProcessSlashCommandParams(Command, Params); if (PreparedCommands[Command] == nil) then return; end local Bars = Util.ActiveBars; local Commands = PreparedCommands; -- Check the constraint rules -- 1. Only 1 group is allowed -- 2. A rules required's must be present -- 3. A rules exclusions must not be present local Group; for k, v in pairs(Commands) do FirstCommand = FirstCommand or k; if (Group ~= nil and Group ~= Const.SlashCommands[k].group) then -- must be the same group DEFAULT_CHAT_FRAME:AddMessage(string.gsub(string.gsub(Util.GetLocaleString("SlashCommandIncompatible"), "", FirstCommand), "", k), .5, 1, 0, 1); return; end Group = Const.SlashCommands[k].group; local Requires = Const.SlashCommands[k].requires; if (Requires) then local RequiresValid = false; local RequiresInfo = {}; -- make sure we have at least one of the requirements for k1, v1 in pairs(Requires) do table.insert(RequiresInfo,v1); if (Commands[v1] ~= nil) then RequiresValid = true; end end if (RequiresValid == false) then -- Missing a required command DEFAULT_CHAT_FRAME:AddMessage(string.gsub(string.gsub(Util.GetLocaleString("SlashCommandRequired"), "", k), "", table.concat(RequiresInfo, " or ")), .5, 1, 0, 1); return; end end local Incompat = Const.SlashCommands[k].incompat; if (Incompat) then for k1, v1 in pairs(Incompat) do if (Commands[v1]) then -- Incompatible command present DEFAULT_CHAT_FRAME:AddMessage(string.gsub(string.gsub(Util.GetLocaleString("SlashCommandIncompatible"), "", k), "", v1), .5, 1, 0, 1); return; end if (v1 == "ALL" and Count > 1) then -- Only 1 command would be allowed DEFAULT_CHAT_FRAME:AddMessage(string.gsub(Util.GetLocaleString("SlashCommandAlone"), "", k), .5, 1, 0, 1); return; end end end local Validate = Const.SlashCommands[k].validate; if (Validate) then if (not Validate(unpack(v))) then -- Validate function failed DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("SlashParamsInvalid")..k.." "..table.concat(v, " "), .5, 1, 0, 1); return; end end end if (Group == "bar") then if (Commands["-createbar"]) then Util.ApplySlashCommands(Commands); else local BarName; if (Commands["-bar"]) then BarName = Commands["-bar"][1]; elseif (Commands["-destroybar"]) then BarName = Commands["-destroybar"][1]; end local barFound = false; for i = 1, #Bars do if ((not BarName) or strlower(BarName) == strlower(Bars[i].BarSave["Label"])) then Util.ApplySlashCommands(Commands, Bars[i]); barFound = true; end end -- bar name not found, check with Index if ( barFound == false ) then for i = 1, #Bars do if ( tonumber(BarName) == i ) then Util.ApplySlashCommands(Commands, Bars[i]); barFound = true; end end end end else Util.ApplySlashCommands(Commands); end else Util.SlashShowMessageByLine(Util.GetLocaleString("SlashHelpFormatted")); --]] end end function Util.ApplySlashCommands(Commands, Bar) if (Commands["-createbar"]) then Bar = Util.NewBar(0, 0); if (Bar == nil) then DEFAULT_CHAT_FRAME:AddMessage(Util.GetLocaleString("SlashCreateBarFailed"), .5, 1, 0, 1); return end Commands["-rename"] = Commands["-createbar"]; --this could arguably work by having an empty param to createbar but I think it will feel more natural to require a name with this command end if (Commands["-list"]) then local Bars = Util.ActiveBars; for i = 1, #Bars do local label = string.gsub(Util.GetLocaleString("SlashListBarWithLabel"), "