-- local functions local UnitName = UnitName; local UnitIsDead = UnitIsDead; local pairs = pairs; local type = type; local wipe = wipe; local tonumber = tonumber; local table_insert = table.insert; local string_format = string.format; local string_sub = string.sub; local string_gsub = string.gsub; local string_find = string.find; local string_match = string.match; local string_trim = strtrim; local string_len = strlenutf8; -- local variables local _; -- mainline or classic local wowmainline = (WOW_PROJECT_ID == WOW_PROJECT_MAINLINE); local wowclassic = (WOW_PROJECT_ID == WOW_PROJECT_CLASSIC); local wowbcc = (WOW_PROJECT_ID == WOW_PROJECT_BURNING_CRUSADE_CLASSIC); local wowwc = (WOW_PROJECT_ID == WOW_PROJECT_WRATH_CLASSIC); -- libraries Gnosis = LibStub("AceAddon-3.0"):NewAddon("Gnosis", "AceConsole-3.0", "AceEvent-3.0"); Gnosis.gui = LibStub("AceGUI-3.0"); Gnosis.comm = LibStub("AceComm-3.0"); Gnosis.lsm = LibStub("LibSharedMedia-3.0", 1); Gnosis.smw = LibStub("AceGUISharedMediaWidgets-1.0"); Gnosis.range = LibStub("LibRangeCheck-2.0"); Gnosis.dialog = LibStub("LibDialog-1.0"); Gnosis.libs = LibStub("AceSerializer-3.0"); Gnosis.libc = LibStub("LibCompress"); -- classic only libraries if (wowclassic) then Gnosis.libclcno = LibStub("LibClassicCasterino"); Gnosis.libcldur = LibStub("LibClassicDurations"); Gnosis.libcldur:Register("Gnosis"); end -- classic: use LibClassicCasterino casting functions local UnitCastingInfo = UnitCastingInfo; local UnitChannelInfo = UnitChannelInfo; if (wowclassic and Gnosis.libclcno) then UnitCastingInfo = function(unit) return Gnosis.libclcno:UnitCastingInfo(unit); end UnitChannelInfo = function(unit) return Gnosis.libclcno:UnitChannelInfo(unit); end end if (wowbcc) then UnitCastingInfo = function(unit) local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, spellId = UnitCastingInfo(unit) return name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, nil, spellId end end -- LibSharedMedia if(Gnosis.lsm) then -- statusbar textures Gnosis.lsm:Register("statusbar", "Waterline", "Interface\\Addons\\Gnosis\\Textures\\Waterline"); Gnosis.lsm:Register("statusbar", "Gnosis_Plain", "Interface\\Addons\\Gnosis\\Textures\\Gnosis_Plain"); Gnosis.lsm:Register("statusbar", "Gnosis_Gradient", "Interface\\Addons\\Gnosis\\Textures\\Gnosis_Gradient"); -- fonts Gnosis.lsm:Register("font", "Desyrel", "Interface\\Addons\\Gnosis\\Fonts\\DESYREL_.ttf"); Gnosis.lsm:Register("font", "Accidental Presidency", "Interface\\Addons\\Gnosis\\Fonts\\Accidental Presidency\\accid___.ttf"); -- sound files Gnosis.lsm:Register("sound", "Gnosis_Coin", "Interface\\Addons\\Gnosis\\Sounds\\coin_dropped_on_wooden_floor.ogg"); Gnosis.lsm:Register("sound", "Gnosis_CompStart", "Interface\\Addons\\Gnosis\\Sounds\\Computer_Start-Up-Your_Mom-1280862923.ogg"); Gnosis.lsm:Register("sound", "Gnosis_Cuckoo", "Interface\\Addons\\Gnosis\\Sounds\\Cuckoo Clock-SoundBible.com-1776874523.ogg"); Gnosis.lsm:Register("sound", "Gnosis_Electric", "Interface\\Addons\\Gnosis\\Sounds\\Electrical_Sweep-Sweeper-1760111493.ogg"); Gnosis.lsm:Register("sound", "Gnosis_MusicBox", "Interface\\Addons\\Gnosis\\Sounds\\Music_Box-Big_Daddy-1389738694.ogg"); Gnosis.lsm:Register("sound", "Gnosis_Wharf", "Interface\\Addons\\Gnosis\\Sounds\\announcementonawharf.ogg"); end -- generate configuration frames function Gnosis:InitialConfig() self.optFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("Gnosis", self.L["AddonName"]); self.optCBs = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("Gnosis Castbars", Gnosis.L["TabCastbars"], "Gnosis"); LibStub("AceConfigDialog-3.0"):AddToBlizOptions("Gnosis Channeled Spells", Gnosis.L["TabChanneledSpells"], "Gnosis"); LibStub("AceConfigDialog-3.0"):AddToBlizOptions("Gnosis Combattext/Clip test", Gnosis.L["TabCTClipTest"], "Gnosis"); self.optCfgs = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("Gnosis Configurations", Gnosis.L["TabConfig"], "Gnosis"); end function Gnosis:En(status) if (status) then -- enable addon self.bGnosisEnabled = true; if (self.bOptionsCreated) then LibStub("AceConfig-3.0"):RegisterOptionsTable("Gnosis", self.opt); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Castbars", self.opt_cbs); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Channeled Spells", self.opt_css); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Combattext/Clip test", self.opt_ctclip); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Configurations", self.opt_configs); else LibStub("AceConfig-3.0"):RegisterOptionsTable("Gnosis", self.optunloaded_main); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Castbars", self.optunloaded); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Channeled Spells", self.optunloaded); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Combattext/Clip test", self.optunloaded); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Configurations", self.optunloaded); end self:RegisterEvents(); self:DefaultAllBars(); -- blizzard castbar if (self.s.bHideBlizz) then self:HideBlizzardCastbar(true); end -- mirror castbar if (self.s.bHideMirror) then self:HideBlizzardMirrorCastbar(true); end -- pet/vehicle castbar if (self.s.bHidePetVeh) then self:HideBlizzardPetCastbar(true); end -- unregister global mouse events if (self.s.bUnregGlobalMouse) then self:UnregisterGlobalMouseEvents(); end -- scan table, fast lookup tablese self:CreateCBTables(); -- trigger talent update event (gone with 5.04 sent too early) self:PLAYER_TALENT_UPDATE(); -- resize interface options frame if (self.s.bResizeOptions and wowwc) then InterfaceOptionsFrame:SetWidth(835); end else -- disable addon self.bGnosisEnabled = false; if (self.bOptionsCreated) then LibStub("AceConfig-3.0"):RegisterOptionsTable("Gnosis", self.optdisabled); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Castbars", self.optempty); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Channeled Spells", self.optempty); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Combattext/Clip test", self.optempty); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Configurations", self.optempty); else LibStub("AceConfig-3.0"):RegisterOptionsTable("Gnosis", self.optunloaded_main); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Castbars", self.optunloaded); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Channeled Spells", self.optunloaded); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Combattext/Clip test", self.optunloaded); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Configurations", self.optunloaded); end self:UnregisterEvents(); self:HideAllBars(); -- blizzard castbar if (self.s.bHideBlizz) then self:HideBlizzardCastbar(false); end -- mirror castbar if (self.s.bHideMirror) then self:HideBlizzardMirrorCastbar(false); end -- pet/vehicle castbar if (self.s.bHidePetVeh) then self:HideBlizzardPetCastbar(false); end -- scan table, fast lookup castbar and timer tables self:ClearCBTables(); end end function Gnosis:OpenOptions() if (not self.iofcalled) then -- call twice the first time if (wowmainline) then Settings.OpenToCategory(Gnosis.optFrame); else InterfaceOptionsFrame_OpenToCategory(Gnosis.optFrame); end end if (wowmainline) then Settings.OpenToCategory(Gnosis.optFrame); else InterfaceOptionsFrame_OpenToCategory(Gnosis.optFrame); end self.iofcalled = true; end function Gnosis:OpenCfgOptions() if (not self.iofcfgcalled) then -- call twice the first time if (wowmainline) then Settings.OpenToCategory(Gnosis.optCfgs); else InterfaceOptionsFrame_OpenToCategory(Gnosis.optCfgs); end end if (wowmainline) then Settings.OpenToCategory(Gnosis.optCfgs); else InterfaceOptionsFrame_OpenToCategory(Gnosis.optCfgs); end self.iofcfgcalled = true; end function Gnosis:OpenCastbarOptions() if (not self.iofcbcalled) then -- call twice the first time if (wowmainline) then Settings.OpenToCategory(Gnosis.optCBs); else InterfaceOptionsFrame_OpenToCategory(Gnosis.optCBs); end end if (wowmainline) then Settings.OpenToCategory(Gnosis.optCBs); else InterfaceOptionsFrame_OpenToCategory(Gnosis.optCBs); end self.iofcbcalled = true; end function Gnosis:HideBlizzardCastbarIfStatusChange(status) if (self.s.bHideBlizz ~= status) then self.s.bHideBlizz = status; self:HideBlizzardCastbar(self.s.bHideBlizz); end end function Gnosis:HideBlizzardCastbar(status) if (status) then -- hide castbar for key, value in pairs(self.tBlizzCastbar) do if(wowmainline and PlayerCastingBarFrame:IsEventRegistered(value)) then table_insert(self.blizzcastbar, value); PlayerCastingBarFrame:UnregisterEvent(value); elseif(not wowmainline and CastingBarFrame:IsEventRegistered(value)) then table_insert(self.blizzcastbar, value); CastingBarFrame:UnregisterEvent(value); end end if (#self.blizzcastbar > 0) then if(not self.s.bHideAddonMsgs) then self:Print(Gnosis.L["MsgDisBlizCB"]); end else if(not self.s.bHideAddonMsgs) then self:Print(Gnosis.L["MsgBlizCBIsDis"]); end end else -- restore castbar events, it might not actually enable the blizzard castbar if another addon hides it for key, value in pairs(self.blizzcastbar) do if (wowmainline) then PlayerCastingBarFrame:RegisterEvent(value); else CastingBarFrame:RegisterEvent(value); end end if (#self.blizzcastbar > 0) then if(not self.s.bHideAddonMsgs) then self:Print(Gnosis.L["MsgBlizCBRestored"]); end end self.blizzcastbar = {}; end end function Gnosis:HideBlizzardPetCastbarIfStatusChange(status) if (status == nil) then status = false; end if (self.s.bHidePetVeh ~= status) then self.s.bHidePetVeh = status; self:HideBlizzardPetCastbar(self.s.bHidePetVeh); end end function Gnosis:HideBlizzardMirrorCastbarIfStatusChange(status) if (self.s.bHideMirror ~= status) then self.s.bHideMirror = status; self:HideBlizzardMirrorCastbar(self.s.bHideMirror); end end function Gnosis:HideBlizzardPetCastbar(status) if (status) then -- hide pet castbar for key, value in pairs(self.tBlizzCastbar) do if(PetCastingBarFrame:IsEventRegistered(value)) then table_insert(self.petcastbar, value); PetCastingBarFrame:UnregisterEvent(value); end end if (#self.petcastbar > 0) then if(not self.s.bHideAddonMsgs) then self:Print(Gnosis.L["MsgDisPetCB"]); end else if(not self.s.bHideAddonMsgs) then self:Print(Gnosis.L["MsgPetCBIsDis"]); end end else -- restore pet castbar events, it might not actually enable the blizzard castbar if another addon hides it for key, value in pairs(self.petcastbar) do PetCastingBarFrame:RegisterEvent(value); end if (#self.petcastbar > 0) then if(not self.s.bHideAddonMsgs) then self:Print(Gnosis.L["MsgPetCBRestored"]); end end self.petcastbar = {}; end end function Gnosis:HideBlizzardMirrorCastbar(status) if (status) then -- hide castbar for key, value in pairs(self.tBlizzMirrorUiParent) do if(UIParent:IsEventRegistered(value)) then table_insert(self.blizzmirroruiparent, value); UIParent:UnregisterEvent(value); end end for key, value in pairs(self.tBlizzMirror123) do if(MirrorTimer1:IsEventRegistered(value)) then table_insert(self.blizzmirror1, value); MirrorTimer1:UnregisterEvent(value); end if(MirrorTimer2:IsEventRegistered(value)) then table_insert(self.blizzmirror2, value); MirrorTimer2:UnregisterEvent(value); end if(MirrorTimer3:IsEventRegistered(value)) then table_insert(self.blizzmirror3, value); MirrorTimer3:UnregisterEvent(value); end end MirrorTimer1:Hide(); MirrorTimer2:Hide(); MirrorTimer3:Hide(); if (#self.blizzmirroruiparent > 0) then if(not self.s.bHideAddonMsgs) then self:Print(Gnosis.L["MsgDisMirrCB"]); end else if(not self.s.bHideAddonMsgs) then self:Print(Gnosis.L["MsgMirrCBIsDis"]); end end else -- restore mirror castbar events, it might not actually enable the blizzard mirror castbar if another addon hides it for key, value in pairs(self.blizzmirroruiparent) do UIParent:RegisterEvent(value); end for key, value in pairs(self.blizzmirror1) do MirrorTimer1:RegisterEvent(value); end for key, value in pairs(self.blizzmirror2) do MirrorTimer2:RegisterEvent(value); end for key, value in pairs(self.blizzmirror3) do MirrorTimer3:RegisterEvent(value); end if (#self.blizzmirroruiparent > 0) then if(not self.s.bHideAddonMsgs) then self:Print(Gnosis.L["MsgMirrCBRestored"]); end end self.blizzmirroruiparent = {}; self.blizzmirror1 = {}; self.blizzmirror2 = {}; self.blizzmirror3 = {}; end end function Gnosis:OnInitialize() local unitkey = UnitName("player") .. " - " .. GetRealmName(); if (GnosisDB and GnosisDB.profiles and GnosisDB.profiles[unitkey] and GnosisDB.profileKeys and GnosisDB.profileKeys[unitkey]) then -- copy to new char only based profile (to heavily cut down on mem usage) if (not GnosisChar) then GnosisChar = {}; end if (not GnosisChar.profileKeys) then GnosisChar.profileKeys = {}; end if (not GnosisChar.profiles) then GnosisChar.profiles = {}; end GnosisChar.profileKeys[unitkey] = unitkey; GnosisChar.profiles[unitkey] = self:deepcopy(GnosisDB.profiles[unitkey]); GnosisDB.profileKeys[unitkey] = nil; GnosisDB.profiles[unitkey] = nil; -- fully remove from db if empty if (self:tsize(GnosisDB.profiles) == 0) then GnosisDB.profiles = nil; end if (self:tsize(GnosisDB.profileKeys) == 0) then GnosisDB.profileKeys = nil; end end -- remove character specific profile name in character specific configuration file if (not GnosisCharConfig) then GnosisCharConfig = {}; self.db = LibStub("AceDB-3.0"):New("GnosisChar", defaults); if (self.db and self.db.profile and self:tsize(self.db.profile) > 0) then -- copy AceDB profile to GnosisCharConfig GnosisCharConfig = self:deepcopy(self.db.profile); end elseif (self:tsize(GnosisCharConfig) == 0) then self.db = LibStub("AceDB-3.0"):New("GnosisChar", defaults); if(self.db and self.db.profile and self:tsize(self.db.profile) > 0) then -- copy AceDB profile to GnosisCharConfig GnosisCharConfig = self:deepcopy(self.db.profile); end elseif (self:tsize(GnosisCharConfig) > 0 and GnosisChar) then -- empty GnosisChar table (removing AceDB character specific profile) wipe(GnosisChar); GnosisChar = nil; end -- set link to GnosisCharConfig self.s = GnosisCharConfig; self:RegisterChatCommand("gnosis", "HandleChatCommand"); -- GameTooltip for scanning self.tooltip = CreateFrame("GameTooltip", "GnosisGameTooltip", nil, "GameTooltipTemplate"); self.tooltip:SetOwner(WorldFrame, "ANCHOR_NONE"); end function Gnosis:SetupHooks() if (wowmainline) then -- tradeskill hooking hooksecurefunc(ProfessionsFrame.CraftingPage, "CreateInternal", function(_, recipeID, count, recipeLevel) Gnosis.bNewTradeSkill = tonumber(count) and true or nil; Gnosis.iLastTradeSkillCnt = tonumber(count); end) hooksecurefunc(C_TradeSkillUI, 'CloseTradeSkill', function() Gnosis.bNewTradeSkill = nil; Gnosis.iLastTradeSkillCnt = nil; Gnosis:CloseAllTradeskillBars(); end) -- bandaid for LibDialog-1.0 (MINOR <= 8, until it is fixed) local ld_name, ld_ver = LibStub:GetLibrary("LibDialog-1.0"); if (ld_ver and ld_ver <= 8) then hooksecurefunc(_G, 'CreateFrame', function(frameType, frameName) if (frameName and string_find(frameName, "LibDialog%-1%.0_Dialog") and _G[frameName]) then _G[frameName] = Mixin(_G[frameName], BackdropTemplateMixin); end end ); end elseif (wowclassic) or (wowbcc) then -- tradeskill hooking hooksecurefunc('DoTradeSkill', function(index, num) Gnosis.bNewTradeSkill = tonumber(num) and true or nil; Gnosis.iLastTradeSkillCnt = tonumber(num); end ); hooksecurefunc('CloseTradeSkill', function() Gnosis.bNewTradeSkill = nil; Gnosis.iLastTradeSkillCnt = nil; Gnosis:CloseAllTradeskillBars(); end ); end -- SetCVar hook hooksecurefunc('SetCVar', function(cv, val) if(cv == "uiscale") then Gnosis:UIScaleUpdate(); -- called when changing ui scale in the blizzard menu end end ); -- SetItemRef hook hooksecurefunc('SetItemRef', function(link, text, ...) Gnosis:SetItemRef(link, text); end ); --[[ hooksecurefunc("ChatFrame_OnHyperlinkShow", function(frame, link, text, ...) Gnosis:SetItemRef(link, text); end ); ]] end function Gnosis:OnEnable() -- set init values self:StartupVariables(); -- setup hooks self:SetupHooks(); -- load localization if (not self.s.strLocale and self.LSet[GetLocale()]) then self.s.strLocale = GetLocale(); elseif (not self.s.strLocale) then self.s.strLocale = "default"; end self:SetupLocale(); -- basic tables self:OptCreateBasicTables(); -- set default saved variables if (self.s.bAddonEn == nil) then self.s.optver = self.optver; end for key, value in pairs(self.tDefaults) do if (self.s[key] == nil) then self.s[key] = value; end end for key, value in pairs(self.tDefaults.ct) do if (self.s.ct[key] == nil) then self.s.ct[key] = value; end end for key, value in pairs(self.tDefaults.configs) do if (self.s.configs[key] == nil) then self.s.configs[key] = value; end end if (not self.s.bHideAddonMsgs) then self:Print(self.title .. " " .. Gnosis.L["MsgLoaded"] .. " " .. (self.s.bAddonEn and Gnosis.L["MsgEn"] or Gnosis.L["MsgDis"])); end -- check castbar options self:CheckStoredCastbarOptions(); -- upgrade character specific options version number? if (self.s.optver < self.optver) then self.s.optver = self.optver; end -- first start? local bFirstStart = self:CheckForFirstStart(); -- create saved castbars self:InitialCreateCastbars(); -- add channeled spells to table self:SetupChanneledSpellsTable(); -- create castbar options table if (self.s.bAutoCreateOptions or bFirstStart) then self.bOptionsCreated = true; self:CreateCastbarsOpt(); self:CreateChannelSpellsOpt(); self:OptCreateCTpage(); self:OptCreateConfigurations(); else self.bOptionsCreated = false; end -- enable/disable addon self:InitialConfig(); self:En(self.s.bAddonEn); -- get player GUID self.guid = UnitGUID("player"); -- enable AceComm-3.0 addon communication channel self.comm:RegisterComm("GnosisComm", Gnosis.CommCb); end function Gnosis:CreateOptions() if (not self.bOptionsCreated) then self.bOptionsCreated = true; self:CreateCastbarsOpt(); self:CreateChannelSpellsOpt(); self:OptCreateCTpage(); self:OptCreateConfigurations(); end if (self.bGnosisEnabled) then LibStub("AceConfig-3.0"):RegisterOptionsTable("Gnosis", self.opt); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Castbars", self.opt_cbs); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Channeled Spells", self.opt_css); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Combattext/Clip test", self.opt_ctclip); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Configurations", self.opt_configs); else LibStub("AceConfig-3.0"):RegisterOptionsTable("Gnosis", self.optdisabled); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Castbars", self.optempty); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Channeled Spells", self.optempty); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Combattext/Clip test", self.optempty); LibStub("AceConfigRegistry-3.0"):RegisterOptionsTable("Gnosis Configurations", self.optempty); end end function Gnosis:HandleChatCommand(cmd) local subcmd = string_match(cmd, "(%w+)"); if (subcmd and subcmd == "reanchor") then Gnosis:UIScaleUpdate(); elseif(subcmd and subcmd == "load") then subcmd = string_match(cmd, "load (.+)"); subcmd = subcmd and string_trim(subcmd); if(subcmd and string_len(subcmd) > 0) then self:LoadConfig(subcmd, false, true, false, false); end else local bar, text, cnt, spell, iscast; bar, cmd = Gnosis:ExtractRegex(cmd, "bar=(%w+)", "bar=\"([^\"]+)\""); text, cmd = Gnosis:ExtractRegex(cmd, "text=(%w+)", "text=\"([^\"]+)\""); spell, cmd = Gnosis:ExtractRegex(cmd, "spell=(%w+)", "spell=\"([^\"]+)\"", true); cnt, cmd = Gnosis:ExtractRegex(cmd, "time=([+-]?[0-9]*%.?[0-9]*)", "time=\"([+-]?[0-9]*%.?[0-9]*)\"", true); iscast = string_match(cmd, ".*(cast).*"); if (bar and text and cnt) then self:InjectTimer(bar, text, cnt, spell, iscast); else Gnosis:OpenOptions(); end end end function Gnosis:RecreateAllBars() self:HideAllBars(); wipe(self.castbars); self:CheckStoredCastbarOptions(); self:InitialCreateCastbars(); end function Gnosis:DefaultAllBars() for key, value in pairs(self.s.cbconf) do self:SetBarParams(key); end end function Gnosis:HideAllBars() wipe(self.activebars); wipe(self.fadeoutbars); for key, value in pairs(self.castbars) do value:Hide(); end end function Gnosis:RegisterEvents() local key, value; for key, value in pairs(Gnosis.tCastbarEvents) do if (wowclassic and self.libclcno) then self.libclcno.RegisterCallback(Gnosis, value, value); else self:RegisterEvent(value); end end for key, value in pairs(Gnosis.tMiscEvents) do self:RegisterEvent(value); end for key, value in pairs(Gnosis.tMirrorEvents) do self:RegisterEvent(value); end for key, value in pairs(Gnosis.tSwingEvents) do self:RegisterEvent(value); end end function Gnosis:UnregisterEvents() local key, value; for key, value in pairs(Gnosis.tCastbarEvents) do if (wowclassic and self.libclcno) then self.libclcno.UnregisterCallback(Gnosis, value, value); else self:UnregisterEvent(value); end end for key, value in pairs(Gnosis.tMiscEvents) do self:UnregisterEvent(value); end for key, value in pairs(Gnosis.tMirrorEvents) do self:UnregisterEvent(value); end for key, value in pairs(Gnosis.tSwingEvents) do self:UnregisterEvent(value); end end function Gnosis:RemoveChanneledSpell(name) if(self.s.channeledspells[name] ~= nil) then self.s.channeledspells[name] = nil; self:CreateChannelSpellsOpt(); end end function Gnosis:AddChanneledSpellByName(name, tickcount, bdoaddticks, bars, binit, baoe, school, bheal, iUpdate) local tx = ""; if (name) then if (bheal) then if (baoe) then tx = "col<0,1,0>(spellname) [tickscrits] +eh col
dps HPShittext< Hits>crittext< Crits>ticktext< Ticks>";
			else
				tx = "col<0,1,0>(spellname - col<1,1,1>coltargetcolcol
col<0,1,0>) [tickscrits] +eh  col
dps HPShittext< Hits>crittext< Crits>ticktext< Ticks>";
			end
		else
			if (school) then
				tx = "col<" .. school .. ">dmg col
