/
--[[ |
LibDualSpec-1.0 - Adds dual spec support to individual AceDB-3.0 databases |
Copyright (C) 2009-2011 Adirelle |
All rights reserved. |
Redistribution and use in source and binary forms, with or without |
modification, are permitted provided that the following conditions are met: |
* Redistributions of source code must retain the above copyright notice, |
this list of conditions and the following disclaimer. |
* Redistributions in binary form must reproduce the above copyright notice, |
this list of conditions and the following disclaimer in the documentation |
and/or other materials provided with the distribution. |
* Redistribution of a stand alone version is strictly prohibited without |
prior written authorization from the LibDualSpec project manager. |
* Neither the name of the LibDualSpec authors nor the names of its contributors |
may be used to endorse or promote products derived from this software without |
specific prior written permission. |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
--]] |
local MAJOR, MINOR = "LibDualSpec-1.0", 8 |
assert(LibStub, MAJOR.." requires LibStub") |
local lib = LibStub:NewLibrary(MAJOR, MINOR) |
if not lib then return end |
-- ---------------------------------------------------------------------------- |
-- Library data |
-- ---------------------------------------------------------------------------- |
lib.eventFrame = lib.eventFrame or CreateFrame("Frame") |
lib.registry = lib.registry or {} |
lib.options = lib.options or {} |
lib.mixin = lib.mixin or {} |
-- ---------------------------------------------------------------------------- |
-- Locals |
-- ---------------------------------------------------------------------------- |
local registry = lib.registry |
local options = lib.options |
local mixin = lib.mixin |
-- "Externals" |
local AceDB3 = LibStub('AceDB-3.0', true) |
local AceDBOptions3 = LibStub('AceDBOptions-3.0', true) |
-- ---------------------------------------------------------------------------- |
-- Localization |
-- ---------------------------------------------------------------------------- |
local L_DUALSPEC_DESC, L_ENABLED, L_ENABLED_DESC, L_DUAL_PROFILE, L_DUAL_PROFILE_DESC |
do |
L_DUALSPEC_DESC = "When enabled, this feature allow you to select a different ".. |
"profile for each talent spec. The dual profile will be swapped with the ".. |
"current profile each time you switch from a talent spec to the other." |
L_ENABLED = 'Enable dual profile' |
L_ENABLED_DESC = 'Check this box to automatically swap profiles on talent switch.' |
L_DUAL_PROFILE = 'Dual profile' |
L_DUAL_PROFILE_DESC = 'Select the profile to swap with on talent switch.' |
local locale = GetLocale() |
if locale == "frFR" then |
L_DUALSPEC_DESC = "Lorsqu'elle est activée, cette fonctionnalité vous permet de choisir un profil différent pour chaque spécialisation de talents. Le second profil sera échangé avec le profil courant chaque fois que vous passerez d'une spécialisation à l'autre." |
L_DUAL_PROFILE = "Second profil" |
L_DUAL_PROFILE_DESC = "Sélectionnez le profil à échanger avec le profil courant lors du changement de spécialisation." |
L_ENABLED = "Activez le second profil" |
L_ENABLED_DESC = "Cochez cette case pour échanger automatiquement les profils lors d'un changement de spécialisation." |
elseif locale == "deDE" then |
L_DUALSPEC_DESC = "Wenn aktiv, wechselt dieses Feature bei jedem Wechsel der dualen Talentspezialisierung das Profil. Das duale Profil wird beim Wechsel automatisch mit dem derzeit aktiven Profil getauscht." |
L_DUAL_PROFILE = "Duales Profil" |
L_DUAL_PROFILE_DESC = "Wähle das Profil, das beim Wechsel der Talente aktiviert wird." |
L_ENABLED = "Aktiviere Duale Profile" |
L_ENABLED_DESC = "Aktiviere diese Option, um beim Talentwechsel automatisch zwischen den Profilen zu wechseln." |
elseif locale == "koKR" then |
L_DUALSPEC_DESC = "ê°ë¥íë©´ ì¬ì©í©ëë¤. ì´ì¤ í¹ì±ì ìíì¬ ë¤ë¥¸ íë¡íì ì íí ì ìê² íëë¤. ì´ì¤ íë¡íì íì¬ íë¡íê³¼ ë²ê°ìì í¹ì±ì´ ë³ê²½ë ë ê°ì´ ì ì©ë©ëë¤." |
L_DUAL_PROFILE = "ì´ì¤ íë¡í" |
L_DUAL_PROFILE_DESC = "í¹ì±ì´ ë°ë ë íë¡íì ì íí©ëë¤." |
L_ENABLED = "ì´ì¤ íë¡í ì¬ì©" |
L_ENABLED_DESC = "í¹ì±ì´ ë³ê²½ ë ë ìëì¼ë¡ íë¡íì ë³ê²½íëë¡ ì íí©ëë¤." |
elseif locale == "ruRU" then |
L_DUALSPEC_DESC = "Ðвойной пÑоÑÐ¸Ð»Ñ Ð¿Ð¾Ð·Ð²Ð¾Ð»ÑÐµÑ Ð²Ð°Ð¼ вÑбÑаÑÑ ÑазлиÑнÑе пÑоÑили Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð¹ ÑаÑкладки ÑаланÑов. ÐÑоÑили бÑдÑÑ Ð¿ÐµÑеклÑÑаÑÑÑÑ ÐºÐ°Ð¶Ð´Ñй Ñаз, когда Ð²Ñ Ð¿ÐµÑеклÑÑаеÑе ÑаÑÐºÐ»Ð°Ð´ÐºÑ ÑаланÑов." |
L_DUAL_PROFILE = "ÐÑоÑой пÑоÑилÑ" |
L_DUAL_PROFILE_DESC = "ÐÑбеÑиÑе пÑоÑилÑ, коÑоÑÑй Ð½ÐµÐ¾Ð±Ñ Ð¾Ð´Ð¸Ð¼Ð¾ акÑивиÑоваÑÑ Ð¿Ñи пеÑеклÑÑениии ÑаланÑов." |
L_ENABLED = "ÐклÑÑиÑÑ Ð´Ð²Ð¾Ð¹Ð½Ð¾Ð¹ пÑоÑилÑ" |
L_ENABLED_DESC = "ÐклÑÑиÑе ÑÑÑ Ð¾Ð¿ÑÐ¸Ñ Ð´Ð»Ñ Ð°Ð²ÑомаÑиÑеÑкого пеÑеклÑÑÐµÐ½Ð¸Ñ Ð¼ÐµÐ¶Ð´Ñ Ð¿ÑоÑилÑми пÑи пеÑеклÑÑении ÑаÑкладки ÑаланÑов." |
elseif locale == "zhCN" then |
L_DUALSPEC_DESC = "å¯æ¶ï¼ä½ å¯ä»¥ä¸ºä½ çå天èµè®¾å®å¦ä¸ç»é ç½®æ件ï¼ä½ çåéé ç½®æ件å°å¨ä½ 转æ¢å¤©èµæ¶èªå¨ä¸ç®å使ç¨é ç½®æ件交æ¢ã" |
L_DUAL_PROFILE = "åéé ç½®æ件" |
L_DUAL_PROFILE_DESC = "éæ©è½¬æ¢å¤©èµæ¶æè¦ä½¿ç¨çé ç½®æ件" |
L_ENABLED = "å¼å¯åéé ç½®æ件" |
L_ENABLED_DESC = "å¾é以便转æ¢å¤©èµæ¶èªå¨äº¤æ¢é ç½®æ件ã" |
elseif locale == "zhTW" then |
L_DUALSPEC_DESC = "åç¨æï¼ä½ å¯ä»¥çºä½ çé天賦è¨å®å¦ä¸çµè¨å®æªãä½ çéè¨å®æªå°å¨ä½ è½æ天賦æèªåèç®å使ç¨è¨å®æªäº¤æã" |
L_DUAL_PROFILE = "éè¨å®æª" |
L_DUAL_PROFILE_DESC = "é¸æè½æ天賦å¾æè¦ä½¿ç¨çè¨å®æª" |
L_ENABLED = "åç¨éè¨å®æª" |
L_ENABLED_DESC = "å¾é¸ä»¥å¨è½æ天賦æèªå交æè¨å®æª" |
elseif locale == "esES" then |
L_DUALSPEC_DESC = "Si está activa, esta caracterÃstica te permite seleccionar un perfil distinto para cada configuración de talentos. El perfil secundario será intercambiado por el activo cada vez que cambies de una configuración de talentos a otra." |
L_DUAL_PROFILE = "Perfil secundario" |
L_DUAL_PROFILE_DESC = "Elige el perfil secundario que se usará cuando cambies de talentos." |
L_ENABLED = "Activar perfil secundario" |
L_ENABLED_DESC = "Activa esta casilla para alternar automáticamente entre prefiles cuando cambies de talentos." |
end |
end |
-- ---------------------------------------------------------------------------- |
-- Mixin |
-- ---------------------------------------------------------------------------- |
--- Get dual spec feature status. |
-- @return (boolean) true is dual spec feature enabled. |
-- @name enhancedDB:IsDualSpecEnabled |
function mixin:IsDualSpecEnabled() |
return registry[self].db.char.enabled |
end |
--- Enable/disabled dual spec feature. |
-- @param enabled (boolean) true to enable dual spec feature, false to disable it. |
-- @name enhancedDB:SetDualSpecEnabled |
function mixin:SetDualSpecEnabled(enabled) |
local db = registry[self].db |
if enabled and not db.char.talentGroup then |
db.char.talentGroup = lib.talentGroup |
db.char.profile = self:GetCurrentProfile() |
db.char.enabled = true |
else |
db.char.enabled = enabled |
self:CheckDualSpecState() |
end |
end |
--- Get the alternate profile name. |
-- Defaults to the current profile. |
-- @return (string) Alternate profile name. |
-- @name enhancedDB:GetDualSpecProfile |
function mixin:GetDualSpecProfile() |
return registry[self].db.char.profile or self:GetCurrentProfile() |
end |
--- Set the alternate profile name. |
-- No validation are done to ensure the profile is valid. |
-- @param profileName (string) the profile name to use. |
-- @name enhancedDB:SetDualSpecProfile |
function mixin:SetDualSpecProfile(profileName) |
registry[self].db.char.profile = profileName |
end |
--- Check if a profile swap should occur. |
-- Do nothing if the dual spec feature is disabled. In the other |
-- case, if the internally stored talent spec is different from the |
-- actual active talent spec, the database swaps to the alternate profile. |
-- There is normally no reason to call this method directly as LibDualSpec |
-- takes care of calling it at appropriate times. |
-- @name enhancedDB:CheckDualSpecState |
function mixin:CheckDualSpecState() |
local db = registry[self].db |
if lib.talentsLoaded and db.char.enabled and db.char.talentGroup ~= lib.talentGroup then |
local currentProfile = self:GetCurrentProfile() |
local newProfile = db.char.profile |
db.char.talentGroup = lib.talentGroup |
if newProfile ~= currentProfile then |
self:SetProfile(newProfile) |
db.char.profile = currentProfile |
end |
end |
end |
-- ---------------------------------------------------------------------------- |
-- AceDB-3.0 support |
-- ---------------------------------------------------------------------------- |
local function EmbedMixin(target) |
for k,v in pairs(mixin) do |
rawset(target, k, v) |
end |
end |
-- Upgrade existing mixins |
for target in pairs(registry) do |
EmbedMixin(target) |
end |
-- Actually enhance the database |
-- This is used on first initialization and everytime the database is reset using :ResetDB |
function lib:_EnhanceDatabase(event, target) |
registry[target].db = target:GetNamespace(MAJOR, true) or target:RegisterNamespace(MAJOR) |
EmbedMixin(target) |
target:CheckDualSpecState() |
end |
--- Embed dual spec feature into an existing AceDB-3.0 database. |
-- LibDualSpec specific methods are added to the instance. |
-- @name LibDualSpec:EnhanceDatabase |
-- @param target (table) the AceDB-3.0 instance. |
-- @param name (string) a user-friendly name of the database (best bet is the addon name). |
function lib:EnhanceDatabase(target, name) |
AceDB3 = AceDB3 or LibStub('AceDB-3.0', true) |
if type(target) ~= "table" then |
error("Usage: LibDualSpec:EnhanceDatabase(target, name): target should be a table.", 2) |
elseif type(name) ~= "string" then |
error("Usage: LibDualSpec:EnhanceDatabase(target, name): name should be a string.", 2) |
elseif not AceDB3 or not AceDB3.db_registry[target] then |
error("Usage: LibDualSpec:EnhanceDatabase(target, name): target should be an AceDB-3.0 database.", 2) |
elseif target.parent then |
error("Usage: LibDualSpec:EnhanceDatabase(target, name): cannot enhance a namespace.", 2) |
elseif registry[target] then |
return |
end |
registry[target] = { name = name } |
lib:_EnhanceDatabase("EnhanceDatabase", target) |
target.RegisterCallback(lib, "OnDatabaseReset", "_EnhanceDatabase") |
end |
-- ---------------------------------------------------------------------------- |
-- AceDBOptions-3.0 support |
-- ---------------------------------------------------------------------------- |
local function NoDualSpec() |
return GetNumTalentGroups() == 1 |
end |
options.dualSpecDesc = { |
name = L_DUALSPEC_DESC, |
type = 'description', |
order = 40.1, |
hidden = NoDualSpec, |
} |
options.enabled = { |
name = L_ENABLED, |
desc = L_ENABLED_DESC, |
type = 'toggle', |
order = 40.2, |
get = function(info) return info.handler.db:IsDualSpecEnabled() end, |
set = function(info, value) info.handler.db:SetDualSpecEnabled(value) end, |
hidden = NoDualSpec, |
} |
options.dualProfile = { |
name = L_DUAL_PROFILE, |
desc = L_DUAL_PROFILE_DESC, |
type = 'select', |
order = 40.3, |
get = function(info) return info.handler.db:GetDualSpecProfile() end, |
set = function(info, value) info.handler.db:SetDualSpecProfile(value) end, |
values = "ListProfiles", |
arg = "common", |
hidden = NoDualSpec, |
disabled = function(info) return not info.handler.db:IsDualSpecEnabled() end, |
} |
--- Embed dual spec options into an existing AceDBOptions-3.0 option table. |
-- @name LibDualSpec:EnhanceOptions |
-- @param optionTable (table) The option table returned by AceDBOptions-3.0. |
-- @param target (table) The AceDB-3.0 the options operate on. |
function lib:EnhanceOptions(optionTable, target) |
AceDBOptions3 = AceDBOptions3 or LibStub('AceDBOptions-3.0', true) |
if type(optionTable) ~= "table" then |
error("Usage: LibDualSpec:EnhanceOptions(optionTable, target): optionTable should be a table.", 2) |
elseif type(target) ~= "table" then |
error("Usage: LibDualSpec:EnhanceOptions(optionTable, target): target should be a table.", 2) |
elseif not (AceDBOptions3 and AceDBOptions3.optionTables[target]) then |
error("Usage: LibDualSpec:EnhanceOptions(optionTable, target): optionTable is not an AceDBOptions-3.0 table.", 2) |
elseif optionTable.handler.db ~= target then |
error("Usage: LibDualSpec:EnhanceOptions(optionTable, target): optionTable must be the option table of target.", 2) |
elseif not registry[target] then |
error("Usage: LibDualSpec:EnhanceOptions(optionTable, target): EnhanceDatabase should be called before EnhanceOptions(optionTable, target).", 2) |
elseif optionTable.plugins and optionTable.plugins[MAJOR] then |
return |
end |
if not optionTable.plugins then |
optionTable.plugins = {} |
end |
optionTable.plugins[MAJOR] = options |
end |
-- ---------------------------------------------------------------------------- |
-- Inspection |
-- ---------------------------------------------------------------------------- |
local function iterator(registry, key) |
local data |
key, data = next(registry, key) |
if key then |
return key, data.name |
end |
end |
--- Iterate through enhanced AceDB3.0 instances. |
-- The iterator returns (instance, name) pairs where instance and name are the |
-- arguments that were provided to lib:EnhanceDatabase. |
-- @name LibDualSpec:IterateDatabases |
-- @return Values to be used in a for .. in .. do statement. |
function lib:IterateDatabases() |
return iterator, lib.registry |
end |
-- ---------------------------------------------------------------------------- |
-- Switching logic |
-- ---------------------------------------------------------------------------- |
lib.eventFrame:RegisterEvent('PLAYER_TALENT_UPDATE') |
lib.eventFrame:SetScript('OnEvent', function() |
lib.talentsLoaded = true |
local newTalentGroup = GetActiveTalentGroup() |
if lib.talentGroup ~= newTalentGroup then |
lib.talentGroup = newTalentGroup |
for target in pairs(registry) do |
target:CheckDualSpecState() |
end |
end |
end) |
local AceGUI = LibStub("AceGUI-3.0") |
do |
local Type = "Spell_EditBox" |
local Version = 3 |
-- I know theres a better way of doing this than this, but not sure for the time being, works fine though! |
local function Constructor() |
return AceGUI:Create("Predictor_Base") |
end |
AceGUI:RegisterWidgetType(Type, Constructor, Version) |
end |
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/"> |
<Script file="SpellLoader.lua"/> |
<Script file="EditBox-Core.lua"/> |
<Script file="Spells-EditBox.lua"/> |
<Script file="Casts-EditBox.lua"/> |
<Script file="Auras-EditBox.lua"/> |
<Script file="Player-EditBox.lua"/> |
</Ui> |
-- Based off of AceGUI-3.0 EditBox |
local AceGUI = LibStub("AceGUI-3.0") |
do |
local Type = "Spell_EditBox" |
local Version = 2 |
local PREDICTION_ROWS = 10 |
local totalSpellsLoaded, spellLoader = 0 |
local spells, indexedSpells, visiblePredicters = {}, {}, {} |
-- Defined blew |
local searchSpells |
-- Spells have to gradually be loaded in to prevent the client from lagging, this starts as soon as one widget is shown |
-- as of 3.1 the spellID goes up to ~66,000 which means it'll take around 5 - 10 seconds for it to load them all |
-- Given users have to actually move the mouse, type what they want etc |
-- it should result in them not noticing it does not have all the spell data yet |
local function startLoading() |
if( spellLoader ) then return end |
spellLoader = CreateFrame("Frame") |
spellLoader.timeElapsed = 0 |
spellLoader.totalInvalid = 0 |
spellLoader.index = 0 |
spellLoader:SetScript("OnUpdate", function(self, elapsed) |
self.timeElapsed = self.timeElapsed + elapsed |
if( self.timeElapsed < 0.10 ) then return end |
self.timeElapsed = self.timeElapsed - 0.10 |
-- Too many invalid spells found will assume we found all there is that we can |
if( self.totalInvalid >= 5000 ) then |
self:Hide() |
return |
end |
-- Load as many spells in |
local spellsLoaded = totalSpellsLoaded |
for i=spellLoader.index + 1, spellLoader.index + 500 do |
local name, _, icon = GetSpellInfo(i) |
-- The majority of spells that use the engineer gear icon are actually invalid spells that we can easily ignore |
-- since there are ~12000 not counting duplicate names that use this icon it's worthwhile to filter out these spells |
self.totalInvalid = self.totalInvalid + 1 |
if( name and icon ~= "Interface\\Icons\\Trade_Engineering" ) then |
name = string.lower(name) |
if( not spells[name] ) then |
spells[string.lower(name)] = i |
table.insert(indexedSpells, name) |
totalSpellsLoaded = totalSpellsLoaded + 1 |
self.totalInvalid = 0 |
end |
end |
end |
-- Every ~1 second it will update any visible predicters to make up for the fact that the data is delay loaded |
if( spellLoader.index % 5000 == 0 ) then |
for predicter in pairs(visiblePredicters) do |
searchSpells(predicter, predicter.lastQuery) |
end |
end |
-- Increment and do it all over! |
spellLoader.index = spellLoader.index + 500 |
end) |
end |
-- Search for spells quickly |
searchSpells = function(self, query) |
for _, button in pairs(self.buttons) do button:Hide() end |
local usedButtons = 0 |
for i=1, totalSpellsLoaded do |
local name = indexedSpells[i] |
if( string.match(name, query) ) then |
usedButtons = usedButtons + 1 |
local spellName, _, spellIcon = GetSpellInfo(spells[name]) |
local button = self.buttons[usedButtons] |
button.spellID = spells[name] |
button:SetFormattedText("|T%s:20:20:2:11|t %s", spellIcon, spellName) |
button:Show() |
if( usedButtons ~= self.selectedButton ) then |
button:UnlockHighlight() |
if( GameTooltip:IsOwned(button) ) then |
GameTooltip:Hide() |
end |
end |
-- Ran out of text to suggest :< |
if( usedButtons >= PREDICTION_ROWS ) then break end |
end |
end |
if( usedButtons > 0 ) then |
self:SetHeight(15 + usedButtons * 17) |
self:Show() |
else |
self:Hide() |
end |
self.lastQuery = query |
self.usedButtons = usedButtons |
end |
local function OnAcquire(self) |
self:SetHeight(26) |
self:SetWidth(200) |
self:SetDisabled(false) |
self:SetLabel() |
self.showbutton = true |
end |
local function OnRelease(self) |
self.frame:ClearAllPoints() |
self.frame:Hide() |
self.predictFrame:Hide() |
self:SetDisabled(false) |
end |
local function Control_OnEnter(this) |
this.obj:Fire("OnEnter") |
end |
local function Control_OnLeave(this) |
this.obj:Fire("OnLeave") |
end |
local function EditBox_OnEscapePressed(this) |
this:ClearFocus() |
end |
local function ShowButton(self) |
if( self.lasttext ~= "" ) then |
self.editbox.predictFrame.selectedButton = nil |
searchSpells(self.editbox.predictFrame, "^" .. string.lower(self.lasttext)) |
else |
self.editbox.predictFrame:Hide() |
end |
if( self.showbutton ) then |
self.button:Show() |
self.editbox:SetTextInsets(0,20,3,3) |
end |
end |
local function HideButton(self) |
self.button:Hide() |
self.editbox:SetTextInsets(0,0,3,3) |
self.editbox.predictFrame:Hide() |
end |
local function EditBox_OnEnterPressed(this) |
if( this.predictFrame.selectedButton ) then |
this.predictFrame.buttons[this.predictFrame.selectedButton]:Click() |
this.predictFrame.selectedButton = nil |
return |
end |
local self = this.obj |
local value = this:GetText() |
local cancel = self:Fire("OnEnterPressed", value) |
if( not cancel ) then |
HideButton(self) |
end |
-- Reactivate the cursor, odds are if you're adding auras you're adding multiple auras |
self.editbox:SetFocus() |
end |
local function Button_OnClick(this) |
local editbox = this.obj.editbox |
editbox:ClearFocus() |
EditBox_OnEnterPressed(editbox) |
end |
local function Predicter_OnHide(self) |
-- Allow users to use arrows to go back and forth again without the fix |
self.editbox:SetAltArrowKeyMode(false) |
visiblePredicters[self] = nil |
ClearOverrideBindings(self) |
end |
local function Predicter_OnShow(self) |
-- I'm pretty sure this is completely against what you are supposed to actually do :> |
visiblePredicters[self] = true |
-- User doesn't need arrow keys, and by doing this the override binding for up/down arrows will work properly |
self.editbox:SetAltArrowKeyMode(true) |
SetOverrideBindingClick(self, true, "DOWN", self:GetName(), 1) |
SetOverrideBindingClick(self, true, "UP", self:GetName(), -1) |
SetOverrideBindingClick(self, true, "LEFT", self:GetName(), "LEFT") |
SetOverrideBindingClick(self, true, "RIGHT", self:GetName(), "RIGHT") |
end |
-- When using SetAltArrowKeyMode the ability to move the cursor with left and right is disabled, this reenables that |
-- since the cursor position automatically can't go below 0, this is a quick and easy fix |
local function EditBox_FixCursorPosition(self, direction) |
self:SetCursorPosition(self:GetCursorPosition() + (direction == "RIGHT" and 1 or -1)) |
end |
local function EditBox_OnReceiveDrag(this) |
local self = this.obj |
local type, id, info = GetCursorInfo() |
if( type == "spell" ) then |
local name = GetSpellName(id, info) |
self:SetText(name) |
self:Fire("OnEnterPressed" ,name) |
ClearCursor() |
end |
HideButton(self) |
AceGUI:ClearFocus() |
end |
local function EditBox_OnTextChanged(this) |
local self = this.obj |
local value = this:GetText() |
if( value ~= self.lasttext ) then |
self:Fire("OnTextChanged", value) |
self.lasttext = value |
ShowButton(self) |
end |
end |
local function EditBox_OnEditFocusLost(self) |
Predicter_OnHide(self.predictFrame) |
end |
local function EditBox_OnEditFocusGained(self) |
if( self.predictFrame:IsVisible() ) then |
Predicter_OnShow(self.predictFrame) |
end |
end |
local function SetDisabled(self, disabled) |
self.disabled = disabled |
if( disabled ) then |
self.editbox:EnableMouse(false) |
self.editbox:ClearFocus() |
self.editbox:SetTextColor(0.5, 0.5, 0.5) |
self.label:SetTextColor(0.5, 0.5, 0.5) |
else |
self.editbox:EnableMouse(true) |
self.editbox:SetTextColor(1, 1, 1) |
self.label:SetTextColor(1, 0.82, 0) |
end |
end |
local function SetText(self, text, cursor) |
self.lasttext = text or "" |
self.editbox:SetText(self.lasttext) |
self.editbox:SetCursorPosition(cursor or 0) |
HideButton(self) |
end |
local function SetLabel(self, text) |
if( text and text ~= "" ) then |
self.label:SetText(text) |
self.label:Show() |
self.editbox:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 7, -18) |
self:SetHeight(44) |
self.alignoffset = 30 |
else |
self.label:SetText("") |
self.label:Hide() |
self.editbox:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 7, 0) |
self:SetHeight(26) |
self.alignoffset = 12 |
end |
end |
local function Predicter_OnMouseDown(self, direction) |
if( direction == "LEFT" or direction == "RIGHT" ) then |
EditBox_FixCursorPosition(self.editbox, direction) |
return |
end |
self.selectedButton = (self.selectedButton or 0) + direction |
if( self.selectedButton > self.usedButtons ) then |
self.selectedButton = 1 |
elseif( self.selectedButton <= 0 ) then |
self.selectedButton = self.usedButtons |
end |
for i=1, self.usedButtons do |
local button = self.buttons[i] |
if( i == self.selectedButton ) then |
button:LockHighlight() |
GameTooltip:SetOwner(button, "ANCHOR_BOTTOMRIGHT") |
GameTooltip:SetHyperlink("spell:" .. button.spellID) |
else |
button:UnlockHighlight() |
if( GameTooltip:IsOwned(button) ) then |
GameTooltip:Hide() |
end |
end |
end |
end |
local function Spell_OnClick(self) |
local name = GetSpellInfo(self.spellID) |
SetText(self.parent.obj, name, string.len(name)) |
self.parent.obj:Fire("OnEnterPressed", name) |
end |
local function Spell_OnEnter(self) |
self.parent.selectedButton = nil |
self:LockHighlight() |
GameTooltip:SetOwner(self, "ANCHOR_BOTTOMRIGHT") |
GameTooltip:SetHyperlink("spell:" .. self.spellID) |
end |
local function Spell_OnLeave(self) |
self:UnlockHighlight() |
GameTooltip:Hide() |
end |
local predicterBackdrop = { |
bgFile = "Interface\\ChatFrame\\ChatFrameBackground", |
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", |
edgeSize = 26, |
insets = {left = 9, right = 9, top = 9, bottom = 9}, |
} |
local function Constructor() |
local num = AceGUI:GetNextWidgetNum(Type) |
local frame = CreateFrame("Frame", nil, UIParent) |
local editbox = CreateFrame("EditBox", "AceGUI30SpellEditBox" .. num, frame, "InputBoxTemplate") |
-- Don't feel like looking up the specific callbacks for when a widget resizes, so going to be creative with SetPoint instead! |
local predictFrame = CreateFrame("Frame", "AceGUI30SpellEditBox" .. num .. "Predicter", UIParent) |
predictFrame:SetBackdrop(predicterBackdrop) |
predictFrame:SetBackdropColor(0, 0, 0, 0.85) |
predictFrame:SetWidth(1) |
predictFrame:SetHeight(150) |
predictFrame:SetPoint("TOPLEFT", editbox, "BOTTOMLEFT", -6, 0) |
predictFrame:SetPoint("TOPRIGHT", editbox, "BOTTOMRIGHT", 0, 0) |
predictFrame:SetFrameStrata("TOOLTIP") |
predictFrame:Hide() |
predictFrame.buttons = {} |
for i=1, PREDICTION_ROWS do |
local button = CreateFrame("Button", nil, predictFrame) |
button:SetHeight(17) |
button:SetWidth(1) |
button:SetPushedTextOffset(-2, 0) |
button:SetScript("OnClick", Spell_OnClick) |
button:SetScript("OnEnter", Spell_OnEnter) |
button:SetScript("OnLeave", Spell_OnLeave) |
button.parent = predictFrame |
button.editbox = editbox |
button:Hide() |
if( i > 1 ) then |
button:SetPoint("TOPLEFT", predictFrame.buttons[i - 1], "BOTTOMLEFT", 0, 0) |
button:SetPoint("TOPRIGHT", predictFrame.buttons[i - 1], "BOTTOMRIGHT", 0, 0) |
else |
button:SetPoint("TOPLEFT", predictFrame, 8, -8) |
button:SetPoint("TOPRIGHT", predictFrame, -7, 0) |
end |
-- Create the actual text |
local text = button:CreateFontString(nil, "ARTWORK", "GameFontNormal") |
text:SetHeight(1) |
text:SetWidth(1) |
text:SetJustifyH("LEFT") |
text:SetAllPoints(button) |
button:SetFontString(text) |
-- Setup the highlighting |
local texture = button:CreateTexture(nil, "ARTWORK") |
texture:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight") |
texture:ClearAllPoints() |
texture:SetPoint("TOPLEFT", button, 0, -2) |
texture:SetPoint("BOTTOMRIGHT", button, 5, 2) |
texture:SetAlpha(0.70) |
button:SetHighlightTexture(texture) |
button:SetHighlightFontObject(GameFontHighlight) |
button:SetNormalFontObject(GameFontNormal) |
table.insert(predictFrame.buttons, button) |
end |
local self = {} |
self.type = Type |
self.num = num |
self.OnRelease = OnRelease |
self.OnAcquire = OnAcquire |
self.SetDisabled = SetDisabled |
self.SetText = SetText |
self.SetLabel = SetLabel |
frame.obj = self |
self.frame = frame |
editbox.obj = self |
editbox.predictFrame = predictFrame |
self.editbox = editbox |
self.predictFrame = predictFrame |
predictFrame.editbox = editbox |
predictFrame.obj = self |
self.alignoffset = 30 |
frame:SetHeight(44) |
frame:SetWidth(200) |
-- Despite the fact that wowprogramming/wowwiki say EditBoxes have OnKeyUp/OnKeyDown thats not actually true |
-- so doing some trickery with bindings and such to make navigation work |
predictFrame:SetScript("OnMouseDown", Predicter_OnMouseDown) |
predictFrame:SetScript("OnHide", Predicter_OnHide) |
predictFrame:SetScript("OnShow", Predicter_OnShow) |
editbox:SetScript("OnShow", startLoading) |
editbox:SetScript("OnEnter", Control_OnEnter) |
editbox:SetScript("OnLeave", Control_OnLeave) |
editbox:SetAutoFocus(false) |
editbox:SetFontObject(ChatFontNormal) |
editbox:SetScript("OnEscapePressed", EditBox_OnEscapePressed) |
editbox:SetScript("OnEnterPressed", EditBox_OnEnterPressed) |
editbox:SetScript("OnTextChanged", EditBox_OnTextChanged) |
editbox:SetScript("OnReceiveDrag", EditBox_OnReceiveDrag) |
editbox:SetScript("OnMouseDown", EditBox_OnReceiveDrag) |
editbox:SetScript("OnEditFocusGained", EditBox_OnEditFocusGained) |
editbox:SetScript("OnEditFocusLost", EditBox_OnEditFocusLost) |
editbox:SetTextInsets(0, 0, 3, 3) |
editbox:SetMaxLetters(256) |
editbox:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 6, 0) |
editbox:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 0, 0) |
editbox:SetHeight(19) |
local label = frame:CreateFontString(nil,"OVERLAY","GameFontNormalSmall") |
label:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, -2) |
label:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, -2) |
label:SetJustifyH("LEFT") |
label:SetHeight(18) |
self.label = label |
local button = CreateFrame("Button",nil,editbox,"UIPanelButtonTemplate") |
button:SetWidth(40) |
button:SetHeight(20) |
button:SetPoint("RIGHT", editbox, "RIGHT", -2, 0) |
button:SetText(OKAY) |
button:SetScript("OnClick", Button_OnClick) |
button:Hide() |
self.button = button |
button.obj = self |
AceGUI:RegisterAsWidget(self) |
return self |
end |
AceGUI:RegisterWidgetType(Type, Constructor, Version) |
end |
-- This is basically the main portion of the predictor, the other files handle |
local AceGUI = LibStub("AceGUI-3.0") |
do |
local Type = "Predictor_Base" |
local Version = 1 |
local PREDICTOR_ROWS = 10 |
local SpellData = LibStub("AceGUI-3.0-SpellLoader") |
local tooltip |
local alreadyAdded = {} |
local predictorBackdrop = { |
bgFile = "Interface\\ChatFrame\\ChatFrameBackground", |
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", |
edgeSize = 26, |
insets = {left = 9, right = 9, top = 9, bottom = 9}, |
} |
local function OnAcquire(self) |
self:SetHeight(26) |
self:SetWidth(200) |
self:SetDisabled(false) |
self:SetLabel() |
self.showButton = true |
SpellData:RegisterPredictor(self.predictFrame) |
SpellData:StartLoading() |
end |
local function OnRelease(self) |
self.frame:ClearAllPoints() |
self.frame:Hide() |
self.predictFrame:Hide() |
self.spellFilter = nil |
self:SetDisabled(false) |
SpellData:UnregisterPredictor(self.predictFrame) |
end |
local function Control_OnEnter(this) |
this.obj:Fire("OnEnter") |
end |
local function Control_OnLeave(this) |
this.obj:Fire("OnLeave") |
end |
local function Predictor_Query(self) |
for _, button in pairs(self.buttons) do button:Hide() end |
for k in pairs(alreadyAdded) do alreadyAdded[k] = nil end |
local query = "^" .. string.lower(self.obj.editBox:GetText()) |
local activeButtons = 0 |
for spellID, name in pairs(SpellData.spellList) do |
if( not alreadyAdded[name] and string.match(name, query) and ( not self.obj.spellFilter or self.obj.spellFilter(self.obj, spellID) ) ) then |
activeButtons = activeButtons + 1 |
local button = self.buttons[activeButtons] |
local spellName, spellRank, spellIcon = GetSpellInfo(spellID) |
if( self.obj.useRanks and spellRank and spellRank ~= "" ) then |
button:SetFormattedText("|T%s:20:20:2:11|t %s (%s)", spellIcon, spellName, spellRank) |
else |
button:SetFormattedText("|T%s:20:20:2:11|t %s", spellIcon, spellName) |
end |
if( not self.obj.useRanks ) then |
alreadyAdded[name] = true |
end |
button.spellID = spellID |
button:Show() |
-- Highlight if needed |
if( activeButtons ~= self.selectedButton ) then |
button:UnlockHighlight() |
if( GameTooltip:IsOwned(button) ) then |
GameTooltip:Hide() |
end |
end |
-- Ran out of text to suggest :< |
if( activeButtons >= PREDICTOR_ROWS ) then break end |
end |
end |
if( activeButtons > 0 ) then |
self:SetHeight(15 + activeButtons * 17) |
self:Show() |
else |
self:Hide() |
end |
self.activeButtons = activeButtons |
end |
local function ShowButton(self) |
if( self.lastText ~= "" ) then |
self.predictFrame.selectedButton = nil |
Predictor_Query(self.predictFrame) |
else |
self.predictFrame:Hide() |
end |
if( self.showButton ) then |
self.button:Show() |
self.editBox:SetTextInsets(0, 20, 3, 3) |
end |
end |
local function HideButton(self) |
self.button:Hide() |
self.editBox:SetTextInsets(0, 0, 3, 3) |
self.predictFrame.selectedButton = nil |
self.predictFrame:Hide() |
end |
local function Predictor_OnHide(self) |
-- Allow users to use arrows to go back and forth again without the fix |
self.obj.editBox:SetAltArrowKeyMode(false) |
-- Make sure the tooltip isn't kept open if one of the buttons was using it |
for _, button in pairs(self.buttons) do |
if( GameTooltip:IsOwned(button) ) then |
GameTooltip:Hide() |
end |
end |
-- Reset all bindings set on this predictor |
ClearOverrideBindings(self) |
end |
local function Predictor_OnShow(self) |
-- If the user is using an edit box in a configuration, they will live without arrow keys while the predictor |
-- is opened, this also is the only way of getting up/down arrow for browsing the predictor to work. |
self.obj.editBox:SetAltArrowKeyMode(true) |
local name = self:GetName() |
SetOverrideBindingClick(self, true, "DOWN", name, 1) |
SetOverrideBindingClick(self, true, "UP", name, -1) |
SetOverrideBindingClick(self, true, "LEFT", name, "LEFT") |
SetOverrideBindingClick(self, true, "RIGHT", name, "RIGHT") |
end |
local function EditBox_OnEnterPressed(this) |
local self = this.obj |
-- Something is selected in the predictor, use that value instead of whatever is in the input box |
if( self.predictFrame.selectedButton ) then |
self.predictFrame.buttons[self.predictFrame.selectedButton]:Click() |
return |
end |
local cancel = self:Fire("OnEnterPressed", this:GetText()) |
if( not cancel ) then |
HideButton(self) |
end |
-- Reactive the cursor, odds are if someone is adding spells they are adding more than one |
-- and if they aren't, it can't hurt anyway. |
self.editBox:SetFocus() |
end |
local function EditBox_OnEscapePressed(this) |
this:ClearFocus() |
end |
-- When using SetAltArrowKeyMode the ability to move the cursor with left and right arrows is disabled |
-- this reenables that so the user doesn't notice anything wrong |
local function EditBox_FixCursorPosition(self, direction) |
self:SetCursorPosition(self:GetCursorPosition() + (direction == "RIGHT" and 1 or -1)) |
end |
local function EditBox_OnReceiveDrag(this) |
local self = this.obj |
local type, id, info = GetCursorInfo() |
if( type == "spell" ) then |
local name, rank = GetSpellName(id, info) |
if( self.useRanks and rank and rank ~= "" ) then |
name = string.format("%s (%s)", name, rank) |
end |
self:SetText(name) |
self:Fire("OnEnterPressed", name) |
ClearCursor() |
end |
HideButton(self) |
AceGUI:ClearFocus() |
end |
local function EditBox_OnTextChanged(this) |
local self = this.obj |
local value = this:GetText() |
if( value ~= self.lastText ) then |
self:Fire("OnTextChanged", value) |
self.lastText = value |
ShowButton(self) |
end |
end |
local function EditBox_OnEditFocusLost(self) |
Predictor_OnHide(self.obj.predictFrame) |
end |
local function EditBox_OnEditFocusGained(self) |
if( self.obj.predictFrame:IsVisible() ) then |
Predictor_OnShow(self.obj.predictFrame) |
end |
end |
local function Button_OnClick(this) |
EditBox_OnEnterPressed(this.obj.editBox) |
end |
-- API calls |
local function SetUseRanks(self, enabled) |
self.useRanks = enabled |
end |
local function SetDisabled(self, disabled) |
self.disabled = disabled |
if( disabled ) then |
self.editBox:EnableMouse(false) |
self.editBox:ClearFocus() |
self.editBox:SetTextColor(0.5, 0.5, 0.5) |
self.label:SetTextColor(0.5, 0.5, 0.5) |
else |
self.editBox:EnableMouse(true) |
self.editBox:SetTextColor(1, 1, 1) |
self.label:SetTextColor(1, 0.82, 0) |
end |
end |
local function SetText(self, text, cursor) |
self.lastText = text or "" |
self.editBox:SetText(self.lastText) |
self.editBox:SetCursorPosition(cursor or 0) |
HideButton(self) |
end |
local function SetLabel(self, text) |
if( text and text ~= "" ) then |
self.label:SetText(text) |
self.label:Show() |
self.editBox:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 7, -18) |
self:SetHeight(44) |
self.alignoffset = 30 |
else |
self.label:SetText("") |
self.label:Hide() |
self.editBox:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 7, 0) |
self:SetHeight(26) |
self.alignoffset = 12 |
end |
end |
local function Predictor_OnMouseDown(self, direction) |
-- Fix the cursor positioning if left or right arrow key was used |
if( direction == "LEFT" or direction == "RIGHT" ) then |
EditBox_FixCursorPosition(self.editBox, direction) |
return |
end |
self.selectedButton = (self.selectedButton or 0) + direction |
if( self.selectedButton > self.activeButtons ) then |
self.selectedButton = 1 |
elseif( self.selectedButton <= 0 ) then |
self.selectedButton = self.activeButtons |
end |
-- Figure out what to highlight and show the spell tooltip while we're at it |
for i=1, self.activeButtons do |
local button = self.buttons[i] |
if( i == self.selectedButton ) then |
button:LockHighlight() |
GameTooltip:SetOwner(button, "ANCHOR_BOTTOMRIGHT", 3) |
GameTooltip:SetHyperlink("spell:" .. button.spellID) |
else |
button:UnlockHighlight() |
if( GameTooltip:IsOwned(button) ) then |
GameTooltip:Hide() |
end |
end |
end |
end |
local function Spell_OnClick(self) |
local name, rank = GetSpellInfo(self.spellID) |
if( self.useRanks and rank and rank ~= "" ) then |
name = string.format("%s (%s)", name, rank) |
end |
SetText(self.parent.obj, name, string.len(name)) |
self.parent.selectedButton = nil |
self.parent.obj:Fire("OnEnterPressed", name) |
end |
local function Spell_OnEnter(self) |
self.parent.selectedButton = nil |
self:LockHighlight() |
GameTooltip:SetOwner(self, "ANCHOR_BOTTOMRIGHT", 3) |
GameTooltip:SetHyperlink("spell:" .. self.spellID) |
end |
local function Spell_OnLeave(self) |
self:UnlockHighlight() |
GameTooltip:Hide() |
end |
local function Constructor() |
local num = AceGUI:GetNextWidgetNum(Type) |
local frame = CreateFrame("Frame", nil, UIParent) |
local editBox = CreateFrame("EditBox", "AceGUI30SpellEditBox" .. num, frame, "InputBoxTemplate") |
-- Don't feel like looking up the specific callbacks for when a widget resizes, so going to be creative with SetPoint instead! |
local predictFrame = CreateFrame("Frame", "AceGUI30SpellEditBox" .. num .. "Predictor", UIParent) |
predictFrame:SetBackdrop(predictorBackdrop) |
predictFrame:SetBackdropColor(0, 0, 0, 0.85) |
predictFrame:SetWidth(1) |
predictFrame:SetHeight(150) |
predictFrame:SetPoint("TOPLEFT", editBox, "BOTTOMLEFT", -6, 0) |
predictFrame:SetPoint("TOPRIGHT", editBox, "BOTTOMRIGHT", 0, 0) |
predictFrame:SetFrameStrata("TOOLTIP") |
predictFrame.buttons = {} |
predictFrame.Query = Predictor_Query |
predictFrame:Hide() |
-- Create the mass of predictor rows |
for i=1, PREDICTOR_ROWS do |
local button = CreateFrame("Button", nil, predictFrame) |
button:SetHeight(17) |
button:SetWidth(1) |
button:SetPushedTextOffset(-2, 0) |
button:SetScript("OnClick", Spell_OnClick) |
button:SetScript("OnEnter", Spell_OnEnter) |
button:SetScript("OnLeave", Spell_OnLeave) |
button.parent = predictFrame |
button.editBox = editBox |
button:Hide() |
if( i > 1 ) then |
button:SetPoint("TOPLEFT", predictFrame.buttons[i - 1], "BOTTOMLEFT", 0, 0) |
button:SetPoint("TOPRIGHT", predictFrame.buttons[i - 1], "BOTTOMRIGHT", 0, 0) |
else |
button:SetPoint("TOPLEFT", predictFrame, 8, -8) |
button:SetPoint("TOPRIGHT", predictFrame, -7, 0) |
end |
-- Create the actual text |
local text = button:CreateFontString(nil, "ARTWORK", "GameFontNormal") |
text:SetHeight(1) |
text:SetWidth(1) |
text:SetJustifyH("LEFT") |
text:SetAllPoints(button) |
button:SetFontString(text) |
-- Setup the highlighting |
local texture = button:CreateTexture(nil, "ARTWORK") |
texture:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight") |
texture:ClearAllPoints() |
texture:SetPoint("TOPLEFT", button, 0, -2) |
texture:SetPoint("BOTTOMRIGHT", button, 5, 2) |
texture:SetAlpha(0.70) |
button:SetHighlightTexture(texture) |
button:SetHighlightFontObject(GameFontHighlight) |
button:SetNormalFontObject(GameFontNormal) |
table.insert(predictFrame.buttons, button) |
end |
-- Set the main info things for this thingy |
local self = {} |
self.type = Type |
self.num = num |
self.OnRelease = OnRelease |
self.OnAcquire = OnAcquire |
self.SetDisabled = SetDisabled |
self.SetText = SetText |
self.SetLabel = SetLabel |
self.frame = frame |
self.predictFrame = predictFrame |
self.editBox = editBox |
self.alignoffset = 30 |
frame:SetHeight(44) |
frame:SetWidth(200) |
frame.obj = self |
editBox.obj = self |
predictFrame.obj = self |
-- Purely meant for a single tooltip for doing scanning |
if( not tooltip ) then |
tooltip = CreateFrame("GameTooltip") |
tooltip:SetOwner(UIParent, "ANCHOR_NONE") |
for i=1, 6 do |
tooltip["TextLeft" .. i] = tooltip:CreateFontString() |
tooltip["TextRight" .. i] = tooltip:CreateFontString() |
tooltip:AddFontStrings(tooltip["TextLeft" .. i], tooltip["TextRight" .. i]) |
end |
end |
self.tooltip = tooltip |
-- EditBoxes override the OnKeyUp/OnKeyDown events so that they can function, meaning in order to make up and down |
-- arrow navigation of the menu work, I have to do some trickery with temporary bindings. |
predictFrame:SetScript("OnMouseDown", Predictor_OnMouseDown) |
predictFrame:SetScript("OnHide", Predictor_OnHide) |
predictFrame:SetScript("OnShow", Predictor_OnShow) |
editBox:SetScript("OnEnter", Control_OnEnter) |
editBox:SetScript("OnLeave", Control_OnLeave) |
editBox:SetAutoFocus(false) |
editBox:SetFontObject(ChatFontNormal) |
editBox:SetScript("OnEscapePressed", EditBox_OnEscapePressed) |
editBox:SetScript("OnEnterPressed", EditBox_OnEnterPressed) |
editBox:SetScript("OnTextChanged", EditBox_OnTextChanged) |
editBox:SetScript("OnReceiveDrag", EditBox_OnReceiveDrag) |
editBox:SetScript("OnMouseDown", EditBox_OnReceiveDrag) |
editBox:SetScript("OnEditFocusGained", EditBox_OnEditFocusGained) |
editBox:SetScript("OnEditFocusLost", EditBox_OnEditFocusLost) |
editBox:SetTextInsets(0, 0, 3, 3) |
editBox:SetMaxLetters(256) |
editBox:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 6, 0) |
editBox:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 0, 0) |
editBox:SetHeight(19) |
local label = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") |
label:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, -2) |
label:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, -2) |
label:SetJustifyH("LEFT") |
label:SetHeight(18) |
self.label = label |
local button = CreateFrame("Button", nil, editBox, "UIPanelButtonTemplate") |
button:SetPoint("RIGHT", editBox, "RIGHT", -2, 0) |
button:SetScript("OnClick", Button_OnClick) |
button:SetWidth(40) |
button:SetHeight(20) |
button:SetText(OKAY) |
button:Hide() |
self.button = button |
button.obj = self |
AceGUI:RegisterAsWidget(self) |
return self |
end |
AceGUI:RegisterWidgetType(Type, Constructor, Version) |
end |
local major = "AceGUI-3.0-SpellLoader" |
local minor = 1 |
local SpellLoader = LibStub:NewLibrary(major, minor) |
if( not SpellLoader ) then return end |
SpellLoader.predictors = SpellLoader.predictors or {} |
SpellLoader.spellList = SpellLoader.spellList or {} |
SpellLoader.spellsLoaded = SpellLoader.spellsLoaded or 0 |
local SPELLS_PER_RUN = 500 |
local TIMER_THROTTLE = 0.10 |
local spells, predictors = SpellLoader.spellList, SpellLoader.predictors |
function SpellLoader:RegisterPredictor(frame) |
self.predictors[frame] = true |
end |
function SpellLoader:UnregisterPredictor(frame) |
self.predictors[frame] = nil |
end |
function SpellLoader:StartLoading() |
if( self.loader ) then return end |
local blacklist = { |
["Interface\\Icons\\Trade_Alchemy"] = true, |
["Interface\\Icons\\Trade_BlackSmithing"] = true, |
["Interface\\Icons\\Trade_BrewPoison"] = true, |
["Interface\\Icons\\Trade_Engineering"] = true, |
["Interface\\Icons\\Trade_Engraving"] = true, |
["Interface\\Icons\\Trade_Fishing"] = true, |
["Interface\\Icons\\Trade_Herbalism"] = true, |
["Interface\\Icons\\Trade_LeatherWorking"] = true, |
["Interface\\Icons\\Trade_Mining"] = true, |
["Interface\\Icons\\Trade_Tailoring"] = true, |
["Interface\\Icons\\Temp"] = true, |
} |
local timeElapsed, totalInvalid, currentIndex = 0, 0, 0 |
self.loader = CreateFrame("Frame") |
self.loader:SetScript("OnUpdate", function(self, elapsed) |
timeElapsed = timeElapsed + elapsed |
if( timeElapsed < TIMER_THROTTLE ) then return end |
timeElapsed = timeElapsed - TIMER_THROTTLE |
-- 5,000 invalid spells in a row means it's a safe assumption that there are no more spells to query |
if( totalInvalid >= 5000 ) then |
self:Hide() |
return |
end |
-- Load as many spells in |
for spellID=currentIndex + 1, currentIndex + SPELLS_PER_RUN do |
local name, rank, icon = GetSpellInfo(spellID) |
-- Pretty much every profession spell uses Trade_* and 99% of the random spells use the Trade_Engineering icon |
-- we can safely blacklist any of these spells as they are not needed. Can get away with this because things like |
-- Alchemy use two icons, the Trade_* for the actual crafted spell and a different icon for the actual buff |
-- Passive spells have no use as well, since they are well passive and can't actually be used |
if( name and not blacklist[icon] and rank ~= SPELL_PASSIVE ) then |
name = string.lower(name) |
SpellLoader.spellsLoaded = SpellLoader.spellsLoaded + 1 |
spells[spellID] = string.lower(name) |
totalInvalid = 0 |
else |
totalInvalid = totalInvalid + 1 |
end |
end |
-- Every ~1 second it will update any visible predictors to make up for the fact that the data is delay loaded |
if( currentIndex % 5000 == 0 ) then |
for predictor in pairs(predictors) do |
if( predictor:IsVisible() ) then |
predictor:Query() |
end |
end |
end |
-- Increment and do it all over! |
currentIndex = currentIndex + SPELLS_PER_RUN |
end) |
end |
local AceGUI = LibStub("AceGUI-3.0") |
do |
local Type = "Aura_EditBox" |
local Version = 1 |
local filterCache = {} |
-- Tooltip scanning every single spell while it's loading would be not be that fun. Scanning spells as we search them (and get valid results) |
-- is a better solution since the results can be cached too. |
local function spellFilter(self, spellID) |
if( filterCache[spellID] ~= nil ) then return filterCache[spellID] end |
-- Very very few auras are over 100 yard range, and those are generally boss spells should be able to get away with this |
if( select(9, GetSpellInfo(spellID)) > 100 ) then |
filterCache[spellID] = true |
return false |
end |
-- We look for a description tag, 99% of auras have a description tag indicating what they are |
-- so we don't find one, then it's likely a safe assumption that it is not an aura |
self.tooltip:SetHyperlink("spell:" .. spellID) |
for i=1, self.tooltip:NumLines() do |
local text = self.tooltip["TextLeft" .. i] |
if( text ) then |
local r, g, b = text:GetTextColor() |
r = math.floor(r + 0.10) |
g = math.floor(g + 0.10) |
b = math.floor(b + 0.10) |
-- Gold first text, it's a profession link |
if( i == 1 and ( r ~= 1 or g ~= 1 or g ~= 1 ) ) then |
filterCache[spellID] = false |
return false |
-- Gold for anything else and it should be a valid aura |
elseif( r ~= 1 or g ~= 1 or b ~= 1 ) then |
filterCache[spellID] = true |
return true |
end |
end |
end |
filterCache[spellID] = false |
return false |
end |
-- I know theres a better way of doing this than this, but not sure for the time being, works fine though! |
local function Constructor() |
local self = AceGUI:Create("Predictor_Base") |
self.spellFilter = spellFilter |
return self |
end |
AceGUI:RegisterWidgetType(Type, Constructor, Version) |
end |
## Interface: 30200 |
## Title: Lib: AceGUI-3.0-Spell-EditBox |
## Notes: Adds predicted lists for spells while the user types |
## Author: Shadowed |
## X-Category: Library |
## OptionalDeps: Ace3 |
## X-Curse-Packaged-Version: r20090807071246 |
## X-Curse-Project-Name: AceGUI-3.0-Spell-EditBox |
## X-Curse-Project-ID: ace-gui-3-0-spell-editbox |
## X-Curse-Repository-ID: wow/ace-gui-3-0-spell-editbox/mainline |
AceGUI-3.0-Spell-EditBox.xml |
## test.lua |
local AceGUI = LibStub("AceGUI-3.0") |
do |
local Type = "Player_EditBox" |
local Version = 1 |
local playerSpells = {} |
local frame |
local function spellFilter(self, spellID) |
return playerSpells[spellID] |
end |
local function loadPlayerSpells(self) |
table.wipe(playerSpells) |
for tab=2, GetNumSpellTabs() do |
local offset = select(3, GetSpellTabInfo(tab)) |
for i=1, offset do |
self.tooltip:SetSpell(i + offset, tab) |
local spellName, _, spellID = self.tooltip:GetSpell() |
if( spellName ) then |
playerSpells[spellID] = true |
end |
end |
end |
end |
-- I know theres a better way of doing this than this, but not sure for the time being, works fine though! |
local function Constructor() |
local self = AceGUI:Create("Predictor_Base") |
self.spellFilter = spellFilter |
if( not frame ) then |
frame = CreateFrame("Frame") |
frame:RegisterEvent("SPELLS_CHANGED") |
frame:SetScript("OnEvent", loadPlayerSpells) |
frame.tooltip = self.tooltip |
loadPlayerSpells(frame) |
end |
return self |
end |
AceGUI:RegisterWidgetType(Type, Constructor, Version) |
end |
local AceGUI = LibStub("AceGUI-3.0") |
do |
local Type = "Cast_EditBox" |
local Version = 1 |
local filterCache = {} |
local function spellFilter(self, spellID) |
if( filterCache[spellID] ~= nil ) then return filterCache[spellID] end |
-- Scan first line to figure out if it's a profession |
self.tooltip:SetHyperlink("spell:" .. spellID) |
local r, g, b = self.tooltip.TextLeft1:GetTextColor() |
r = math.floor(r + 0.10) |
g = math.floor(g + 0.10) |
b = math.floor(b + 0.10) |
if( r ~= 1 or g ~= 1 or b ~= 1 ) then |
filterCache[spellID] = false |
return false |
end |
-- Hide all spells without a cast time |
filterCache[spellID] = select(7, GetSpellInfo(spellID)) > 0 |
return filterCache[spellID] |
end |
-- I know theres a better way of doing this than this, but not sure for the time being, works fine though! |
local function Constructor() |
local self = AceGUI:Create("Predictor_Base") |
self.spellFilter = spellFilter |
return self |
end |
AceGUI:RegisterWidgetType(Type, Constructor, Version) |
end |
-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info |
-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke |
local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS! |
local LibStub = _G[LIBSTUB_MAJOR] |
if not LibStub or LibStub.minor < LIBSTUB_MINOR then |
LibStub = LibStub or {libs = {}, minors = {} } |
_G[LIBSTUB_MAJOR] = LibStub |
LibStub.minor = LIBSTUB_MINOR |
function LibStub:NewLibrary(major, minor) |
assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)") |
minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.") |
local oldminor = self.minors[major] |
if oldminor and oldminor >= minor then return nil end |
self.minors[major], self.libs[major] = minor, self.libs[major] or {} |
return self.libs[major], oldminor |
end |
function LibStub:GetLibrary(major, silent) |
if not self.libs[major] and not silent then |
error(("Cannot find a library instance of %q."):format(tostring(major)), 2) |
end |
return self.libs[major], self.minors[major] |
end |
function LibStub:IterateLibraries() return pairs(self.libs) end |
setmetatable(LibStub, { __call = LibStub.GetLibrary }) |
end |
-- |
-- ChatThrottleLib by Mikk |
-- |
-- Manages AddOn chat output to keep player from getting kicked off. |
-- |
-- ChatThrottleLib:SendChatMessage/:SendAddonMessage functions that accept |
-- a Priority ("BULK", "NORMAL", "ALERT") as well as prefix for SendChatMessage. |
-- |
-- Priorities get an equal share of available bandwidth when fully loaded. |
-- Communication channels are separated on extension+chattype+destination and |
-- get round-robinned. (Destination only matters for whispers and channels, |
-- obviously) |
-- |
-- Will install hooks for SendChatMessage and SendAddonMessage to measure |
-- bandwidth bypassing the library and use less bandwidth itself. |
-- |
-- |
-- Fully embeddable library. Just copy this file into your addon directory, |
-- add it to the .toc, and it's done. |
-- |
-- Can run as a standalone addon also, but, really, just embed it! :-) |
-- |
local CTL_VERSION = 21 |
local _G = _G |
if _G.ChatThrottleLib then |
if _G.ChatThrottleLib.version >= CTL_VERSION then |
-- There's already a newer (or same) version loaded. Buh-bye. |
return |
elseif not _G.ChatThrottleLib.securelyHooked then |
print("ChatThrottleLib: Warning: There's an ANCIENT ChatThrottleLib.lua (pre-wow 2.0, <v16) in an addon somewhere. Get the addon updated or copy in a newer ChatThrottleLib.lua (>=v16) in it!") |
-- ATTEMPT to unhook; this'll behave badly if someone else has hooked... |
-- ... and if someone has securehooked, they can kiss that goodbye too... >.< |
_G.SendChatMessage = _G.ChatThrottleLib.ORIG_SendChatMessage |
if _G.ChatThrottleLib.ORIG_SendAddonMessage then |
_G.SendAddonMessage = _G.ChatThrottleLib.ORIG_SendAddonMessage |
end |
end |
_G.ChatThrottleLib.ORIG_SendChatMessage = nil |
_G.ChatThrottleLib.ORIG_SendAddonMessage = nil |
end |
if not _G.ChatThrottleLib then |
_G.ChatThrottleLib = {} |
end |
ChatThrottleLib = _G.ChatThrottleLib -- in case some addon does "local ChatThrottleLib" above us and we're copypasted (AceComm-2, sigh) |
local ChatThrottleLib = _G.ChatThrottleLib |
ChatThrottleLib.version = CTL_VERSION |
------------------ TWEAKABLES ----------------- |
ChatThrottleLib.MAX_CPS = 800 -- 2000 seems to be safe if NOTHING ELSE is happening. let's call it 800. |
ChatThrottleLib.MSG_OVERHEAD = 40 -- Guesstimate overhead for sending a message; source+dest+chattype+protocolstuff |
ChatThrottleLib.BURST = 4000 -- WoW's server buffer seems to be about 32KB. 8KB should be safe, but seen disconnects on _some_ servers. Using 4KB now. |
ChatThrottleLib.MIN_FPS = 20 -- Reduce output CPS to half (and don't burst) if FPS drops below this value |
local setmetatable = setmetatable |
local table_remove = table.remove |
local tostring = tostring |
local GetTime = GetTime |
local math_min = math.min |
local math_max = math.max |
local next = next |
local strlen = string.len |
local GetFrameRate = GetFrameRate |
----------------------------------------------------------------------- |
-- Double-linked ring implementation |
local Ring = {} |
local RingMeta = { __index = Ring } |
function Ring:New() |
local ret = {} |
setmetatable(ret, RingMeta) |
return ret |
end |
function Ring:Add(obj) -- Append at the "far end" of the ring (aka just before the current position) |
if self.pos then |
obj.prev = self.pos.prev |
obj.prev.next = obj |
obj.next = self.pos |
obj.next.prev = obj |
else |
obj.next = obj |
obj.prev = obj |
self.pos = obj |
end |
end |
function Ring:Remove(obj) |
obj.next.prev = obj.prev |
obj.prev.next = obj.next |
if self.pos == obj then |
self.pos = obj.next |
if self.pos == obj then |
self.pos = nil |
end |
end |
end |
----------------------------------------------------------------------- |
-- Recycling bin for pipes |
-- A pipe is a plain integer-indexed queue, which also happens to be a ring member |
ChatThrottleLib.PipeBin = nil -- pre-v19, drastically different |
local PipeBin = setmetatable({}, {__mode="k"}) |
local function DelPipe(pipe) |
for i = #pipe, 1, -1 do |
pipe[i] = nil |
end |
pipe.prev = nil |
pipe.next = nil |
PipeBin[pipe] = true |
end |
local function NewPipe() |
local pipe = next(PipeBin) |
if pipe then |
PipeBin[pipe] = nil |
return pipe |
end |
return {} |
end |
----------------------------------------------------------------------- |
-- Recycling bin for messages |
ChatThrottleLib.MsgBin = nil -- pre-v19, drastically different |
local MsgBin = setmetatable({}, {__mode="k"}) |
local function DelMsg(msg) |
msg[1] = nil |
-- there's more parameters, but they're very repetetive so the string pool doesn't suffer really, and it's faster to just not delete them. |
MsgBin[msg] = true |
end |
local function NewMsg() |
local msg = next(MsgBin) |
if msg then |
MsgBin[msg] = nil |
return msg |
end |
return {} |
end |
----------------------------------------------------------------------- |
-- ChatThrottleLib:Init |
-- Initialize queues, set up frame for OnUpdate, etc |
function ChatThrottleLib:Init() |
-- Set up queues |
if not self.Prio then |
self.Prio = {} |
self.Prio["ALERT"] = { ByName = {}, Ring = Ring:New(), avail = 0 } |
self.Prio["NORMAL"] = { ByName = {}, Ring = Ring:New(), avail = 0 } |
self.Prio["BULK"] = { ByName = {}, Ring = Ring:New(), avail = 0 } |
end |
-- v4: total send counters per priority |
for _, Prio in pairs(self.Prio) do |
Prio.nTotalSent = Prio.nTotalSent or 0 |
end |
if not self.avail then |
self.avail = 0 -- v5 |
end |
if not self.nTotalSent then |
self.nTotalSent = 0 -- v5 |
end |
-- Set up a frame to get OnUpdate events |
if not self.Frame then |
self.Frame = CreateFrame("Frame") |
self.Frame:Hide() |
end |
self.Frame:SetScript("OnUpdate", self.OnUpdate) |
self.Frame:SetScript("OnEvent", self.OnEvent) -- v11: Monitor P_E_W so we can throttle hard for a few seconds |
self.Frame:RegisterEvent("PLAYER_ENTERING_WORLD") |
self.OnUpdateDelay = 0 |
self.LastAvailUpdate = GetTime() |
self.HardThrottlingBeginTime = GetTime() -- v11: Throttle hard for a few seconds after startup |
-- Hook SendChatMessage and SendAddonMessage so we can measure unpiped traffic and avoid overloads (v7) |
if not self.securelyHooked then |
-- Use secure hooks as of v16. Old regular hook support yanked out in v21. |
self.securelyHooked = true |
--SendChatMessage |
hooksecurefunc("SendChatMessage", function(...) |
return ChatThrottleLib.Hook_SendChatMessage(...) |
end) |
--SendAddonMessage |
hooksecurefunc("SendAddonMessage", function(...) |
return ChatThrottleLib.Hook_SendAddonMessage(...) |
end) |
end |
self.nBypass = 0 |
end |
----------------------------------------------------------------------- |
-- ChatThrottleLib.Hook_SendChatMessage / .Hook_SendAddonMessage |
local bMyTraffic = false |
function ChatThrottleLib.Hook_SendChatMessage(text, chattype, language, destination, ...) |
if bMyTraffic then |
return |
end |
local self = ChatThrottleLib |
local size = strlen(tostring(text or "")) + strlen(tostring(destination or "")) + self.MSG_OVERHEAD |
self.avail = self.avail - size |
self.nBypass = self.nBypass + size -- just a statistic |
end |
function ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destination, ...) |
if bMyTraffic then |
return |
end |
local self = ChatThrottleLib |
local size = tostring(text or ""):len() + tostring(prefix or ""):len(); |
size = size + tostring(destination or ""):len() + self.MSG_OVERHEAD |
self.avail = self.avail - size |
self.nBypass = self.nBypass + size -- just a statistic |
end |
----------------------------------------------------------------------- |
-- ChatThrottleLib:UpdateAvail |
-- Update self.avail with how much bandwidth is currently available |
function ChatThrottleLib:UpdateAvail() |
local now = GetTime() |
local MAX_CPS = self.MAX_CPS; |
local newavail = MAX_CPS * (now - self.LastAvailUpdate) |
local avail = self.avail |
if now - self.HardThrottlingBeginTime < 5 then |
-- First 5 seconds after startup/zoning: VERY hard clamping to avoid irritating the server rate limiter, it seems very cranky then |
avail = math_min(avail + (newavail*0.1), MAX_CPS*0.5) |
self.bChoking = true |
elseif GetFramerate() < self.MIN_FPS then -- GetFrameRate call takes ~0.002 secs |
avail = math_min(MAX_CPS, avail + newavail*0.5) |
self.bChoking = true -- just a statistic |
else |
avail = math_min(self.BURST, avail + newavail) |
self.bChoking = false |
end |
avail = math_max(avail, 0-(MAX_CPS*2)) -- Can go negative when someone is eating bandwidth past the lib. but we refuse to stay silent for more than 2 seconds; if they can do it, we can. |
self.avail = avail |
self.LastAvailUpdate = now |
return avail |
end |
----------------------------------------------------------------------- |
-- Despooling logic |
function ChatThrottleLib:Despool(Prio) |
local ring = Prio.Ring |
while ring.pos and Prio.avail > ring.pos[1].nSize do |
local msg = table_remove(Prio.Ring.pos, 1) |
if not Prio.Ring.pos[1] then |
local pipe = Prio.Ring.pos |
Prio.Ring:Remove(pipe) |
Prio.ByName[pipe.name] = nil |
DelPipe(pipe) |
else |
Prio.Ring.pos = Prio.Ring.pos.next |
end |
Prio.avail = Prio.avail - msg.nSize |
bMyTraffic = true |
msg.f(unpack(msg, 1, msg.n)) |
bMyTraffic = false |
Prio.nTotalSent = Prio.nTotalSent + msg.nSize |
DelMsg(msg) |
if msg.callbackFn then |
msg.callbackFn (msg.callbackArg) |
end |
end |
end |
function ChatThrottleLib.OnEvent(this,event) |
-- v11: We know that the rate limiter is touchy after login. Assume that it's touchy after zoning, too. |
local self = ChatThrottleLib |
if event == "PLAYER_ENTERING_WORLD" then |
self.HardThrottlingBeginTime = GetTime() -- Throttle hard for a few seconds after zoning |
self.avail = 0 |
end |
end |
function ChatThrottleLib.OnUpdate(this,delay) |
local self = ChatThrottleLib |
self.OnUpdateDelay = self.OnUpdateDelay + delay |
if self.OnUpdateDelay < 0.08 then |
return |
end |
self.OnUpdateDelay = 0 |
self:UpdateAvail() |
if self.avail < 0 then |
return -- argh. some bastard is spewing stuff past the lib. just bail early to save cpu. |
end |
-- See how many of our priorities have queued messages (we only have 3, don't worry about the loop) |
local n = 0 |
for prioname,Prio in pairs(self.Prio) do |
if Prio.Ring.pos or Prio.avail < 0 then |
n = n + 1 |
end |
end |
-- Anything queued still? |
if n<1 then |
-- Nope. Move spillover bandwidth to global availability gauge and clear self.bQueueing |
for prioname, Prio in pairs(self.Prio) do |
self.avail = self.avail + Prio.avail |
Prio.avail = 0 |
end |
self.bQueueing = false |
self.Frame:Hide() |
return |
end |
-- There's stuff queued. Hand out available bandwidth to priorities as needed and despool their queues |
local avail = self.avail/n |
self.avail = 0 |
for prioname, Prio in pairs(self.Prio) do |
if Prio.Ring.pos or Prio.avail < 0 then |
Prio.avail = Prio.avail + avail |
if Prio.Ring.pos and Prio.avail > Prio.Ring.pos[1].nSize then |
self:Despool(Prio) |
-- Note: We might not get here if the user-supplied callback function errors out! Take care! |
end |
end |
end |
end |
----------------------------------------------------------------------- |
-- Spooling logic |
function ChatThrottleLib:Enqueue(prioname, pipename, msg) |
local Prio = self.Prio[prioname] |
local pipe = Prio.ByName[pipename] |
if not pipe then |
self.Frame:Show() |
pipe = NewPipe() |
pipe.name = pipename |
Prio.ByName[pipename] = pipe |
Prio.Ring:Add(pipe) |
end |
pipe[#pipe + 1] = msg |
self.bQueueing = true |
end |
function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, language, destination, queueName, callbackFn, callbackArg) |
if not self or not prio or not prefix or not text or not self.Prio[prio] then |
error('Usage: ChatThrottleLib:SendChatMessage("{BULK||NORMAL||ALERT}", "prefix", "text"[, "chattype"[, "language"[, "destination"]]]', 2) |
end |
if callbackFn and type(callbackFn)~="function" then |
error('ChatThrottleLib:ChatMessage(): callbackFn: expected function, got '..type(callbackFn), 2) |
end |
local nSize = text:len() |
if nSize>255 then |
error("ChatThrottleLib:SendChatMessage(): message length cannot exceed 255 bytes", 2) |
end |
nSize = nSize + self.MSG_OVERHEAD |
-- Check if there's room in the global available bandwidth gauge to send directly |
if not self.bQueueing and nSize < self:UpdateAvail() then |
self.avail = self.avail - nSize |
bMyTraffic = true |
_G.SendChatMessage(text, chattype, language, destination) |
bMyTraffic = false |
self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize |
if callbackFn then |
callbackFn (callbackArg) |
end |
return |
end |
-- Message needs to be queued |
local msg = NewMsg() |
msg.f = _G.SendChatMessage |
msg[1] = text |
msg[2] = chattype or "SAY" |
msg[3] = language |
msg[4] = destination |
msg.n = 4 |
msg.nSize = nSize |
msg.callbackFn = callbackFn |
msg.callbackArg = callbackArg |
self:Enqueue(prio, queueName or (prefix..(chattype or "SAY")..(destination or "")), msg) |
end |
function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) |
if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then |
error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2) |
end |
if callbackFn and type(callbackFn)~="function" then |
error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2) |
end |
local nSize = prefix:len() + 1 + text:len(); |
if nSize>255 then |
error("ChatThrottleLib:SendAddonMessage(): prefix + message length cannot exceed 254 bytes", 2) |
end |
nSize = nSize + self.MSG_OVERHEAD; |
-- Check if there's room in the global available bandwidth gauge to send directly |
if not self.bQueueing and nSize < self:UpdateAvail() then |
self.avail = self.avail - nSize |
bMyTraffic = true |
_G.SendAddonMessage(prefix, text, chattype, target) |
bMyTraffic = false |
self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize |
if callbackFn then |
callbackFn (callbackArg) |
end |
return |
end |
-- Message needs to be queued |
local msg = NewMsg() |
msg.f = _G.SendAddonMessage |
msg[1] = prefix |
msg[2] = text |
msg[3] = chattype |
msg[4] = target |
msg.n = (target~=nil) and 4 or 3; |
msg.nSize = nSize |
msg.callbackFn = callbackFn |
msg.callbackArg = callbackArg |
self:Enqueue(prio, queueName or (prefix..chattype..(target or "")), msg) |
end |
----------------------------------------------------------------------- |
-- Get the ball rolling! |
ChatThrottleLib:Init() |
--[[ WoWBench debugging snippet |
if(WOWB_VER) then |
local function SayTimer() |
print("SAY: "..GetTime().." "..arg1) |
end |
ChatThrottleLib.Frame:SetScript("OnEvent", SayTimer) |
ChatThrottleLib.Frame:RegisterEvent("CHAT_MSG_SAY") |
end |
]] |
--[[ |
**************************************************************************************** |
LibAboutPanel |
File date: 2010-12-01T19:31:23Z |
Project version: v1.51 |
Author: Tekkub, Ackis |
**************************************************************************************** |
]]-- |
local lib, oldminor = LibStub:NewLibrary("LibAboutPanel", 2) |
if not lib then return end |
function lib.new(parent, addonname) |
local frame = CreateFrame("Frame", nil, UIParent) |
frame.name, frame.parent, frame.addonname = not parent and gsub(addonname," ","") or "About", parent, gsub(addonname," ","") -- Remove spaces from addonname because GetMetadata doesn't like that |
frame:Hide() |
frame:SetScript("OnShow", lib.OnShow) |
InterfaceOptions_AddCategory(frame) |
return frame |
end |
local GAME_LOCALE = GetLocale() |
local L = {} |
-- frFR |
if GAME_LOCALE == "frFR" then |
L["About"] = "Ã propos de" |
L["Click and press Ctrl-C to copy"] = "Click and press Ctrl-C to copy" |
-- deDE |
elseif GAME_LOCALE == "deDE" then |
L["About"] = "Ãber" |
L["Click and press Ctrl-C to copy"] = "Klicken und Strg-C drücken zum kopieren" |
-- esES |
elseif GAME_LOCALE == "esES" then |
L["About"] = "Acerca de" |
L["Click and press Ctrl-C to copy"] = "Click and press Ctrl-C to copy" |
-- esMX |
elseif GAME_LOCALE == "esMX" then |
L["About"] = "Sobre" |
L["Click and press Ctrl-C to copy"] = "Click and press Ctrl-C to copy" |
-- koKR |
elseif GAME_LOCALE == "koKR" then |
L["About"] = "ëíì¬" |
L["Click and press Ctrl-C to copy"] = "í´ë¦ í Ctrl-C ë³µì¬" |
-- ruRU |
elseif GAME_LOCALE == "ruRU" then |
L["About"] = "Ðб аддоне" |
L["Click and press Ctrl-C to copy"] = "Click and press Ctrl-C to copy" |
-- zhCN |
elseif GAME_LOCALE == "zhCN" then |
L["About"] = "å ³äº" |
L["Click and press Ctrl-C to copy"] = "ç¹å»å¹¶ Ctrl-C å¤å¶" |
-- zhTW |
elseif GAME_LOCALE == "zhTW" then |
L["About"] = "éæ¼" |
L["Click and press Ctrl-C to copy"] = "å·¦éµé»æ並æä¸ Ctrl-C 以è¤è£½å串" |
-- enUS and non-localized |
else |
L["About"] ="About" |
L["Click and press Ctrl-C to copy"] = "Click and press Ctrl-C to copy" |
end |
local editbox = CreateFrame('EditBox', nil, UIParent) |
editbox:Hide() |
editbox:SetAutoFocus(true) |
editbox:SetHeight(32) |
editbox:SetFontObject('GameFontHighlightSmall') |
lib.editbox = editbox |
local left = editbox:CreateTexture(nil, "BACKGROUND") |
left:SetWidth(8) left:SetHeight(20) |
left:SetPoint("LEFT", -5, 0) |
left:SetTexture("Interface\\Common\\Common-Input-Border") |
left:SetTexCoord(0, 0.0625, 0, 0.625) |
local right = editbox:CreateTexture(nil, "BACKGROUND") |
right:SetWidth(8) right:SetHeight(20) |
right:SetPoint("RIGHT", 0, 0) |
right:SetTexture("Interface\\Common\\Common-Input-Border") |
right:SetTexCoord(0.9375, 1, 0, 0.625) |
local center = editbox:CreateTexture(nil, "BACKGROUND") |
center:SetHeight(20) |
center:SetPoint("RIGHT", right, "LEFT", 0, 0) |
center:SetPoint("LEFT", left, "RIGHT", 0, 0) |
center:SetTexture("Interface\\Common\\Common-Input-Border") |
center:SetTexCoord(0.0625, 0.9375, 0, 0.625) |
editbox:SetScript("OnEscapePressed", editbox.ClearFocus) |
editbox:SetScript("OnEnterPressed", editbox.ClearFocus) |
editbox:SetScript("OnEditFocusLost", editbox.Hide) |
editbox:SetScript("OnEditFocusGained", editbox.HighlightText) |
editbox:SetScript("OnTextChanged", function(self) |
self:SetText(self:GetParent().val) |
self:HighlightText() |
end) |
function lib.OpenEditbox(self) |
editbox:SetText(self.val) |
editbox:SetParent(self) |
editbox:SetPoint("LEFT", self) |
editbox:SetPoint("RIGHT", self) |
editbox:Show() |
end |
local fields = {"Version", "Author", "X-Category", "X-License", "X-Email", "Email", "eMail", "X-Website", "X-Credits", "X-Localizations", "X-Donate"} |
local haseditbox = {["X-Website"] = true, ["X-Email"] = true, ["X-Donate"] = true, ["Email"] = true, ["eMail"] = true} |
local function HideTooltip() GameTooltip:Hide() end |
local function ShowTooltip(self) |
GameTooltip:SetOwner(self, "ANCHOR_TOPRIGHT") |
GameTooltip:SetText(L["Click and press Ctrl-C to copy"]) |
--GameTooltip:SetText("Click and press Ctrl-C to copy") |
end |
function lib.OnShow(frame) |
local notefield = "Notes" |
if (GAME_LOCALE ~= "enUS") then |
notefield = notefield .. "-" .. GAME_LOCALE |
end |
-- Get the localized version of notes if it exists or fall back to the english one. |
local notes = GetAddOnMetadata(frame.addonname, notefield) or GetAddOnMetadata(frame.addonname, "Notes") |
if not frame.about_title then |
frame.about_title = frame:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge") |
end |
local title = frame.about_title |
title:SetPoint("TOPLEFT", 16, -16) |
title:SetText(frame.parent and (frame.parent.." - " .. L["About"]) or frame.name) |
if not frame.about_subtitle then |
frame.about_subtitle = frame:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") |
end |
local subtitle = frame.about_subtitle |
subtitle:SetHeight(32) |
subtitle:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -8) |
subtitle:SetPoint("RIGHT", frame, -32, 0) |
subtitle:SetNonSpaceWrap(true) |
subtitle:SetJustifyH("LEFT") |
subtitle:SetJustifyV("TOP") |
subtitle:SetText(notes) |
local anchor |
for _,field in pairs(fields) do |
local val = GetAddOnMetadata(frame.addonname, field) |
if val then |
if not frame[field .. "_title"] then |
frame[field .. "_title"] = frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall") |
end |
local title = frame[field .. "_title"] |
title:SetWidth(75) |
if not anchor then |
title:SetPoint("TOPLEFT", subtitle, "BOTTOMLEFT", -2, -12) |
else |
title:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, -10) |
end |
title:SetJustifyH("RIGHT") |
title:SetText(field:gsub("X%-", "")) |
if not frame[field .. "_detail"] then |
frame[field .. "_detail"] = frame:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall") |
end |
local detail = frame[field .. "_detail"] |
detail:SetHeight(32) |
detail:SetPoint("LEFT", title, "RIGHT", 4, 0) |
detail:SetPoint("RIGHT", frame, -16, 0) |
detail:SetJustifyH("LEFT") |
if (field == "Author") then |
local authorservername = GetAddOnMetadata(frame.addonname, "X-Author-Server") |
local authorfaction = GetAddOnMetadata(frame.addonname, "X-Author-Faction") |
if authorservername and authorfaction then |
detail:SetText((haseditbox[field] and "|cff9999ff" or "").. val .. " on " .. authorservername .. " (" .. authorfaction .. ")") |
elseif authorservername and not authorfaction then |
detail:SetText((haseditbox[field] and "|cff9999ff" or "").. val .. " on " .. authorservername) |
elseif not authorservername and authorfaction then |
detail:SetText((haseditbox[field] and "|cff9999ff" or "").. val .. " (" .. authorfaction .. ")") |
else |
detail:SetText((haseditbox[field] and "|cff9999ff" or "").. val) |
end |
elseif (field == "Version") then |
local addonversion = GetAddOnMetadata(frame.addonname, field) |
-- Remove @project-revision@ and replace it with Repository |
addonversion = string.gsub(addonversion,"@project.revision@","Repository") |
detail:SetText((haseditbox[field] and "|cff9999ff" or "").. addonversion) |
else |
detail:SetText((haseditbox[field] and "|cff9999ff" or "").. val) |
end |
if haseditbox[field] then |
local button = CreateFrame("Button", nil, frame) |
button:SetAllPoints(detail) |
button.val = val |
button:SetScript("OnClick", lib.OpenEditbox) |
button:SetScript("OnEnter", ShowTooltip) |
button:SetScript("OnLeave", HideTooltip) |
end |
anchor = title |
end |
end |
end |