col<1,1,0>(spellname) [tickscrits]col
clipped dps DPScliptext<(Clipped) >hittext< Hits>crittext< Crits>ticktext< Ticks>";
			else
				tx = "dmg col<1,1,0>(spellname) [tickscrits]col
clipped dps DPScliptext<(Clipped) >hittext< Hits>crittext< Crits>ticktext< Ticks>";
			end
		end

		local bStoredcliptest = false;
		local bStoredcombattext = false;

		-- update existing
		if (iUpdate and self.s.channeledspells[name] and (self.s.channeledspells[name].iupdate == nil or self.s.channeledspells[name].iupdate < iUpdate)) then
			bStoredcliptest = self.s.channeledspells[name] and self.s.channeledspells[name].bcliptest or false;
			bStoredcombattext = self.s.channeledspells[name] and self.s.channeledspells[name].bcombattext or false;
			tx = self.s.channeledspells[name] and self.s.channeledspells[name].ctstring or tx;
			self.s.channeledspells[name] = nil;
		end

		-- do not overwrite (possibly user edited) entry
		if (self.s.channeledspells[name] == nil) then
			self.s.channeledspells[name] = {
				ben = true,
				ticks = tickcount,
				baddticks = bdoaddticks,
				bars = bars,
				binit = binit,
				baoe = baoe,
				bticksound = false,
				bcliptest = bStoredcliptest,
				bcombattext = bStoredcombattext,
				bicon = true,
				bsticky = true,
				fontsizeclip = 0,
				fontsizenclip = 0,
				ctstring = tx,
				bhidenonplayer = bdoaddticks,
				iupdate = iUpdate,
			};
		end
	end
end

function Gnosis:AddChanneledSpellById(id, tickcount, bdoaddticks, bars, binit, baoe, school, bheal, iUpdate)
	local name = GetSpellInfo(id);

	self:AddChanneledSpellByName(name, tickcount, bdoaddticks, bars, binit, baoe, school, bheal, iUpdate);
end

function Gnosis:SetupChanneledSpellsTable()
	-- spellid, #ticks, addticks, #shown ticks, first tick instant, multi mob (no clipping detection), spellschool, isheal, upgrade_num

	-- priest
	if (wowclassic) or (wowbcc) then
		self:AddChanneledSpellById(15407, 3, false, 4, false, false, "shadow", false, 4);	-- mind flay
	else
		self:AddChanneledSpellById(15407, 6, false, 7, false, false, "shadow", false, 3);	-- mind flay
	end
	self:AddChanneledSpellById(48045, 6, false, 7, false, true, "shadow", false, 3); 	-- mind sear
	self:AddChanneledSpellById(47540, 3, false, 2, true, false, "holy", true, 1);		-- penance, first tick instant
	self:AddChanneledSpellById(64843, 4, false, 15, false, true, "holy", true, 4);		-- divine hymn

	-- mage
	if (wowclassic) or (wowbcc) then
		self:AddChanneledSpellById(10, 8, false, 15, false, true, "frost", false, 2);		-- blizzard
	end
	self:AddChanneledSpellById(5143, 5, false, 6, false, false, "arcane", false, 2);	-- arcane missiles
	self:AddChanneledSpellById(12051, 6, false, 7, true, false, "arcane", false, 2);	-- evocation

	-- warlock
	if (wowclassic) or (wowbcc) then
		self:AddChanneledSpellById(689, 6, false, 15, false, false, "shadow", false, 3);	-- drain life
		self:AddChanneledSpellById(1120, 4, false, 5, false, false, "shadow", false, 4);	-- drain soul
		self:AddChanneledSpellById(4629, 6, false, 15, false, true, "fire", false, 3);		-- rain of fire
	else
		self:AddChanneledSpellById(234153, 5, false, 6, false, false, "shadow", false, 3);	-- drain life
		self:AddChanneledSpellById(198590, 5, false, 6, false, false, "shadow", false, 4);	-- drain soul
	end
	self:AddChanneledSpellById(755, 6, false, 6, false, false, "shadow", false, 3);		-- health funnel

	-- druid
	self:AddChanneledSpellById(740, 4, false, 15, false, true, "nature", true, 3);		-- tranquility
	if (wowclassic) or (wowbcc) then
		self:AddChanneledSpellById(16914, 10, false, 15, false, true, "nature", false, 2);	-- hurricane
	end

	-- monk
	self:AddChanneledSpellById(113656, 20, false, 21, true, true, "physical", false, 1);	-- fists of fury, first tick instant, aoe
	self:AddChanneledSpellById(115175, 8, false, 9, false, false, "nature", true, 3);	-- soothing mist
end

function Gnosis:CreateColorString(r, g, b, a)
	if (not (tonumber(r) and tonumber(g) and tonumber(b) and tonumber (a))) then
		return "";
	end

	local str = string_format("%.2f, %.2f, %.2f, %.2f", r, g, b, a);
	return str;
end

function Gnosis:GetCoordinatesFromString(str)
	str = str .. ",0.0,0.0";	-- append safety net, also default coordinate

	local x, y = string_match(str, ".-([%+%-%.%d]+).-([%+%-%.%d]+)");

	if (not(x and y and tonumber(x) and tonumber(y))) then
		return 0.0, 0.0;
	else
		return tonumber(x), tonumber(y);
	end
end

function Gnosis:ScreenPercentageToString(px, py)
	local uis = UIParent:GetEffectiveScale();
	local xm, ym = GetScreenWidth(), GetScreenHeight();
	return string_format("%.2f, %.2f", px * xm * uis, py * ym * uis);
end

function Gnosis:StringToScreenPercentage(str)
	str = str .. ",0.0,0.0";	-- append safety net, also default coordinate

	local x, y = string_match(str, ".-([%+%-%.%d]+).-([%+%-%.%d]+)");

	if (not(x and y and tonumber(x) and tonumber(y))) then
		return 0.0, 0.0;
	else
		local uis = UIParent:GetEffectiveScale();
		local xm, ym = GetScreenWidth(), GetScreenHeight();
		return x / (xm*uis), y / (ym*uis);
	end
end

function Gnosis:GetColorsFromString(str, dst)
	str = str .. ",1.0,1.0,1.0,1.0";	-- append safety net (also default if illegal r,g,b values given
	local r, g, b, a = string_match(
		str,
		".-([-+]?[0-9]*%.?[0-9]+).-([-+]?[0-9]*%.?[0-9]+).-([-+]?[0-9]*%.?[0-9]+).-([-+]?[0-9]*%.?[0-9]+)"
	);

	r, g, b, a = tonumber(r), tonumber(g), tonumber(b), tonumber(a);
	if (not a) then
		r, g, b, a = nil, nil, nil, nil;
	end

	if (dst and r) then
		dst.r, dst.g, dst.b, dst.a = r, g, b, a;
	end

	return r, g, b, a;
end

local RCS_strClass = "";
local function ReplaceColorsStrings_GSubFunc(sub)
	if (sub == "pre" or sub == "prev") then
		return "|r";
	elseif (sub == "cpre") then
		return RCS_strClass and "|r" or "";
	elseif (sub == "class" and not RCS_strClass) then
		return "";
	end

	local colstr = (Gnosis.colSchools[sub] and Gnosis.colSchools[sub] or (
		(sub == "class" and RCS_strClass) and Gnosis.colClasses[RCS_strClass] or sub
		)) .. ",1.0,1.0,1.0,1.0";
	local r, g, b, a = string_match(
		colstr,".-([-+]?[0-9]*%.?[0-9]+).-([-+]?[0-9]*%.?[0-9]+).-([-+]?[0-9]*%.?[0-9]+).-([-+]?[0-9]*%.?[0-9]+)"
	);
	return string_format(
		"|c%02x%02x%02x%02x",
		tonumber(a) * 255,
		tonumber(r) * 255,
		tonumber(g) * 255,
		tonumber(b) * 255
	);
end

function Gnosis:ReplaceColorStrings(str, strClass)
	-- new line
	str = string_gsub(str, "\\n", "\n");

	RCS_strClass = strClass;
	return string_gsub(
		str,
		"col<([^>]*)>",
		ReplaceColorsStrings_GSubFunc,
		10
	);
end

function Gnosis:GenerateCombattext(cc, cs, bClip)
	-- substitute strings
	local str = cs.ctstring;
	local sub;
	local clipped, hit, crit, tick;

	-- spell school colors, format is col or col
		-- e.g. col<1.0,0.5,0.5,1.0>; col
	str = string_gsub(str, "col", "|r");
	str = self:ReplaceColorStrings(str, cc.class);

	-- clipped text
	clipped = string_match(str, "cliptext<(.-)>");
	clipped = clipped and clipped or "";
	str = string_gsub(str, "cliptext<(.-)>", "");
	hit = string_match(str, "hittext<(.-)>");
	hit = hit and hit or "";
	str = string_gsub(str, "hittext<(.-)>", "");
	crit = string_match(str, "crittext<(.-)>");
	crit = crit and crit or "";
	str = string_gsub(str, "crittext<(.-)>", "");
	tick = string_match(str, "ticktext<(.-)>");
	tick = tick and tick or "";
	str = string_gsub(str, "ticktext<(.-)>", "");

	-- hits, crits, ticks, dmg, dps, clipped
	local dpstime = (cc.freqtest and min(cc.freqtest-cc.starttime,cc.duration) or cc.duration) / 1000;
	local tickscrits = string_format("%d%s", cc.ticks + cc.mastery, tick);
	if (cc.crits > 0) then tickscrits = string_format("%s, %d%s", tickscrits, cc.crits, crit); end

	str = string_gsub(str, "tickscrits", tickscrits);
	str = string_gsub(str, "spellname", string_format("%s", cc.spell));
	str = string_gsub(str, "hits", string_format("%d", cc.hits) .. hit);
	str = string_gsub(str, "crits", string_format("%d", cc.crits) .. crit);
	str = string_gsub(str, "ticks", string_format("%d", cc.ticks + cc.mastery) .. tick);
	str = string_gsub(str, "dmg", string_format("%d", cc.dmg));
	str = string_gsub(str, "dps", (dpstime > 0.02) and string_format("%d", cc.dmg / dpstime) or "1.#INF");
	str = string_gsub(str, "eh", string_format("%d", cc.eh));
	str = string_gsub(str, "oh", string_format("%d", cc.oh));
	str = string_gsub(str, "target", cc.target or "");
	if (bClip)	then
		str = string_gsub(str, "clipped", clipped);
	else
		str = string_gsub(str, "clipped", "");
	end

	-- string ready for combat text output
	local strTex = nil;
	local bSticky = nil;
	if (cs.bicon) then strTex = cc.texture; end
	if (cs.bsticky and bClip) then bSticky = true; end

	-- font size
	local fs = nil;
	if (bClip and cs.fontsizeclip > 0) then
		fs = cs.fontsizeclip;
	elseif (not bClip and cs.fontsizenclip > 0) then
		fs = cs.fontsizenclip;
	end

	if (self.s.ct.addon == "MSBT" and MikSBT and MikSBT.IsModDisabled() == nil) then
		MikSBT.DisplayMessage(str, MikSBT.DISPLAYTYPE_OUTGOING, bSticky, nil, nil, nil, fs, nil, nil, strTex);
	elseif (self.s.ct.addon == "SCT" and SCT and (cc.type or SCTD)) then
		SCT:DisplayText(str, nil, bSticky, "damage", cc.type and SCT.FRAME2 or SCT.FRAME3, nil, nil, strTex);
	elseif (self.s.ct.addon == "Parrot" and Parrot) then
		Parrot:ShowMessage(str, "Outgoing", bSticky, 1, 1, 1, nil, fs, nil, strTex);
	elseif (self.s.ct.addon == "Blizz" and tostring(SHOW_COMBAT_TEXT) ~= "0") then
		CombatText_AddMessage(str, CombatText_StandardScroll, 1, 1, 1, bSticky, false);
	end
end

-- clip test
function Gnosis:SetupChannelData()
	local fCurTime = GetTime() * 1000.0;
	local name, displayName, texture, startTime, endTime = UnitChannelInfo("player");

	local cs = self.s.channeledspells[name];
	if (cs and cs.ben and (cs.bcliptest or cs.bcombattext or cs.bticksound)) then
		local cc = {};
		local totalticks = cs.ticks;
		local noninitticks = cs.binit and (totalticks-1) or totalticks;

		if(cs.baddticks) then
			-- estimate amount of ticks due to haste
			local haste = UnitSpellHaste("player") / 100.0 + 1.0;
			noninitticks = floor(noninitticks * haste + 0.5);
			totalticks = cs.binit and (noninitticks+1) or noninitticks;
		end

		-- data
		cc.spell = name;
		cc.endtime = endTime;
		cc.starttime = startTime;
		cc.duration = endTime - startTime;
		cc.channelticktime = (1 / noninitticks) * (endTime - startTime);
		cc.maxticks = totalticks;
		cc.testtime = endTime + self.s.wfcl;
		cc.pushback = 0;
		cc.dmg = 0;
		cc.eh = 0;
		cc.oh = 0;
		cc.ticks = 0;
		cc.hits = 0;
		cc.crits = 0;
		cc.mastery = 0;
		cc.bcliptest = cs.bcliptest;
		cc.baeo = cs.baoe;
		cc.texture = texture;
		cc.target = self.strLastTarget;
		cc.class = self.strLastTargetClass;
		cc.bticksound = cs.bticksound;
		cc.lastticktime = nil;

		-- ticks table
		cc.tticks = {};
		local idx = cc.maxticks;
		for i = 1, cc.maxticks do
			cc.tticks[idx] = endTime - (i-1) * cc.channelticktime;
			idx = idx - 1;
		end

		if (self.curchannel) then
			self.nextchannel = cc;
		else
			self.curchannel = cc;
		end
	end
end

function Gnosis:RequestClipTest()
	local fCurTime = GetTime() * 1000.0;

	local cc, nc = self.curchannel, self.nextchannel;
	if (cc) then
		cc.freqtest = cc.freqtest and min(cc.freqtest,fCurTime) or fCurTime;
		cc.fforcedtest = cc.fforcedtest and min(cc.fforcedtest,fCurTime + self.s.wfcl) or (fCurTime + self.s.wfcl);
	end
end

function Gnosis:UpdateClipTest()
	local spell, _, _, startTime, endTime = UnitChannelInfo("player");
	local cc = (self.nextchannel and self.nextchannel.spell == spell) and self.nextchannel or
		((self.curchannel and self.curchannel.spell == spell) and self.curchannel or nil);

	if (cc) then
		local fspb = endTime - cc.endtime;
		cc.endtime = endTime;
		cc.testtime = cc.testtime + fspb;

		if (fspb > 0) then
			-- chain channeling
			for i = 1, cc.maxticks do
				cc.tticks[i] = cc.tticks[i] + fspb;
			end
			cc.duration = cc.duration + fspb;
		elseif (fspb < 0) then
			local max_ = cc.maxticks;
			for i = 1, max_ do
				if(cc.tticks[i] > cc.endtime) then
					cc.tticks[i] = nil;
					cc.maxticks = cc.maxticks - 1;
				end
			end
		end
	end
end

function Gnosis:PlaySounds()
	if (self.s.ct.bsound and self.s.ct.sound and SOUNDKIT[self.s.ct.sound]) then
		PlaySound(SOUNDKIT[self.s.ct.sound], self.s.ct.channel and
			self.tSoundChannels[self.s.ct.channel] or self.tSoundChannels[1]);
	end
	if (self.s.ct.bmusic and self.s.ct.music) then
		PlaySoundFile(self.lsm:Fetch("sound", self.s.ct.music),
			self.s.ct.channel and self.tSoundChannels[self.s.ct.channel] or self.tSoundChannels[1]);
	end
	if (self.s.ct.bfile and self.s.ct.file) then
		PlaySoundFile(self.s.ct.file,
			self.s.ct.channel and self.tSoundChannels[self.s.ct.channel] or self.tSoundChannels[1]);
	end
end

function Gnosis:ClipTest(fCurTime)
	local cc, nc = self.curchannel, self.nextchannel;

	if (cc) then
		local cs = self.s.channeledspells[cc.spell];
		local bClip, bOutput = false, false;

		if ((not cs.baoe and cc.ticks == cc.maxticks) or fCurTime >= cc.testtime) then
			-- check spell out, no clipping
			bOutput = true;
		elseif (cc.fforcedtest and fCurTime >= cc.fforcedtest) then	-- clip test requested
			bOutput = true;
			-- test for clipping
			if (cs.bcliptest and not cs.baoe) then
				if (cc.tticks[cc.ticks+1] and (cc.tticks[cc.ticks+1] - cc.freqtest) <= self.s.ctt) then
					-- unintentional clipping detected, do not output as clip if player had no target when clip test was requested
					local tarname = UnitName("target");
					if((not nc or nc.spellname ~= cc.spellname) and tarname and not UnitIsDead("target")) then
						bClip = true;
					end
				end
			end
 		end

		if (bOutput) then
			-- play clip sound and output to combat text
			if (bClip and cs.bcliptest and not cs.bticksound) then
				self:PlaySounds();
			end

			if (cs.bcombattext and cc.ticks > 0) then
				self:GenerateCombattext(cc, cs, bClip);
			end

			-- done, next channeled spell in queue
			self.curchannel = nil;
			self.curchannel = self.nextchannel;
			self.nextchannel = nil;
		end

	elseif (nc) then
		self.curchannel = nil;
		self.curchannel = self.nextchannel;
		self.nextchannel = nil;
	end
end

function Gnosis:AddCustomBar(name, unit, width, height, scale, movefactor_y, movefactor_x, unlockicon)
	local fScale = UIParent:GetScale();
	local cfg;

	if (self.s.cbconf[name]) then
		self:RemoveCastbar(name);
	end

	self:OptCreateNewCastbar(name, unit);

	cfg = self.s.cbconf[name];

	cfg.scale = scale;
	cfg.width = width;
	cfg.height = height;
	cfg.bIconUnlocked = unlockicon;
	Gnosis:SetBarParams(name);

	cfg.anchor.py = cfg.anchor.py + movefactor_y * (self.tCastbarDefaults.height/GetScreenHeight() + 0.01) * fScale;
	cfg.anchor.px = cfg.anchor.px + movefactor_x * (self.tCastbarDefaults.height/GetScreenHeight()*2.5 + self.tCastbarDefaults.width/GetScreenWidth() + 0.01) * fScale;
	self:AnchorBar(name);
end

function Gnosis:CreateCustomCastbarSet()
	self:AddCustomBar("gn" .. self.L["CBSetPlayer"], "player", 300, 28, 1.0, 3, 0, true);
	self:AddCustomBar("gn" .. self.L["CBSetTarget"], "target", 250, 20, 1.0, 1, 0, false);
	self:AddCustomBar("gn" .. self.L["CBSetFocus"], "focus", 250, 20, 1.0, -1, 0, false);
	self:AddCustomBar("gn" .. self.L["CBSetPet"], "pet", 250, 20, 1.0, -3, 0, false);
	self:AddCustomBar("gn" .. self.L["CBSetMirror"], "mirror", 250, 20, 1.0, 6, 0, false);
end

function Gnosis:CreateGCDSwingTimers()
	self:AddCustomBar("gn" .. self.L["CBSetGCD"], "gcd", 250, 3, 1.0, -5, 0);
	self:AddCustomBar("gn" .. self.L["CBSetSwing"], "smr", 250, 3, 1.0, -6, 0);

	local cfg = self.s.cbconf["gn" .. self.L["CBSetGCD"]];
	cfg.border = 0;
	cfg.colBar = { 0.85, 0.85, 0.85, 0.70 };
	cfg.strNameFormat = "";
	cfg.strTimeFormat = "";
	cfg.iconside = "NONE";
	self:SetBarParams("gn" .. self.L["CBSetGCD"]);

	cfg = self.s.cbconf["gn" .. self.L["CBSetSwing"]];
	cfg.border = 0;
	cfg.colBar = { 0.85, 0.85, 0.85, 0.70 };
	cfg.strNameFormat = "";
	cfg.strTimeFormat = "r<1>";
	cfg.fontsize_timer = 11;
	cfg.coord.casttime.y = -9;
	cfg.iconside = "NONE";
	cfg.alignment = "FREE";
	self:SetBarParams("gn" .. self.L["CBSetSwing"]);
end

function Gnosis:RedoLocalization()
	self:SetupLocale();
	-- recreate tables
	self:OptCreateBasicTables();
	self:CreateCastbarsOpt();
	self:CreateChannelSpellsOpt();
	self:OptCreateCTpage();
	self:OptCreateConfigurations();

	local hide = self.s.bHideAddonMsgs;
	self.s.bHideAddonMsgs = true;
	-- reload most of the addon for localization
	self:En(false);
	self:En(true);
	self.s.bHideAddonMsgs = hide;
end

function Gnosis:tsize(t)
	local i = 0;
	if (t and type(t) == "table") then
		for k, v in pairs(t) do
			i = i + 1;
		end
	end

	return i;
end

function Gnosis:CheckForFirstStart(bForce)
	if (self.s.bAddonEn and (bForce or ((not Gnosis.s) or self:tsize(Gnosis.s.cbconf) == 0))) then
		-- create window
		local f = self.gui:Create("Window");

		local _, uc = UnitClass("player");
		if (UnitLevel("player") == 1 or (uc == "DEATHKNIGHT" and UnitLevel("player") == 55)) then
			-- probably newly created char, don't release widget for OnClose callback,
			-- otherwise window will be gone after the intro sequence
			f:SetCallback("OnClose", function(w) end);
		else
			f:SetCallback("OnClose", function(w) Gnosis.gui:Release(w); end);
		end

		f:SetTitle(Gnosis.L["AddonName"]);
		f:SetStatusText(Gnosis.L["IfCWAction"]);
		f:SetLayout("Flow");
		f:SetWidth(500);
		f:SetFullHeight(true);

		local h1 = self.gui:Create("Heading");
		if ((not Gnosis.s) or self:tsize(Gnosis.s.cbconf) == 0) then
			h1:SetText(Gnosis.L["IfNoCBs"]);
		end
		h1.width = "fill";
		f:AddChild(h1);

		local msg = self.gui:Create("Label");
		msg:SetText(Gnosis.L["IfCCSString"]);
		msg:SetFullWidth(true);
		f:AddChild(msg);

		local btnLCS = self.gui:Create("Button");
		btnLCS:SetWidth(230);
		btnLCS:SetText(Gnosis.L["IfCCSetup"]);
		btnLCS:SetCallback("OnClick", function()
				Gnosis:CreateCustomCastbarSet();
				Gnosis:HideBlizzardCastbarIfStatusChange(true);
				Gnosis:HideBlizzardMirrorCastbarIfStatusChange(true);
				Gnosis:HideBlizzardPetCastbarIfStatusChange(true);
			end
		);
		f:AddChild(btnLCS);

		local btnLCS = self.gui:Create("Button");
		btnLCS:SetWidth(230);
		btnLCS:SetText(Gnosis.L["IfCCTimers"]);
		btnLCS:SetCallback("OnClick", function()
				Gnosis:CreateGCDSwingTimers();
			end
		);
		f:AddChild(btnLCS);

		if (self:tsize(GnosisConfigs) > 0) then
			local h2 = self.gui:Create("Heading");
			h2:SetText(Gnosis.L["IfConfigs"]);
			h2.width = "fill";
			f:AddChild(h2);

			local msg2 = self.gui:Create("Label");
			msg2:SetText(Gnosis.L["IfLFConigs"]);
			msg2:SetFullWidth(true);
			f:AddChild(msg2);

			for key, value in pairs(GnosisConfigs) do
				local butCfg = self.gui:Create("Button");
				butCfg:SetWidth(230);
				butCfg:SetText(key);
				butCfg:SetCallback("OnClick", function()
						Gnosis:LoadConfig(key, true, true, true, true);
					end
				);
				f:AddChild(butCfg);
			end
		end

		local h3 = self.gui:Create("Heading");
		h3:SetText("");
		h3.width = "fill";
		f:AddChild(h3);

		local btnGUI = self.gui:Create("Button");
		btnGUI:SetWidth(230);
		btnGUI:SetText(Gnosis.L["IfOpenGUI"]);
		btnGUI:SetCallback("OnClick", function()
				Gnosis:OpenOptions();
			end
		);
		f:AddChild(btnGUI);

		self.IntroFrame = f;

		return true;
	end

	return false;
end

-- returns: embedded match, text before, text after, first match, last match
function Gnosis:ExtractEmbeddedString(str, first, last, dotrim)
	-- check parameters
	if (first and (type(first) ~= "string" or string_len(first) < 1)) then
		return;
	end
	if (last and (type(last) ~= "string" or string_len(last) < 1)) then
		return;
	end
	if (type(str) == "string") then
		-- trim string (remove leading and trailing whitespace)
		if (dotrim) then
			str = string.trim(str);
		end
	else
		return;
	end

	-- local variables
	local first_found = nil;
	local last_found = nil;
	local cur = str;
	local embedded = "";
	local before = "";
	local after = "";
	local pattern_esc = "(\\)";
	local pattern_first = first and ("(" .. first .. ")") or nil;
	local pattern_last = last and ("(" .. last .. ")") or nil;

	-- find first
	if (pattern_first) then
		local s, f, match_first = string_find(str, pattern_first);

		if (match_first) then -- pattern_first
			first_found = match_first;
			before = string_sub(str, 1, s-1);
			cur = string_sub(str, f+1);
		else -- no match
			return nil;
		end
	end

	-- find last
	while (true) do
		local s1, f1, s2, f2, s3, f3, match_esc, match_last, new_match_first;

		s1, f1, match_esc = string_find(cur, pattern_esc);
		if (pattern_last) then
			s2, f2, match_last = string_find(cur, pattern_last);
		end
		if (pattern_first) then
			s3, f3, new_match_first = string_find(cur, pattern_first);
		end

		local bContinue = true;
		-- matched both, pattern_esc < pattern_first?
		if (new_match_first) then
			local bNewMatch;
			if (s1 and s2) then
				bNewMatch = s3 < s1 and s3 < s2;
			elseif (s1) then
				bNewMatch = s3 < s1;
			elseif (s2) then
				bNewMatch = s3 < s2;
			end

			if (bNewMatch) then
				-- found new beginning
				first_found = new_match_first;
				before = before .. string_sub(cur, 1, s3-1);
				cur = string_sub(cur, f3+1);
				bContinue = false;
			end
		end

		if (bContinue) then
			if (s1 and s2 and s1 < s2) then
				match_last = nil;
			end

			if (match_last) then -- pattern_last
				-- found substring ending
				last_found = match_last;
				embedded = embedded .. string_sub(cur, 1, s2-1);
				after = string_sub(cur, f2+1);
				return embedded, before, after, first_found, last_found;
			elseif (match_esc) then -- pattern_esc
				embedded = embedded .. string_sub(cur, 1, s1-1) .. string_sub(cur, s1+1, s1+1);
				cur = string_sub(cur, s1+2);
			else -- no match
				-- did not find end of embedded string
				if (last) then
					return;
				else
					if (first_found) then
						embedded = embedded .. cur;
					end

					if (string_len(embedded) > 0) then
						return embedded, before, after, first_found;
					else
						return;
					end
				end
			end
		end
	end
end

-- exchanges all chars in charsToEscape string with \char, don't forget % if "magic" character
function Gnosis:ExchangeEscapeSequenceChars(str, charsToEscape)
   local pattern = "([" .. charsToEscape .. "])";
   return string_gsub(str, pattern, "\\%1");
end

-- hook SetItemRef (import Gnosis bar via chatlink)
function Gnosis:SetItemRef(link, text)
	-- remove text coloring
	link = string_gsub(link, "\124c[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]", "");
	link = string_gsub(link, "\124r", "");
	text = string_gsub(text, "\124c[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]", "");
	text = string_gsub(text, "\124r", "");

	local s, f, name, server, barname;
	if (link and text) then
		s, f, name, server = link:find("^gnosis([^%s/]+)/(.+)$");
		s, f, barname = text:find("%[[^:]+: (.-)%]\124h$");
	end

	if (name and server and barname) then
		if (not IsShiftKeyDown()) then
			Gnosis.comm:SendCommMessage("GnosisComm", "req:" .. barname, "WHISPER", name .. "-" .. server);
		end
	end
end

-- communication events
function Gnosis:CommCb(message, distribution, sender)
	local s, f, barname = message:find("req:(.+)");
	local hash, serial_tab;

	local importname, before, after, match_first, match_last = Gnosis:ExtractEmbeddedString(message, "%[", "%]");
	if (importname and string_len(after) > 0) then
		s, f, hash, serial_tab = string_find(after, "(.-):(.+)$");
	end

	if (barname) then
		-- request received, send bar via addon channel
		if (Gnosis.s.cbconf[barname]) then
			-- serialize table
			local comp = Gnosis.libs:Serialize(Gnosis.s.cbconf[barname]);
			-- compress and encode for communication via addon channel
			local lc = Gnosis.libc;
			comp = lc:Compress(comp);
			comp = lc:GetAddonEncodeTable():Encode(comp);
			-- generate 32bit hash
			local comp_hash = lc:fcs32final(lc:fcs32update(lc:fcs32init(), comp));
			-- complete message to send
			local msg = "[" .. Gnosis:ExchangeEscapeSequenceChars(barname, "\\:%[%]") .. "]" .. comp_hash .. ":" .. comp;

			-- send to sender of request
			Gnosis.comm:SendCommMessage("GnosisComm", msg, distribution, sender);
		end
	elseif (importname and hash and serial_tab) then
		-- bar data received
		local lc = Gnosis.libc;
		-- compute hash
		local comp_hash = lc:fcs32final(lc:fcs32update(lc:fcs32init(), serial_tab));

		-- check hash
		if (comp_hash == tonumber(hash)) then
			-- message ok, import
			local ok;
			-- decode string
			local uncomp = lc:GetAddonEncodeTable():Decode(serial_tab);
			-- decompress
			uncomp = lc:Decompress(uncomp);
			-- deserialize, uncomp holds original table afterwards
			ok, uncomp = Gnosis.libs:Deserialize(uncomp);

			if (ok) then
				-- create import dialog
				Gnosis.dialog:Register("GNOSIS_IMPORT_HYPERLINK",
					{
						text = sender .. " -> |cffdddd22" .. importname .. "|r\n\n" .. Gnosis.L["ImportFromHyperlink"],
						buttons = {
							{
								text = Gnosis.L["Import"],
								on_click = function(self)
									Gnosis:ImportBarInit(importname);
									Gnosis.s.cbconf[importname] = uncomp;
									Gnosis:ImportBarFinalize(importname);
									--InterfaceOptionsFrame_OpenToCategory(Gnosis.optCBs);
								end,
							},
							(Gnosis.s.cbconf[importname] and {
								text = Gnosis.L["ImportKeepPos"],
								on_click = function(self)
									Gnosis:ImportBarInit(importname);
									local anchor = Gnosis.s.cbconf[importname].anchor;
									Gnosis.s.cbconf[importname] = uncomp;
									Gnosis.s.cbconf[importname].anchor = anchor;
									Gnosis:ImportBarFinalize(importname);
									--InterfaceOptionsFrame_OpenToCategory(Gnosis.optCBs);
								end,
							} or {}),
							{
								text = Gnosis.L["NoImport"],
								on_click = function(self)
								end,
							},
						},
						hide_on_escape = false,
						show_while_dead = true,
						width = 420,
						strata = 5,
					}
				);

				Gnosis.dialog:Spawn("GNOSIS_IMPORT_HYPERLINK");
			end
		end
	end
end

local oldSetHyperlink = ItemRefTooltip.SetHyperlink;
function ItemRefTooltip:SetHyperlink(link, ...)
	if (link and link:find("^gnosis")) then
		return;
    end

    return oldSetHyperlink(self, link, ...);
end

local function exchangeHyperlink(_, _, msg, ...)
    local msgToPrint = "";

	while (true) do
		local embedded, before, after, match_first = Gnosis:ExtractEmbeddedString(msg, "%[Gnosis:[^%s%-]+%-[^%s%:]+:", "]");

		if (embedded) then
			local s, f, name, server = string_find(match_first, "%[Gnosis:([^%s%-]+)%-([^%s%-]+):");
			msgToPrint = msgToPrint .. before ..
				"\124Hgnosis" .. name .. "/" .. server .. "\124h" ..
				"\124cffdddd22[" .. name .. "\124r\124cffdddd22: " .. embedded .. "]\124r\124h";

			msg = after;
		else
			msgToPrint = msgToPrint .. msg;
			break;
		end
	end

	return false, msgToPrint, ...;
end

ChatFrame_AddMessageEventFilter("CHAT_MSG_WHISPER", exchangeHyperlink);
ChatFrame_AddMessageEventFilter("CHAT_MSG_WHISPER_INFORM", exchangeHyperlink);
ChatFrame_AddMessageEventFilter("CHAT_MSG_BN_WHISPER", exchangeHyperlink);
ChatFrame_AddMessageEventFilter("CHAT_MSG_BN_WHISPER_INFORM", exchangeHyperlink);
ChatFrame_AddMessageEventFilter("CHAT_MSG_BN_CONVERSATION", exchangeHyperlink);
ChatFrame_AddMessageEventFilter("CHAT_MSG_SAY", exchangeHyperlink);
ChatFrame_AddMessageEventFilter("CHAT_MSG_YELL", exchangeHyperlink);
ChatFrame_AddMessageEventFilter("CHAT_MSG_GUILD", exchangeHyperlink);
ChatFrame_AddMessageEventFilter("CHAT_MSG_OFFICER", exchangeHyperlink);
ChatFrame_AddMessageEventFilter("CHAT_MSG_PARTY", exchangeHyperlink);
ChatFrame_AddMessageEventFilter("CHAT_MSG_PARTY_LEADER", exchangeHyperlink);
ChatFrame_AddMessageEventFilter("CHAT_MSG_RAID", exchangeHyperlink);
ChatFrame_AddMessageEventFilter("CHAT_MSG_RAID_LEADER", exchangeHyperlink);
ChatFrame_AddMessageEventFilter("CHAT_MSG_INSTANCE_CHAT", exchangeHyperlink);
ChatFrame_AddMessageEventFilter("CHAT_MSG_INSTANCE_CHAT_LEADER", exchangeHyperlink);
ChatFrame_AddMessageEventFilter("CHAT_MSG_CHANNEL", exchangeHyperlink);

local function repaste_hyperlink(self, link, text, ...)
	-- remove text coloring
	link = string_gsub(link, "\124c[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]", "");
	link = string_gsub(link, "\124r", "");
	text = string_gsub(text, "\124c[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]", "");
	text = string_gsub(text, "\124r", "");

	local s, f, name, server, barname;
	if (link and text) then
		s, f, name, server = link:find("^gnosis([^%s/]+)/(.+)$");
		s, f, barname = text:find("%[[^:]+: (.-)%]\124h$");
	end

	if (name and server and barname and IsShiftKeyDown()) then
		local link = "[Gnosis:" .. name .. "-" .. server .. ":" .. Gnosis:ExchangeEscapeSequenceChars(barname, "\\:%[%]") .. "]";

		local eb = GetCurrentKeyBoardFocus();
		if (eb) then
			eb:Insert(link);
		end
	end
end

hooksecurefunc("ChatFrame_OnHyperlinkShow", repaste_hyperlink);

function Gnosis:ExportBarEncStr(key)
	if (key and self.s.cbconf[key]) then
		-- serialize table
		local comp = self.libs:Serialize(self.s.cbconf[key]);
		local len = string.len(comp);	-- do not use utf8 version
		-- compress and encode for communication via addon channel
		local lc = Gnosis.libc;
		comp = lc:Compress(comp);
		comp = self:EncStr(comp);
		-- generate 32bit hash
		local comp_hash = lc:fcs32final(lc:fcs32update(lc:fcs32init(), comp));
		-- complete message to send
		local msg = "[" .. Gnosis:ExchangeEscapeSequenceChars(key, "\\:%[%]") .. ":" ..
			comp_hash .. ":" .. len .. ":=" .. comp .. ";]";

		return msg;
	end
end

function Gnosis:ExtractAndImportEncStr(str)
	local import, _;
	local importname, before, after, match_first, match_last = Gnosis:ExtractEmbeddedString(str, "%[", ":%d+:%d+:=[0-9A-Za-z%#%*]+;%]");

	if (importname) then
		local lc = Gnosis.libc;
		local s, f, hash, len, import = string_find(match_last, ":(%d+):(%d+):=([0-9A-Za-z%#%*]+);%]");

		if (s) then
			if (tonumber(hash) == lc:fcs32final(lc:fcs32update(lc:fcs32init(), import))) then
				-- compress and encode for communication via addon channel
				-- message ok, import
				local ok;
				-- decode string
				local uncomp = Gnosis:DecStr(import, tonumber(len));
				-- decompress
				uncomp = lc:Decompress(uncomp);
				-- deserialize, uncomp holds original table afterwards
				ok, uncomp = Gnosis.libs:Deserialize(uncomp);

				if (ok) then
					-- create import dialog
					Gnosis.dialog:Register("GNOSIS_IMPORT_ENCSTR",
						{
							text = "|cffdddd22" .. importname .. "|r\n\n" .. Gnosis.L["ImportFromHyperlink"],
							buttons = {
								{
									text = Gnosis.L["Import"],
									on_click = function(self)
										Gnosis:ImportBarInit(importname);
										Gnosis.s.cbconf[importname] = uncomp;
										Gnosis:ImportBarFinalize(importname);
										--InterfaceOptionsFrame_OpenToCategory(Gnosis.optCBs);
									end,
								},
								Gnosis.s.cbconf[importname] and {
									text = Gnosis.L["ImportKeepPos"],
									on_click = function(self)
										Gnosis:ImportBarInit(importname);
										local anchor = Gnosis.s.cbconf[importname].anchor;
										Gnosis.s.cbconf[importname] = uncomp;
										Gnosis.s.cbconf[importname].anchor = anchor;
										Gnosis:ImportBarFinalize(importname);
										--InterfaceOptionsFrame_OpenToCategory(Gnosis.optCBs);
									end,
								} or {},
								{
									text = Gnosis.L["NoImport"],
									on_click = function(self)
									end,
								},
							},
							on_hide = function(self)
								-- LibDialog-1.0 bandaid
								Gnosis.bDelayedEsc = true;
							end,
							hide_on_escape = false,
							show_while_dead = true,
							width = 420,
							strata = 5,
						}
					);

					Gnosis.dialog:Spawn("GNOSIS_IMPORT_ENCSTR");
				end
				return before .. after, true;
			end
		end
	end

	return str;
end

function Gnosis:ImportBarsFromStr(str)
	local found;

	-- import encoded bars
	repeat
		str, found = Gnosis:ExtractAndImportEncStr(str);
	until (found == nil);
end

function Gnosis:UnregisterGlobalMouseEvents()
	if (Gnosis.s.bUnregGlobalMouse) then
		if (not UIParent:IsEventRegistered("GLOBAL_MOUSE_DOWN") and
			not UIParent:IsEventRegistered("GLOBAL_MOUSE_DOWN")) then
			if (not self.s.bHideAddonMsgs) then
				self:Print(Gnosis.L["MsgUnregGlobalMouseNotReq"]);
			end
		else
			UIParent:UnregisterEvent("GLOBAL_MOUSE_DOWN");
			UIParent:UnregisterEvent("GLOBAL_MOUSE_UP");

			if (not self.s.bHideAddonMsgs) then
				self:Print(Gnosis.L["MsgUnregGlobalMouse"]);
			end
		end
	end
end