/trunk
local addonName, BiM = ... |
<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/ |
..\FrameXML\UI.xsd"> |
<Script file="AceLocale-3.0.lua"/> |
</Ui> |
--- **AceLocale-3.0** manages localization in addons, allowing for multiple locale to be registered with fallback to the base locale for untranslated strings. |
-- @class file |
-- @name AceLocale-3.0 |
-- @release $Id: AceLocale-3.0.lua 1035 2011-07-09 03:20:13Z kaelten $ |
local MAJOR,MINOR = "AceLocale-3.0", 6 |
local AceLocale, oldminor = LibStub:NewLibrary(MAJOR, MINOR) |
if not AceLocale then return end -- no upgrade needed |
-- Lua APIs |
local assert, tostring, error = assert, tostring, error |
local getmetatable, setmetatable, rawset, rawget = getmetatable, setmetatable, rawset, rawget |
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded |
-- List them here for Mikk's FindGlobals script |
-- GLOBALS: GAME_LOCALE, geterrorhandler |
local gameLocale = GetLocale() |
if gameLocale == "enGB" then |
gameLocale = "enUS" |
end |
AceLocale.apps = AceLocale.apps or {} -- array of ["AppName"]=localetableref |
AceLocale.appnames = AceLocale.appnames or {} -- array of [localetableref]="AppName" |
-- This metatable is used on all tables returned from GetLocale |
local readmeta = { |
__index = function(self, key) -- requesting totally unknown entries: fire off a nonbreaking error and return key |
rawset(self, key, key) -- only need to see the warning once, really |
geterrorhandler()(MAJOR..": "..tostring(AceLocale.appnames[self])..": Missing entry for '"..tostring(key).."'") |
return key |
end |
} |
-- This metatable is used on all tables returned from GetLocale if the silent flag is true, it does not issue a warning on unknown keys |
local readmetasilent = { |
__index = function(self, key) -- requesting totally unknown entries: return key |
rawset(self, key, key) -- only need to invoke this function once |
return key |
end |
} |
-- Remember the locale table being registered right now (it gets set by :NewLocale()) |
-- NOTE: Do never try to register 2 locale tables at once and mix their definition. |
local registering |
-- local assert false function |
local assertfalse = function() assert(false) end |
-- This metatable proxy is used when registering nondefault locales |
local writeproxy = setmetatable({}, { |
__newindex = function(self, key, value) |
rawset(registering, key, value == true and key or value) -- assigning values: replace 'true' with key string |
end, |
__index = assertfalse |
}) |
-- This metatable proxy is used when registering the default locale. |
-- It refuses to overwrite existing values |
-- Reason 1: Allows loading locales in any order |
-- Reason 2: If 2 modules have the same string, but only the first one to be |
-- loaded has a translation for the current locale, the translation |
-- doesn't get overwritten. |
-- |
local writedefaultproxy = setmetatable({}, { |
__newindex = function(self, key, value) |
if not rawget(registering, key) then |
rawset(registering, key, value == true and key or value) |
end |
end, |
__index = assertfalse |
}) |
--- Register a new locale (or extend an existing one) for the specified application. |
-- :NewLocale will return a table you can fill your locale into, or nil if the locale isn't needed for the players |
-- game locale. |
-- @paramsig application, locale[, isDefault[, silent]] |
-- @param application Unique name of addon / module |
-- @param locale Name of the locale to register, e.g. "enUS", "deDE", etc. |
-- @param isDefault If this is the default locale being registered (your addon is written in this language, generally enUS) |
-- @param silent If true, the locale will not issue warnings for missing keys. Must be set on the first locale registered. If set to "raw", nils will be returned for unknown keys (no metatable used). |
-- @usage |
-- -- enUS.lua |
-- local L = LibStub("AceLocale-3.0"):NewLocale("TestLocale", "enUS", true) |
-- L["string1"] = true |
-- |
-- -- deDE.lua |
-- local L = LibStub("AceLocale-3.0"):NewLocale("TestLocale", "deDE") |
-- if not L then return end |
-- L["string1"] = "Zeichenkette1" |
-- @return Locale Table to add localizations to, or nil if the current locale is not required. |
function AceLocale:NewLocale(application, locale, isDefault, silent) |
-- GAME_LOCALE allows translators to test translations of addons without having that wow client installed |
local gameLocale = GAME_LOCALE or gameLocale |
local app = AceLocale.apps[application] |
if silent and app and getmetatable(app) ~= readmetasilent then |
geterrorhandler()("Usage: NewLocale(application, locale[, isDefault[, silent]]): 'silent' must be specified for the first locale registered") |
end |
if not app then |
if silent=="raw" then |
app = {} |
else |
app = setmetatable({}, silent and readmetasilent or readmeta) |
end |
AceLocale.apps[application] = app |
AceLocale.appnames[app] = application |
end |
if locale ~= gameLocale and not isDefault then |
return -- nop, we don't need these translations |
end |
registering = app -- remember globally for writeproxy and writedefaultproxy |
if isDefault then |
return writedefaultproxy |
end |
return writeproxy |
end |
--- Returns localizations for the current locale (or default locale if translations are missing). |
-- Errors if nothing is registered (spank developer, not just a missing translation) |
-- @param application Unique name of addon / module |
-- @param silent If true, the locale is optional, silently return nil if it's not found (defaults to false, optional) |
-- @return The locale table for the current language. |
function AceLocale:GetLocale(application, silent) |
if not silent and not AceLocale.apps[application] then |
error("Usage: GetLocale(application[, silent]): 'application' - No locales registered for '"..tostring(application).."'", 2) |
end |
return AceLocale.apps[application] |
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/ |
..\FrameXML\UI.xsd"> |
<Script file="AceTimer-3.0.lua"/> |
</Ui> |
--- **AceTimer-3.0** provides a central facility for registering timers. |
-- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient |
-- data structure that allows easy dispatching and fast rescheduling. Timers can be registered |
-- or canceled at any time, even from within a running timer, without conflict or large overhead.\\ |
-- AceTimer is currently limited to firing timers at a frequency of 0.01s as this is what the WoW timer API |
-- restricts us to. |
-- |
-- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you |
-- need to cancel the timer you just registered. |
-- |
-- **AceTimer-3.0** can be embeded into your addon, either explicitly by calling AceTimer:Embed(MyAddon) or by |
-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object |
-- and can be accessed directly, without having to explicitly call AceTimer itself.\\ |
-- It is recommended to embed AceTimer, otherwise you'll have to specify a custom `self` on all calls you |
-- make into AceTimer. |
-- @class file |
-- @name AceTimer-3.0 |
-- @release $Id: AceTimer-3.0.lua 1119 2014-10-14 17:23:29Z nevcairiel $ |
local MAJOR, MINOR = "AceTimer-3.0", 17 -- Bump minor on changes |
local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR) |
if not AceTimer then return end -- No upgrade needed |
AceTimer.activeTimers = AceTimer.activeTimers or {} -- Active timer list |
local activeTimers = AceTimer.activeTimers -- Upvalue our private data |
-- Lua APIs |
local type, unpack, next, error, select = type, unpack, next, error, select |
-- WoW APIs |
local GetTime, C_TimerAfter = GetTime, C_Timer.After |
local function new(self, loop, func, delay, ...) |
if delay < 0.01 then |
delay = 0.01 -- Restrict to the lowest time that the C_Timer API allows us |
end |
local timer = {...} |
timer.object = self |
timer.func = func |
timer.looping = loop |
timer.argsCount = select("#", ...) |
timer.delay = delay |
timer.ends = GetTime() + delay |
activeTimers[timer] = timer |
-- Create new timer closure to wrap the "timer" object |
timer.callback = function() |
if not timer.cancelled then |
if type(timer.func) == "string" then |
-- We manually set the unpack count to prevent issues with an arg set that contains nil and ends with nil |
-- e.g. local t = {1, 2, nil, 3, nil} print(#t) will result in 2, instead of 5. This fixes said issue. |
timer.object[timer.func](timer.object, unpack(timer, 1, timer.argsCount)) |
else |
timer.func(unpack(timer, 1, timer.argsCount)) |
end |
if timer.looping and not timer.cancelled then |
-- Compensate delay to get a perfect average delay, even if individual times don't match up perfectly |
-- due to fps differences |
local time = GetTime() |
local delay = timer.delay - (time - timer.ends) |
-- Ensure the delay doesn't go below the threshold |
if delay < 0.01 then delay = 0.01 end |
C_TimerAfter(delay, timer.callback) |
timer.ends = time + delay |
else |
activeTimers[timer.handle or timer] = nil |
end |
end |
end |
C_TimerAfter(delay, timer.callback) |
return timer |
end |
--- Schedule a new one-shot timer. |
-- The timer will fire once in `delay` seconds, unless canceled before. |
-- @param callback Callback function for the timer pulse (funcref or method name). |
-- @param delay Delay for the timer, in seconds. |
-- @param ... An optional, unlimited amount of arguments to pass to the callback function. |
-- @usage |
-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0") |
-- |
-- function MyAddOn:OnEnable() |
-- self:ScheduleTimer("TimerFeedback", 5) |
-- end |
-- |
-- function MyAddOn:TimerFeedback() |
-- print("5 seconds passed") |
-- end |
function AceTimer:ScheduleTimer(func, delay, ...) |
if not func or not delay then |
error(MAJOR..": ScheduleTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2) |
end |
if type(func) == "string" then |
if type(self) ~= "table" then |
error(MAJOR..": ScheduleTimer(callback, delay, args...): 'self' - must be a table.", 2) |
elseif not self[func] then |
error(MAJOR..": ScheduleTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2) |
end |
end |
return new(self, nil, func, delay, ...) |
end |
--- Schedule a repeating timer. |
-- The timer will fire every `delay` seconds, until canceled. |
-- @param callback Callback function for the timer pulse (funcref or method name). |
-- @param delay Delay for the timer, in seconds. |
-- @param ... An optional, unlimited amount of arguments to pass to the callback function. |
-- @usage |
-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0") |
-- |
-- function MyAddOn:OnEnable() |
-- self.timerCount = 0 |
-- self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5) |
-- end |
-- |
-- function MyAddOn:TimerFeedback() |
-- self.timerCount = self.timerCount + 1 |
-- print(("%d seconds passed"):format(5 * self.timerCount)) |
-- -- run 30 seconds in total |
-- if self.timerCount == 6 then |
-- self:CancelTimer(self.testTimer) |
-- end |
-- end |
function AceTimer:ScheduleRepeatingTimer(func, delay, ...) |
if not func or not delay then |
error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2) |
end |
if type(func) == "string" then |
if type(self) ~= "table" then |
error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'self' - must be a table.", 2) |
elseif not self[func] then |
error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2) |
end |
end |
return new(self, true, func, delay, ...) |
end |
--- Cancels a timer with the given id, registered by the same addon object as used for `:ScheduleTimer` |
-- Both one-shot and repeating timers can be canceled with this function, as long as the `id` is valid |
-- and the timer has not fired yet or was canceled before. |
-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` |
function AceTimer:CancelTimer(id) |
local timer = activeTimers[id] |
if not timer then |
return false |
else |
timer.cancelled = true |
activeTimers[id] = nil |
return true |
end |
end |
--- Cancels all timers registered to the current addon object ('self') |
function AceTimer:CancelAllTimers() |
for k,v in pairs(activeTimers) do |
if v.object == self then |
AceTimer.CancelTimer(self, k) |
end |
end |
end |
--- Returns the time left for a timer with the given id, registered by the current addon object ('self'). |
-- This function will return 0 when the id is invalid. |
-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` |
-- @return The time left on the timer. |
function AceTimer:TimeLeft(id) |
local timer = activeTimers[id] |
if not timer then |
return 0 |
else |
return timer.ends - GetTime() |
end |
end |
-- --------------------------------------------------------------------- |
-- Upgrading |
-- Upgrade from old hash-bucket based timers to C_Timer.After timers. |
if oldminor and oldminor < 10 then |
-- disable old timer logic |
AceTimer.frame:SetScript("OnUpdate", nil) |
AceTimer.frame:SetScript("OnEvent", nil) |
AceTimer.frame:UnregisterAllEvents() |
-- convert timers |
for object,timers in pairs(AceTimer.selfs) do |
for handle,timer in pairs(timers) do |
if type(timer) == "table" and timer.callback then |
local newTimer |
if timer.delay then |
newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.callback, timer.delay, timer.arg) |
else |
newTimer = AceTimer.ScheduleTimer(timer.object, timer.callback, timer.when - GetTime(), timer.arg) |
end |
-- Use the old handle for old timers |
activeTimers[newTimer] = nil |
activeTimers[handle] = newTimer |
newTimer.handle = handle |
end |
end |
end |
AceTimer.selfs = nil |
AceTimer.hash = nil |
AceTimer.debug = nil |
elseif oldminor and oldminor < 17 then |
-- Upgrade from old animation based timers to C_Timer.After timers. |
AceTimer.inactiveTimers = nil |
AceTimer.frame = nil |
local oldTimers = AceTimer.activeTimers |
-- Clear old timer table and update upvalue |
AceTimer.activeTimers = {} |
activeTimers = AceTimer.activeTimers |
for handle, timer in pairs(oldTimers) do |
local newTimer |
-- Stop the old timer animation |
local duration, elapsed = timer:GetDuration(), timer:GetElapsed() |
timer:GetParent():Stop() |
if timer.looping then |
newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.func, duration, unpack(timer.args, 1, timer.argsCount)) |
else |
newTimer = AceTimer.ScheduleTimer(timer.object, timer.func, duration - elapsed, unpack(timer.args, 1, timer.argsCount)) |
end |
-- Use the old handle for old timers |
activeTimers[newTimer] = nil |
activeTimers[handle] = newTimer |
newTimer.handle = handle |
end |
-- Migrate transitional handles |
if oldminor < 13 and AceTimer.hashCompatTable then |
for handle, id in pairs(AceTimer.hashCompatTable) do |
local t = activeTimers[id] |
if t then |
activeTimers[id] = nil |
activeTimers[handle] = t |
t.handle = handle |
end |
end |
AceTimer.hashCompatTable = nil |
end |
end |
-- --------------------------------------------------------------------- |
-- Embed handling |
AceTimer.embeds = AceTimer.embeds or {} |
local mixins = { |
"ScheduleTimer", "ScheduleRepeatingTimer", |
"CancelTimer", "CancelAllTimers", |
"TimeLeft" |
} |
function AceTimer:Embed(target) |
AceTimer.embeds[target] = true |
for _,v in pairs(mixins) do |
target[v] = AceTimer[v] |
end |
return target |
end |
-- AceTimer:OnEmbedDisable(target) |
-- target (object) - target object that AceTimer is embedded in. |
-- |
-- cancel all timers registered for the object |
function AceTimer:OnEmbedDisable(target) |
target:CancelAllTimers() |
end |
for addon in pairs(AceTimer.embeds) do |
AceTimer:Embed(addon) |
end |
======================================================================================== |
LibAdvancedIconSelector provides a searchable icon selection GUI to World |
of Warcraft addons. |
Copyright (c) 2011 - 2012 David Forrester (Darthyl of Bronzebeard-US) |
Email: darthyl@hotmail.com |
Permission is hereby granted, free of charge, to any person obtaining a copy |
of this software and associated documentation files (the "Software"), to deal |
in the Software without restriction, including without limitation the rights |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
copies of the Software, and to permit persons to whom the Software is |
furnished to do so, subject to the following conditions: |
The above copyright notice and this permission notice shall be included in |
all copies or substantial portions of the Software. |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
THE SOFTWARE. |
======================================================================================== |
======================================================================================== |
Please notify me if you wish to make use of this library in your addon! |
I need to know for testing purposes - to make sure that any changes I make |
aren't going to break someone else's addon! |
======================================================================================== |
Official website for AdvancedIconSelector: |
http://www.curse.com/addons/wow/advancediconselector |
Official website for LibAdvancedIconSelector-1.0: |
http://www.curse.com/addons/wow/libadvancediconselector-1-0 |
Original author: Darthyl of Bronzebeard-US <darthyl@hotmail.com> |
======================================================================================== |
Setup work: (to use LibAdvancedIconSelector-1.0 in your addon) |
1) Please LET ME KNOW that you will be using my library in your addon. If I don't |
know you're dependent on my library, I might accidentally break your addon! |
2) Copy the "LibAdvancedIconSelector-1.0" directory into a "Libs" subdirectory of |
your addon. |
3) Add Libs\LibAdvancedIconSelector-1.0\LibAdvancedIconSelector-1.0.xml to your TOC. |
4) You should also add LibAdvancedIconSelector-1.0 to OptionalDeps and X-Embeds. |
(This step is technically only required if your addon can run disembedded, but |
it's a good idea to do it even if not): |
## OptionalDeps: LibAdvancedIconSelector-1.0 |
## X-Embeds: LibAdvancedIconSelector-1.0 |
If you are loading LibAdvancedIconSelector-1.0 on-demand, however |
(via LoadAddOn()), do NOT add it to OptionalDeps - otherwise it will |
load immediately instead of on-demand. |
==== IMPORTANT ==== |
When you distribute your addon, note that the user will NOT be able to search by |
keywords unless (a) you package a copy of the keyword library alongside your addon (see |
instructions at top of AdvancedIconSelector-KeywordData.lua), or (b) the user has installed |
AdvancedIconSelector alongside your addon. Packaging the keyword library is not required, |
however (users will be able to search by filename even if keyword data is not present), |
but it is recommended. |
As of version 1.0.4 of this library, there is also a new technique to load the keyword data. |
Instead of calling LoadAddOn("MyAddon_KeywordData"), simply specify the addon name as an |
option to Create<...>() as follows: |
local options = { |
. . . |
keywordAddonName = "MyAddon_KeywordData" |
} |
Or, you can programmatically load the library with: |
lib:LoadKeywords("MyAddon_KeywordData") |
(but the first approach is preferred) |
Both of these techniques provide the following behavior: |
If your keyword library is not available, or is older than the default keyword library |
(AdvancedIconSelector-KeywordData), the default one will be loaded instead (assuming it's |
available). |
*Do NOT simply bundle AdvancedIconSelector-KeywordData with your addon - you must rename it!* |
(see instructions at top of AdvancedIconSelector-KeywordData.lua) |
(The keyword library is packaged in this strange manner so that it doesn't get loaded until |
it's actually needed, and so multiple addons bundling the library don't have conflicting |
directory names that may cause download managers to get confused and replace newer versions |
with older ones.) |
======================================================================================== |
There are 3 ways to use LibAdvancedIconSelector-1.0: |
a) Standard window mode. A window is created that contains an icon selector, search box, |
okay button, cancel button, etc. |
b) Frame-only mode. Only the icon selector frame is created (the part where you pick |
the icons plus the scroll bar); no search box, window, or buttons are included, and |
you must specify the search parameter programmatically. |
c) Search-only mode. No GUI elements are created. This mode allows you to use the keyword |
and search features of AdvancedIconSelector, while providing your own GUI to display the |
icons. |
Standard window mode example: |
local lib = LibStub("LibAdvancedIconSelector-1.0") -- (ideally, this would be loaded on-demand) |
local options = { } |
local myIconWindow = lib:CreateIconSelectorWindow("MyIconWindow", UIParent, options) |
myIconWindow:SetPoint("CENTER") |
myIconWindow:Show() |
Frame-only mode example: |
local lib = LibStub("LibAdvancedIconSelector-1.0") |
local options = { } |
local myIconFrame = lib:CreateIconSelectorFrame("MyIconFrame", UIParent, options) |
myIconFrame:SetSize(400, 300) |
myIconFrame:SetPoint("CENTER") |
myIconFrame:SetSearchParameter(nil) -- (begin the search!) |
Search-only mode example: |
local lib = LibStub("LibAdvancedIconSelector-1.0") |
local options = { } |
local search = lib:CreateSearch(options) |
search:SetScript("OnSearchStarted", function(search) print("Search started") end) |
search:SetScript("OnSearchResultAdded", function(search, texture) print("Found icon:", texture) end) |
search:SetScript("OnSearchComplete", function(search) print("Search finished") end) |
search:SetSearchParameter(nil) -- (begin the search!) |
Note: This search object should be reused if possible - just call SetSearchParameter() |
or RestartSearch() to begin the enumeration again! |
======================================================================================== |
Library functionality: |
function lib:Embed(addon) |
Embeds this library's functions into your addon. Calling this is not necessary. |
function lib:CreateIconSelectorWindow(name, parent, options) |
Creates a new icon selector WINDOW, including search box, buttons, etc. |
function lib:CreateIconSelectorFrame(name, parent, options) |
Creates a new icon selector FRAME, which includes only the icon display |
and its scroll bar. |
function lib:CreateSearch(options) |
Creates and returns a new search object. |
function lib:GetRevision() |
Returns the revision # of the library. |
function lib:LoadKeywords(addonName) |
Loads the keyword library with the given name, defaulting to AdvancedIconSelector-KeywordData |
(if available) if the given addon can't be found. It's not normally necessary to call this, |
as it's done automatically when an icon scan is started, using keywordAddonName from options. |
(This function is only necessary if you want to access keyword data without starting an icon search) |
function lib:LookupKeywords(texture) |
Using the currently loaded keyword library. Returns a string of keywords corresponding |
to the given icon texture. Note that the filename should NOT include the INTERFACE\\ICONS\\ |
prefix. Returns nil if there is no keyword library loaded, or if the given icon can't |
be found or doesn't have any keywords associated with it. |
======================================================================================== |
IconSelectorWindow functionality: |
** Note: You can always access the icon frame contained within an icon window through |
iconsWindow.iconsFrame ** |
function IconSelectorWindow:SetScript(scriptType, handler) |
The usual SetScript() for a frame, but with the following additional script types: |
OnOkayClicked(self) - fired when the Okay button is clicked |
OnCancelClicked(self) - fired when the Cancel, Close, or X button is clicked |
function IconSelectorWindow:SetSearchParameter(searchText, immediateResults) |
Sets the search parameter, also updating the search field's text. |
======================================================================================== |
IconSelectorFrame functionality: |
function IconSelectorFrame:SetSearchParameter(searchText, immediateResults) |
Sets the search parameter used to filter the icons displayed in the frame, |
and begins the search after a short delay (in case the user is still typing), |
unless immediateResults is set to true, in which case the search begins |
immediately. |
function IconSelectorFrame:SetSelectedIcon(index) |
Sets the selection to the icon with the given (global) index. |
function IconSelectorFrame:SetSelectionByName(name) |
Sets the selection to the icon with the given texture filename, |
*BUT* only once it has been found, unless a new icon has been selected by |
the user before then. |
function IconSelectorFrame:GetSelectedIcon() |
Returns the global index of the currently selected icon, or nil if nothing |
is currently selected. |
function IconSelectorFrame:GetIconInfo(index) |
(Returns id, kind, texture) |
Returns information about the icon at the given (global) index. |
- id is the index of the icon within its section (local index). |
- kind is a section-defined string such as "Macro", or "Item" |
- texture is the icon's texture |
If index is out of range or nil, nil is returned. |
function IconSelectorFrame:ReplaceSection(sectionName, section) |
Replaces a named section of icons with the given one. See "custom icon sections" |
below for details. |
function IconSelectorFrame:SetSectionVisibility(sectionName, visibility) |
Shows or hides a named section of icons. |
function IconSelectorFrame:GetSectionVisibility(sectionName) |
Returns whether or not the given named icon section is currently shown. |
function IconSelectorFrame:SetScript(scriptType, handler) |
New script types have been added to the iconsFrame: |
OnSelectedIconChanged(self) - fired when the selection changes |
BeforeShow(self) - like OnShow, but fired just BEFORE the frame is shown. |
(helpful if updating custom icon sections) |
======================================================================================== |
SearchObject functionality: |
function SearchObject:SetSearchParameter(searchText, immediateResults) |
Sets the search parameter, and then restarts the search. Since this is generally |
called each time an editbox is changed, a small delay occurs before the search |
begins unless immediateResults is set to true, except for the first search performed |
with a search object. |
function SearchObject:GetSearchParameter() |
Returns the current search parameter. |
function SearchObject:RestartSearch() |
Restarts the search from the beginning. |
function SearchObject:Stop() |
Stops the search immediately. Call RestartSearch() to start it again. (This is |
generally called when a frame is hidden to prevent aimlessly searching for icons.) |
function SearchObject:SetScript(script, callback) |
BeforeSearchStarted(search) - called just before the search is (re)started |
OnSearchStarted(search) - called after the search is (re)started, but before any results are reported |
OnSearchResultAdded(search, texture, globalID, localID, kind) - called for each search result found |
OnSearchComplete(search) - called when all results have been found |
OnIconScanned(search, texture, globalID, localID, kind) - called for each icon scanned, regardless of whether it matches the search parameter |
OnSearchTick(search) - called after each search tick (or at a constant rate, if the search is ever not tick-based) |
function SearchObject:ReplaceSection(sectionName, section) |
Replaces a named section of icons with the given one. See "custom icon sections" |
below for details. |
function SearchObject:ExcludeSection(sectionName, exclude) |
Excludes (or includes) a named icon section from the search, but does not restart the search. |
function SearchObject:IsSectionExcluded(sectionName) |
Returns whether or not the given icon section is excluded from the search. |
function SearchObject:GetIconInfo(id) |
(Returns id, kind, texture - see IconSelectorFrame:GetIconInfo()) |
Returns information about the icon at the given global index. |
======================================================================================== |
Options tables: |
You can pass an optional "options" table to the Create<...>() functions that specifies window styling |
and icon section information. Officially supported options are as follows: |
Primary options: |
sectionOrder - a list of strings which defines which icon sections should appear, and the order |
that they should appear in. Predefined sections include: DynamicIcon, MacroIcons, ItemIcons |
You can specify custom section names here as well. |
sections - a table of custom sections - see "custom icon sections" below for details. |
sectionVisibility - mapping from section name to whether that section is initially visible |
visibilityButtons - array of 2-element tables that maps from section name to the text of a check |
button that can be used to toggle that section's visibility. (1st element = name, 2nd = text) |
showDynamicText - shows the "(dynamic)" text under the question mark icon if true (default = false) |
okayCancel - Okay/Cancel vs. Close only (default = true => Okay/cancel) |
customFrame - a custom frame to show at the top of the icon selector window. This is where |
the name editbox is located on the equipment set / macro replacement UIs. |
Basic window options: |
width - initial width of the window |
height - initial height of the window |
enableResize - enable resizing of the frame? (default = true) |
enableMove - enable moving of the frame? (default = true) |
headerText - title of the frame (default = L["Icon Browser"]) |
Additional window customization: |
allowOffscreen - enables positioning of the window offscreen (default = false) |
minResizeWidth - minimum resize width (default = 300) |
minResizeHeight - minimum resize height (default = 200) |
anchorFrame - disables move / resize functionality while this frame is shown (default = nil) |
noCloseButton - remove the X button? (default = false) |
bgFile - override background texture, as passed to SetBackdrop() |
edgeFile - override edge texture, as passed to SetBackdrop() |
tile - override tile parameter for SetBackdrop() |
tileSize - override background tile size, as passed to SetBackdrop() |
edgeSize - override edge segment size, as passed to SetBackdrop() |
insets - override insets for the background texture, as passed to SetBackdrop() |
contentInsets - override insets for frame contents |
headerTexture - override texture used for the frame header |
headerWidth - override width of the frame header |
headerOffsetX / headerOffsetY - override offset of the header text |
headerFont - override font to use for the header text |
======================================================================================== |
Custom icon sections: |
Custom icon sections can be defined as in the following example: |
local MACRO_ICON_FILENAMES = { } |
GetMacroIcons(MACRO_ICON_FILENAMES) |
local lib = LibStub("LibAdvancedIconSelector-1.0") -- (ideally, this would be loaded on-demand) |
local options = { |
sectionOrder = { "MySection1", "MySection2" }, |
sections = { |
MySection1 = { count = 1, GetIconInfo = function(index) return index, "Dynamic", "INV_Misc_QuestionMark" end }, |
MySection2 = { count = 10, GetIconInfo = function(index) return index, "Macro", MACRO_ICON_FILENAMES[index] end } |
} |
} |
local myIconWindow = lib:CreateIconSelectorWindow("MyIconWindow", UIParent, options) |
myIconWindow:SetPoint("CENTER") |
myIconWindow:Show() |
Each section table simply has two entries - a count, and a GetIconInfo() function to |
return information about each icon (local id, kind, texture). If the number of icons |
in a section must change after it's been specified, consider using IconSelectorFrame:ReplaceSection(). |
(altering tables after handing them to L-AIS is unsupported and usually won't work - |
please do not attempt it) |
======================================================================================== |
For the time being, the functionality provided by the library is intentionally |
very limited. Let me know if there's something you need to do that's not |
possible with the current API. |
<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/ |
..\FrameXML\UI.xsd"> |
<Script file="Locales\enUS.lua"/> |
<Script file="LibAdvancedIconSelector-1.0.lua"/> |
</Ui> |
--[[======================================================================================== |
LibAdvancedIconSelector provides a searchable icon selection GUI to World |
of Warcraft addons. |
Copyright (c) 2011 - 2012 David Forrester (Darthyl of Bronzebeard-US) |
Email: darthyl@hotmail.com |
Permission is hereby granted, free of charge, to any person obtaining a copy |
of this software and associated documentation files (the "Software"), to deal |
in the Software without restriction, including without limitation the rights |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
copies of the Software, and to permit persons to whom the Software is |
furnished to do so, subject to the following conditions: |
The above copyright notice and this permission notice shall be included in |
all copies or substantial portions of the Software. |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
THE SOFTWARE. |
========================================================================================]] |
--[[======================================================================================== |
Please notify me if you wish to make use of this library in your addon! |
I need to know for testing purposes - to make sure that any changes I make |
aren't going to break someone else's addon! |
========================================================================================]] |
local DEBUG = false |
if DEBUG and LibDebug then LibDebug() end |
local MAJOR_VERSION = "LibAdvancedIconSelector-1.0" |
local MINOR_VERSION = 14 -- (do not call GetAddOnMetaData) |
if not LibStub then error(MAJOR_VERSION .. " requires LibStub to operate") end |
local lib = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION) |
if not lib then return end |
local L = LibStub("AceLocale-3.0"):GetLocale(MAJOR_VERSION, true) |
LibStub("AceTimer-3.0"):Embed(lib) |
local ICON_WIDTH = 36 |
local ICON_HEIGHT = 36 |
local ICON_SPACING = 4 -- Minimum spacing between icons |
local ICON_PADDING = 4 -- Padding around the icon display |
local INITIATE_SEARCH_DELAY = 0.3 -- The delay between pressing a key and the start of the search |
local SCAN_TICK = 0.1 -- The interval between each search "tick" |
local SCAN_PER_TICK = 1000 -- How many icons to scan per tick? |
local initialized = false |
local MACRO_ICON_FILENAMES = { } |
local ITEM_ICON_FILENAMES = { } |
local keywordLibrary = nil -- The currently loaded keyword library |
-- List of prepositions that aren't re-capitalized when displaying keyword data. Not all prepositions; just the ones that stand out. |
local PREPS = { a = true, an = true, ["and"] = true, by = true, de = true, from = true, ["in"] = true, of = true, on = true, the = true, to = true, ["vs."] = true } |
local defaults = { |
width = 419, |
height = 343, |
enableResize = true, |
enableMove = true, |
okayCancel = true, |
minResizeWidth = 300, |
minResizeHeight = 200, |
insets = { left = 11, right = 11, top = 11, bottom = 10 }, |
contentInsets = { |
left = 11 + 8, right = 11 + 8, |
top = 11 + 20, bottom = 10 + 8 }, |
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", |
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background", |
tile = false, |
tileSize = 32, |
edgeSize = 32, |
headerWidth = 256, |
headerTexture = "Interface\\DialogFrame\\UI-DialogBox-Header", |
headerFont = "GameFontNormal", |
headerOffsetX = 0, |
headerOffsetY = -14, |
headerText = L["FRAME_TITLE"], |
sectionOrder = { "MacroIcons", "ItemIcons" }, |
sections = { }, -- (will be filled in automatically, if not set by user) |
sectionVisibility = { }, -- (will be filled in automatically, if not set by user) |
} |
-- ======================================================================================== |
-- OBJECT MODEL IMPLEMENTATION |
local ObjBase = { } |
-- Derives a new object using "self" as the prototype. |
function ObjBase:Derive(o) |
o = o or { } |
assert(o ~= self and o.superType == nil) |
setmetatable(o, self) -- (self = object / prototype being derived from, not necessarily ObjBase!) |
self.__index = self |
o.superType = self |
return o |
end |
-- Overlays the entries of "self" over the inherited entries of "o". |
-- This is very useful for adding methods to an existing object, such as one created by CreateFrame(). |
-- (Note: replaces o's metatable, and only retains __index of the original metatable) |
function ObjBase:MixInto(o) |
assert(o ~= nil and o ~= self and o.superType == nil) |
local superType = { } -- (indexing this object will index the super type instead) |
o.superType = superType |
setmetatable(superType, getmetatable(o)) |
setmetatable(o, { |
__index = function(t, k) -- (note: do NOT index t from __index or it may loop) |
local r = self[k] -- (mixed-in prototype) |
if r ~= nil then -- (don't use "self[k] or superType[k]" or false won't work) |
return r |
else |
return superType[k] -- (super type) |
end |
end |
}) |
return o |
end |
-- ======================================================================================== |
-- OBJECT DEFINITIONS |
local SearchObject = ObjBase:Derive() |
local IconSelectorWindow = ObjBase:Derive() |
local IconSelectorFrame = ObjBase:Derive() |
local Helpers = ObjBase:Derive() |
-- ================================================================ |
-- LIB IMPLEMENTATION |
-- If DEBUG == true, then prints a console message. |
function lib:Debug(...) |
if DEBUG then |
local prefix = "|cff00ff00[LAIS] [Debug]|r" |
if LibDebug then -- (get around LibDebug's print replacement) |
getmetatable(_G).__index.print(prefix, ...) |
else |
print(prefix, ...) |
end |
end |
end |
lib:Debug("Addon loaded.") |
-- Embeds this library's functions into an addon for ease of use. |
function lib:Embed(addon) |
addon.CreateIconSelectorWindow = lib.CreateIconSelectorWindow |
addon.CreateIconSelectorFrame = lib.CreateIconSelectorFrame |
addon.CreateSearch = lib.CreateSearch |
addon.GetNumMacroIcons = lib.GetNumMacroIcons |
addon.GetNumItemIcons = lib.GetNumItemIcons |
addon.GetRevision = lib.GetRevision |
addon.LoadKeywords = lib.LoadKeywords |
addon.LookupKeywords = lib.LookupKeywords |
addon.ConvertMacroTexture = lib.ConvertMacroTexture |
end |
-- Creates and returns a new icon selector window. |
function lib:CreateIconSelectorWindow(name, parent, options) |
Helpers.InitialInit() |
return IconSelectorWindow:Create(name, parent, options) |
end |
-- Creates and returns a new icon selector frame (i.e., no window, search box, or buttons). |
function lib:CreateIconSelectorFrame(name, parent, options) |
Helpers.InitialInit() |
return IconSelectorFrame:Create(name, parent, options) |
end |
-- Creates and returns a new search object. |
function lib:CreateSearch(options) |
Helpers.InitialInit() |
return SearchObject:Create(options) |
end |
-- Returns the number of "macro" icons. This may go slow the first time it is run if icon filenames aren't yet loaded. |
function lib:GetNumMacroIcons() -- (was removed from the API, but can still be useful when you don't need filenames) |
Helpers.InitialInit() |
return #MACRO_ICON_FILENAMES |
end |
-- Returns the number of "item" icons. This may go slow the first time it is run if icon filenames aren't yet loaded. |
function lib:GetNumItemIcons() -- (was removed from the API, but can still be useful when you don't need filenames) |
Helpers.InitialInit() |
return #ITEM_ICON_FILENAMES |
end |
-- Returns the revision # of the loaded library instance. |
function lib:GetRevision() |
return MINOR_VERSION |
end |
-- Attempts to load the keyword library contained in the addon with the specified name. BUT - it will load the one |
-- at "AdvancedIconSelector-KeywordData" instead if (a) it's newer, or (b) the addon with the specified name cannot |
-- be found / loaded. |
-- |
-- You don't necessarily need to do this manually - it will automatically be done when an icon window / icon frame / |
-- search object is first used, assuming a keywordAddonName field is specified in options. |
function lib:LoadKeywords(addonName) |
-- Get the revision # of the specified addon (if it's enabled and loadable). |
local addonRevision = nil |
local addonLoadable = addonName and select(5, GetAddOnInfo(addonName)) |
if addonLoadable then |
addonRevision = tonumber(GetAddOnMetadata(addonName, "X-Revision")) |
end |
-- Then, get the revision # of the default library (if it's enabled and loadable). |
local defaultRevision = nil |
local defaultLoadable = select(5, GetAddOnInfo("AdvancedIconSelector-KeywordData")) |
if defaultLoadable then |
defaultRevision = tonumber(GetAddOnMetadata("AdvancedIconSelector-KeywordData", "X-Revision")) |
end |
-- Finally, get the revision that is already loaded. |
keywordLibrary = LibStub("LibAdvancedIconSelector-KeywordData-1.0", true) |
local currentRevision = keywordLibrary and tonumber(keywordLibrary:GetRevision()) -- (old revisions may yield a string) |
-- Load the specified addon if it's newer than the current library and at least as new as the default library. |
local source = nil |
if addonRevision and (not currentRevision or addonRevision > currentRevision) and (not defaultRevision or addonRevision >= defaultRevision) then |
LoadAddOn(addonName) |
source = addonName |
-- Otherwise, load the default library if it's newer than the current library. |
elseif defaultRevision and (not currentRevision or defaultRevision > currentRevision) then |
LoadAddOn("AdvancedIconSelector-KeywordData") |
source = "AdvancedIconSelector-KeywordData" |
end |
-- Whatever happens, update keywordLibrary to point to the currently loaded instance. |
keywordLibrary = LibStub("LibAdvancedIconSelector-KeywordData-1.0", true) |
if source then |
lib:Debug("Loaded keyword library revision:", keywordLibrary and keywordLibrary:GetRevision(), "source:", source) |
end |
end |
-- Looks up keywords for the given icon. Returns nil if no keywords exist, or the keyword library has not been loaded |
-- yet using LoadKeywords(). |
function lib:LookupKeywords(texture) |
return keywordLibrary and keywordLibrary:GetKeywords(texture) |
end |
function lib:ConvertMacroTexture(table, index) |
if ( not index ) then |
return; |
end |
if (lib.HiddenSlot == nil) then |
lib.HiddenSlot = CreateFrame("CheckButton"); |
lib.HiddenSlot.icon = lib.HiddenSlot:CreateTexture(); |
end |
local texture = table[index]; |
if (texture) then |
if (type(texture) == "number") then |
lib.HiddenSlot.icon:SetToFileData(texture); |
local texture = lib.HiddenSlot.icon:GetTexture(); |
if texture then |
return gsub(strupper(texture), "INTERFACE\\ICONS\\", ""); |
else |
return nil; |
end |
else |
return texture; |
end |
else |
return nil; |
end |
end |
-- ================================================================ |
-- ICON WINDOW IMPLEMENTATION |
-- Creates a new icon selector window, which includes an icon selector frame, search box, etc. |
-- See Readme.txt for a list of all supported options. |
function IconSelectorWindow:Create(name, parent, options) |
assert(name, "The icon selector window must have a name") |
if not parent then parent = UIParent end |
options = Helpers.ApplyDefaults(options, defaults) |
self = self:MixInto(CreateFrame("Frame", name, parent)) |
self:Hide() |
self:SetFrameStrata("MEDIUM") |
self:SetSize(options.width, options.height) |
self:SetMinResize(options.minResizeWidth, options.minResizeHeight) |
self:SetToplevel(true) |
self.options = options |
if options.customFrame then options.customFrame:SetParent(self) end |
self:SetBackdrop({ |
edgeFile = options.edgeFile, |
bgFile = options.bgFile, |
tile = options.tile, |
tileSize = options.tileSize, |
edgeSize = options.edgeSize, |
insets = options.insets }) |
if not options.noHeader then |
self.header = self:CreateTexture() |
self.header:SetTexture(options.headerTexture) |
self.header:SetWidth(options.headerWidth, 64) |
self.header:SetPoint("TOP", 0, 12) |
self.headerText = self:CreateFontString() |
self.headerText:SetFontObject(options.headerFont) |
self.headerText:SetPoint("TOP", self.header, "TOP", options.headerOffsetX, options.headerOffsetY) |
self.headerText:SetText(options.headerText) |
end |
if not options.noCloseButton then |
self.closeButton = CreateFrame("Button", nil, self, "UIPanelCloseButton") |
self.closeButton:SetPoint("TOPRIGHT", 0, 0) |
self.closeButton:SetScript("OnClick", function(...) |
if self.OnCancel then |
self:OnCancel(...) |
else |
self:Hide() |
end |
end) |
end |
if options.enableResize then self:SetResizable(true) end |
if options.enableMove then self:SetMovable(true) end |
if not options.allowOffscreen then self:SetClampedToScreen(true) end |
if options.enableResize or options.enableMove then self:EnableMouse(true) end |
self:RegisterForDrag("LeftButton") |
self:SetScript("OnDragStart", function(self, button) |
local x, y = self.mouseDownX, self.mouseDownY |
if x and y then |
local scale = UIParent:GetEffectiveScale() |
x = x / scale |
y = y / scale |
y = (y - self:GetBottom()) * scale |
x = (self:GetRight() - x) * scale |
-- (set anchorTo if you want your frame to conditionally be moveable / resizeable) |
if not options.anchorFrame or not options.anchorFrame:IsShown() then |
if options.enableResize and x < 20 and y < 20 then |
self:StartSizing() |
elseif options.enableMove then |
self:StartMoving() |
end |
end |
end |
end) |
self:SetScript("OnDragStop", function(self, button) |
self:StopMovingOrSizing() |
end) |
self:SetScript("OnMouseDown", function(self, button) |
if button == "LeftButton" then |
self.mouseDownX, self.mouseDownY = GetCursorPosition() |
end |
end) |
self:SetScript("OnMouseUp", function(self, button) |
if button == "LeftButton" then |
self.mouseDownX, self.mouseDownY = nil, nil |
end |
end) |
self.iconsFrame = lib:CreateIconSelectorFrame(name .. "_IconsFrame", self, options) |
self.searchLabel = self:CreateFontString() |
self.searchLabel:SetFontObject("GameFontNormal") |
self.searchLabel:SetText(L["Search:"]) |
self.searchLabel:SetHeight(22) |
self.searchBox = CreateFrame("EditBox", name .. "_SearchBox", self, "InputBoxTemplate") |
self.searchBox:SetAutoFocus(false) |
self.searchBox:SetHeight(22) |
self.searchBox:SetScript("OnTextChanged", function(editBox, userInput) |
if userInput then |
self.iconsFrame:SetSearchParameter(editBox:GetText()) |
end |
end) |
self.cancelButton = CreateFrame("Button", name .. "_Cancel", self, "UIPanelButtonTemplate") |
if options.okayCancel then |
self.cancelButton:SetText(L["Cancel"]) |
else |
self.cancelButton:SetText(L["Close"]) |
end |
self.cancelButton:SetSize(78, 22) |
self.cancelButton:SetScript("OnClick", function(...) |
if self.OnCancel then |
self:OnCancel(...) |
else |
self:Hide() |
end |
end) |
if options.okayCancel then |
self.okButton = CreateFrame("Button", name .. "_OK", self, "UIPanelButtonTemplate") |
self.okButton:SetText(L["Okay"]) |
self.okButton:SetSize(78, 22) |
self.okButton:SetScript("OnClick", function(...) |
if self.OnOkay then |
self:OnOkay(...) |
end |
end) |
end |
if options.visibilityButtons then |
self.visibilityButtons = { } |
for _, buttonInfo in ipairs(options.visibilityButtons) do |
local sectionName, buttonText = unpack(buttonInfo) |
local buttonName = name .. "_" .. sectionName .. "_Visibility" |
local button = CreateFrame("CheckButton", buttonName, self, "UICheckButtonTemplate") |
_G[buttonName .. "Text"]:SetText(buttonText) |
button:SetChecked(self.iconsFrame:GetSectionVisibility(sectionName)) |
button:SetSize(24, 24) |
button:SetScript("OnClick", function(button, mouseButton, down) |
self.iconsFrame:SetSectionVisibility(sectionName, button:GetChecked()) |
end) |
tinsert(self.visibilityButtons, button) |
end |
end |
self:SetScript("OnSizeChanged", self.private_OnWindowSizeChanged) |
return self |
end |
-- Provides additional script types for the icon selector WINDOW (not frame). |
function IconSelectorWindow:SetScript(scriptType, handler) |
if scriptType == "OnOkayClicked" then |
self.OnOkay = handler |
elseif scriptType == "OnCancelClicked" then |
self.OnCancel = handler |
else |
return self.superType.SetScript(self, scriptType, handler) |
end |
end |
-- Specifies a new search parameter, setting the text of the search box and starting the search. |
-- Setting immediateResults to true will eliminate the delay before the search actually starts. |
function IconSelectorWindow:SetSearchParameter(searchText, immediateResults) |
if searchText then |
self.searchBox:SetText(searchText) |
else |
self.searchBox:SetText("") |
end |
self.iconsFrame:SetSearchParameter(searchText, immediateResults) |
end |
-- Called when the size of the icon selector window changes. |
function IconSelectorWindow:private_OnWindowSizeChanged(width, height) |
local spacing = 4 |
local options = self.options |
local contentInsets = options.contentInsets |
if options.customFrame then |
options.customFrame:SetPoint("TOPLEFT", contentInsets.left, -contentInsets.top) |
options.customFrame:SetPoint("RIGHT", -contentInsets.right, 0) |
self.iconsFrame:SetPoint("TOPLEFT", options.customFrame, "BOTTOMLEFT", 0, -spacing) |
else |
self.iconsFrame:SetPoint("TOPLEFT", contentInsets.left, -contentInsets.top) |
end |
self.iconsFrame:SetPoint("RIGHT", -contentInsets.right, 0) |
self.cancelButton:SetPoint("BOTTOMRIGHT", -contentInsets.right, contentInsets.bottom) |
if self.okButton then |
self.okButton:SetPoint("BOTTOMRIGHT", self.cancelButton, "BOTTOMLEFT", -2, 0) |
end |
self.searchLabel:SetPoint("BOTTOMLEFT", contentInsets.left, contentInsets.bottom) |
self.searchBox:SetPoint("LEFT", self.searchLabel, "RIGHT", 6, 0) |
self.searchBox:SetPoint("RIGHT", self.okButton or self.cancelButton, "LEFT", -spacing, 0) |
local lastButton = nil |
-- Lay out the visibility buttons in a row |
if self.visibilityButtons then |
for _, button in ipairs(self.visibilityButtons) do |
if lastButton then |
button:SetPoint("LEFT", _G[lastButton:GetName() .. "Text"], "RIGHT", 2, 0) |
else |
button:SetPoint("BOTTOMLEFT", self.searchLabel, "TOPLEFT", -2, 0) |
end |
lastButton = button |
end |
end |
-- Attach the bottom of the icons frame |
if lastButton then |
self.iconsFrame:SetPoint("BOTTOM", lastButton, "TOP", 0, spacing) |
else |
self.iconsFrame:SetPoint("BOTTOM", self.cancelButton, "TOP", 0, spacing) |
end |
end |
-- ================================================================ |
-- ICON FRAME IMPLEMENTATION |
-- Creates a new icon selector frame (no window, search box, etc.) |
-- See Readme.txt for a list of all supported options. |
function IconSelectorFrame:Create(name, parent, options) |
assert(name, "The icon selector frame must have a name") |
options = Helpers.ApplyDefaults(options, defaults) |
self = self:MixInto(CreateFrame("Frame", name, parent)) |
self.scrollOffset = 0 |
self.iconsX = 1 |
self.iconsY = 1 |
self.fauxResults = 0 -- (fake results to keep top-left icon stationary when resizing) |
self.searchResults = { } |
self.icons = { } |
self.showDynamicText = options.showDynamicText |
self:SetScript("OnSizeChanged", self.private_OnIconsFrameSizeChanged) |
self:SetScript("OnShow", function(self) |
-- Call the BeforeShow handler (useful for replacing icon sections, etc.) |
if self.BeforeShow then self:BeforeShow() end |
-- Restart the search, since we stopped it when the frame was hidden. |
self.search:RestartSearch() |
end) |
-- Create the scroll bar |
self.scrollFrame = CreateFrame("ScrollFrame", name .. "_ScrollFrame", self, "FauxScrollFrameTemplate") |
self.scrollFrame:SetScript("OnVerticalScroll", function(scrollFrame, offset) |
if offset == 0 then self.fauxResults = 0 end -- Remove all faux results when the top of the list is hit. |
FauxScrollFrame_OnVerticalScroll(self.scrollFrame, offset, ICON_HEIGHT + ICON_SPACING, function() self:private_UpdateScrollFrame() end) |
end) |
-- Create the internal frame to display the icons |
self.internalFrame = CreateFrame("Frame", name .. "_Internal", self) |
self.internalFrame.parent = self |
self.internalFrame:SetScript("OnSizeChanged", self.private_OnInternalFrameSizeChanged) |
self.internalFrame:SetScript("OnHide", function(internalFrame) |
-- When the frame is hidden, immediately stop the search. |
self.search:Stop() |
-- Release any textures that were being displayed. |
for i = 1, #self.icons do |
local button = self.icons[i] |
if button then |
button:SetNormalTexture(nil) |
end |
end |
end) |
self.search = lib:CreateSearch(options) |
self.search.owner = self |
self.search:SetScript("OnSearchStarted", self.private_OnSearchStarted) |
self.search:SetScript("OnSearchResultAdded", self.private_OnSearchResultAdded) |
self.search:SetScript("OnSearchComplete", self.private_OnSearchComplete) |
self.search:SetScript("OnSearchTick", self.private_OnSearchTick) |
self.search:SetScript("OnIconScanned", self.private_OnIconScanned) |
-- Set the visibility of all sections |
for sectionName, _ in pairs(options.sections) do |
if options.sectionVisibility[sectionName] == false then -- FALSE ONLY, not nil. Sections are visible by default. |
self.search:ExcludeSection(sectionName, true) |
end |
end |
-- NOTE: Do not start the search until the frame is shown! Some addons may choose to create |
-- the frame early and not display it until later, and we don't want to load the keyword library early! |
return self |
end |
-- Called when a new search is started. |
function IconSelectorFrame.private_OnSearchStarted(search) |
local self = search.owner |
wipe(self.searchResults) |
self.updateNeeded = true |
self.resetScroll = true |
end |
-- Called each time a search result is found. |
function IconSelectorFrame.private_OnSearchResultAdded(search, texture, globalID, localID, kind) |
local self = search.owner |
tinsert(self.searchResults, globalID) |
self.updateNeeded = true |
end |
-- Called when the search is completed. |
function IconSelectorFrame.private_OnSearchComplete(search) |
local self = search.owner |
lib:Debug("Found " .. #self.searchResults .. " results") |
self.initialSelection = nil -- (if we didn't find the initial selection first time, we're not going to find it next time) |
end |
-- Called after each search tick. |
function IconSelectorFrame.private_OnSearchTick(search) |
local self = search.owner |
-- Update the icon display if new results have been found |
if self.updateNeeded then |
self.updateNeeded = false |
-- To reduce flashing, scroll to top JUST before calling private_UpdateScrollFrame() on the first search tick. |
if self.resetScroll then |
self.resetScroll = false |
self.fauxResults = 0 |
FauxScrollFrame_Update(self.scrollFrame, 1, 1, 1) -- (scroll to top) |
end |
self:private_UpdateScrollFrame() |
end |
end |
-- Called as each icon is passed in the search. |
function IconSelectorFrame.private_OnIconScanned(search, texture, globalID, localID, kind) |
local self = search.owner |
if self.initialSelection then |
assert(self.selectedID == nil) -- (user selection should have cleared the initial selection) |
-- If we find the texture we're looking for... |
if texture and strupper(texture) == strupper(self.initialSelection) then |
-- Set the selection. |
lib:Debug("Found selected texture at global index", globalID) |
self:SetSelectedIcon(globalID) |
assert(self.initialSelection == nil) -- (should have been cleared by SetSelectedIcon) |
end |
end |
end |
-- Updates the scroll frame and refreshes the main display based on current parameters. |
function IconSelectorFrame:private_UpdateScrollFrame() |
local maxLines = ceil((self.fauxResults + #self.searchResults) / self.iconsX) |
local displayedLines = self.iconsY |
local lineHeight = ICON_HEIGHT + ICON_SPACING |
FauxScrollFrame_Update(self.scrollFrame, maxLines, displayedLines, lineHeight) |
self.scrollOffset = FauxScrollFrame_GetOffset(self.scrollFrame) |
-- Update the icon display to match the new scroll offset |
self:private_UpdateIcons() |
end |
-- Specifies a new search parameter, restarting the search. |
-- Setting immediateResults to true will eliminate the delay before the search actually starts. |
function IconSelectorFrame:SetSearchParameter(searchText, immediateResults) |
self.search:SetSearchParameter(searchText, immediateResults) |
end |
-- Selects the icon at the given global index. Does not trigger an OnSelectedIconChanged event. |
function IconSelectorFrame:SetSelectedIcon(index) |
if self.selectedID ~= index then |
self.selectedID = index |
self.initialSelection = nil |
-- Fire an event. |
if self.OnSelectedIconChanged then self:OnSelectedIconChanged() end |
-- Update the icon display |
self:private_UpdateIcons() |
end |
end |
-- Selects the icon with the given filename (without the INTERFACE\\ICONS\\ prefix) |
-- (NOTE: the icon won't actually be selected until it's found) |
function IconSelectorFrame:SetSelectionByName(texture) |
self:SetSelectedIcon(nil) |
self.initialSelection = texture |
if texture then |
self.search:RestartSearch() |
else |
self:private_UpdateIcons() |
end |
end |
-- Returns information about the icon at the given global index, or nil if out of range. |
-- (Returns a tuple of id (within section), kind, texture) |
function IconSelectorFrame:GetIconInfo(index) |
return self.search:GetIconInfo(index, self) |
end |
-- Returns the ID of the selected icon. |
function IconSelectorFrame:GetSelectedIcon() |
return self.selectedID |
end |
-- Provides additional script types for the icon selector FRAME (not window). |
function IconSelectorFrame:SetScript(scriptType, handler) |
if scriptType == "OnSelectedIconChanged" then -- Called when the selected icon is changed |
self.OnSelectedIconChanged = handler |
elseif scriptType == "OnButtonUpdated" then -- Hook for the icon keyword editor (IKE) button overlays |
self.OnButtonUpdated = handler |
elseif scriptType == "BeforeShow" then -- Called just before the window is shown - useful for replacing icon sections, etc. |
self.BeforeShow = handler |
else |
return self.superType.SetScript(self, scriptType, handler) |
end |
end |
-- Selects the next icon. Used by the icon keyword editor, and not intended for external use. |
-- (i.e., it doesn't take the current search filter into account) |
-- If you'd like me to officially include such a feature, please email me. |
function IconSelectorFrame:unofficial_SelectNextIcon() |
if self.selectedID then |
self:SetSelectedIcon(self.search:private_Skip(self.selectedID + 1)) |
self:private_UpdateIcons() |
end |
end |
-- Replaces the specified section of icons. Useful to change the icons within a custom section. |
-- Also, causes the search to start over. |
-- (see CreateDefaultSection for example section definitions) |
-- (also, see EquipmentSetPopup.lua and MacroPopup.lua of AdvancedIconSelector for an example of actual use) |
function IconSelectorFrame:ReplaceSection(sectionName, section) |
self.search:ReplaceSection(sectionName, section) |
end |
-- Shows or hides the icon section with the given name. |
function IconSelectorFrame:SetSectionVisibility(sectionName, visible) |
self.search:ExcludeSection(sectionName, not visible) |
self.search:RestartSearch() |
end |
-- Returns true if the icon section with the given name is visible, or false otherwise. |
function IconSelectorFrame:GetSectionVisibility(sectionName) |
return not self.search:IsSectionExcluded(sectionName) |
end |
-- (private) Called when the icon frame's size has changed. |
function IconSelectorFrame:private_OnIconsFrameSizeChanged(width, height) |
self.scrollFrame:SetPoint("TOP", self) |
self.scrollFrame:SetPoint("BOTTOMRIGHT", self, -21, 0) |
self.internalFrame:SetPoint("TOPLEFT", self) |
self.internalFrame:SetPoint("BOTTOMRIGHT", self, -16, 0) |
end |
-- (private) Called when the internal icon frame's size has changed (i.e., the part without the scroll bar) |
function IconSelectorFrame.private_OnInternalFrameSizeChanged(internalFrame, width, height) |
local self = internalFrame.parent |
local oldFirstIcon = 1 + self.scrollOffset * self.iconsX - self.fauxResults |
self.iconsX = floor((floor(width + 0.5) - 2 * ICON_PADDING + ICON_SPACING) / (ICON_WIDTH + ICON_SPACING)) |
self.iconsY = floor((floor(height + 0.5) - 2 * ICON_PADDING + ICON_SPACING) / (ICON_HEIGHT + ICON_SPACING)) |
-- Center the icons |
local leftPadding = (width - 2 * ICON_PADDING - (self.iconsX * (ICON_WIDTH + ICON_SPACING) - ICON_SPACING)) / 2 |
local topPadding = (height - 2 * ICON_PADDING - (self.iconsY * (ICON_HEIGHT + ICON_SPACING) - ICON_SPACING)) / 2 |
local lastIconY = nil |
for y = 1, self.iconsY do |
local lastIconX = nil |
for x = 1, self.iconsX do |
local i = (y - 1) * self.iconsX + x |
-- Create the button if it doesn't exist (but don't set its normal texture yet) |
local button = self.icons[i] |
if not button then |
button = CreateFrame("CheckButton", nil, self.internalFrame) |
self.icons[i] = button |
button:SetSize(36, 36) |
button:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square") |
button:SetCheckedTexture("Interface\\Buttons\\CheckButtonHilight") |
button:SetScript("OnClick", function(button, mouseButton, down) |
if button.textureKind and button.textureID then |
if self.selectedButton then |
self.selectedButton:SetChecked(false) |
end |
button:SetChecked(true) |
self.selectedButton = button |
self:SetSelectedIcon(button.globalID) |
else |
button:SetChecked(false) |
end |
end) |
button:SetScript("OnEnter", function(button, motion) |
if button.texture then |
local keywordString = lib:LookupKeywords(button.texture) |
local keywords = Helpers.GetTaggedStrings(keywordString, nil) |
local spells = Helpers.GetTaggedStrings(keywordString, "spell") |
GameTooltip:SetOwner(button, "ANCHOR_TOPRIGHT") |
GameTooltip:ClearLines() |
if button.textureKind == "Equipment" then |
GameTooltip:AddDoubleLine(NORMAL_FONT_COLOR_CODE .. L["Equipped item texture:"] .. FONT_COLOR_CODE_CLOSE, GRAY_FONT_COLOR_CODE .. tostring(button.textureID) .. FONT_COLOR_CODE_CLOSE) |
elseif button.textureKind == "Macro" then |
GameTooltip:AddDoubleLine(NORMAL_FONT_COLOR_CODE .. L["Macro texture:"] .. FONT_COLOR_CODE_CLOSE, GRAY_FONT_COLOR_CODE .. tostring(button.textureID) .. FONT_COLOR_CODE_CLOSE) |
elseif button.textureKind == "Item" then |
GameTooltip:AddDoubleLine(NORMAL_FONT_COLOR_CODE .. L["Item texture:"] .. FONT_COLOR_CODE_CLOSE, GRAY_FONT_COLOR_CODE .. tostring(button.textureID) .. FONT_COLOR_CODE_CLOSE) |
elseif button.textureKind == "Spell" then |
GameTooltip:AddDoubleLine(NORMAL_FONT_COLOR_CODE .. L["Spell texture:"] .. FONT_COLOR_CODE_CLOSE, GRAY_FONT_COLOR_CODE .. tostring(button.textureID) .. FONT_COLOR_CODE_CLOSE) |
elseif button.textureKind == "Dynamic" then |
GameTooltip:AddLine(NORMAL_FONT_COLOR_CODE .. L["Default / dynamic texture:"] .. FONT_COLOR_CODE_CLOSE) |
end |
GameTooltip:AddLine(tostring(button.texture), 1, 1, 1) |
Helpers.AddTaggedInformationToTooltip(keywordString, "spell", L["Spell: "], NORMAL_FONT_COLOR) |
Helpers.AddTaggedInformationToTooltip(keywordString, "companion", L["Companion: "], NORMAL_FONT_COLOR) |
Helpers.AddTaggedInformationToTooltip(keywordString, "mount", L["Mount: "], NORMAL_FONT_COLOR) |
Helpers.AddTaggedInformationToTooltip(keywordString, "talent", L["Talent: "], NORMAL_FONT_COLOR) |
Helpers.AddTaggedInformationToTooltip(keywordString, "tab", L["Tab: "], NORMAL_FONT_COLOR) |
Helpers.AddTaggedInformationToTooltip(keywordString, "passive", L["Passive: "], NORMAL_FONT_COLOR) |
Helpers.AddTaggedInformationToTooltip(keywordString, "racial", L["Racial: "], NORMAL_FONT_COLOR) |
Helpers.AddTaggedInformationToTooltip(keywordString, "racial_passive", L["Racial Passive: "], NORMAL_FONT_COLOR) |
Helpers.AddTaggedInformationToTooltip(keywordString, "mastery", L["Mastery: "], NORMAL_FONT_COLOR) |
Helpers.AddTaggedInformationToTooltip(keywordString, "professions", L["Professions: "], NORMAL_FONT_COLOR) |
Helpers.AddTaggedInformationToTooltip(keywordString, "pet_command", L["Pet Command: "], NORMAL_FONT_COLOR) |
Helpers.AddTaggedInformationToTooltip(keywordString, "pet_stance", L["Pet Stance: "], NORMAL_FONT_COLOR) |
if keywords and strlen(keywords) > 0 then |
GameTooltip:AddLine(GRAY_FONT_COLOR_CODE .. L["Additional keywords: "] .. tostring(keywords) .. FONT_COLOR_CODE_CLOSE, 1, 1, 1, true) |
end |
GameTooltip:Show() |
end |
end) |
button:SetScript("OnLeave", function(button, motion) |
GameTooltip:Hide() |
end) |
end |
button:Show() -- (yes, this is necessary; we hide excess buttons) |
if not lastIconX then |
if not lastIconY then |
button:SetPoint("TOPLEFT", self.internalFrame, leftPadding + ICON_PADDING, -topPadding - ICON_PADDING) |
else |
button:SetPoint("TOPLEFT", lastIconY, "BOTTOMLEFT", 0, -ICON_SPACING) |
end |
lastIconY = button |
else |
button:SetPoint("TOPLEFT", lastIconX, "TOPRIGHT", ICON_SPACING, 0) |
end |
lastIconX = button |
end |
end |
-- Hide any excess buttons. Release the textures, but keep the buttons 'til the window is closed. |
for i = self.iconsY * self.iconsX + 1, #self.icons do |
local button = self.icons[i] |
if button then |
button:SetNormalTexture(nil) |
button:Hide() |
end |
end |
-- Add padding at the top to make the old and new first icon constant |
local newFirstIcon = 1 + self.scrollOffset * self.iconsX - self.fauxResults |
self.fauxResults = self.fauxResults + newFirstIcon - oldFirstIcon |
-- Increase faux results if below 0 |
if self.fauxResults < 0 then |
local scrollDown = -ceil((self.fauxResults + 1) / self.iconsX) + 1 -- careful! Lots of OBOBs here if not done right. |
self.fauxResults = self.fauxResults + scrollDown * self.iconsX |
assert(self.fauxResults >= 0) |
local newOffset = max(FauxScrollFrame_GetOffset(self.scrollFrame) + scrollDown, 0) |
FauxScrollFrame_SetOffset(self.scrollFrame, newOffset) |
end |
-- Decrease faux results if above iconsX |
if self.fauxResults > self.iconsX and self.iconsX > 0 then |
local scrollUp = floor(self.fauxResults / self.iconsX) |
self.fauxResults = self.fauxResults - scrollUp * self.iconsX |
assert(self.fauxResults < self.iconsX) |
local newOffset = max(FauxScrollFrame_GetOffset(self.scrollFrame) - scrollUp, 0) |
FauxScrollFrame_SetOffset(self.scrollFrame, newOffset) |
end |
self:private_UpdateScrollFrame() |
end |
-- Refreshes the icon display. |
function IconSelectorFrame:private_UpdateIcons() |
if self:IsShown() then |
local firstIcon = 1 + self.scrollOffset * self.iconsX - self.fauxResults |
local last = self.iconsX * self.iconsY |
if self.selectedButton then |
self.selectedButton:SetChecked(false) |
self.selectedButton = nil |
end |
for i = 1, last do |
local button = self.icons[i] |
if button then |
local resultIndex = firstIcon + i - 1 |
if self.searchResults[resultIndex] then |
button.globalID = self.searchResults[resultIndex] |
button.textureID, button.textureKind, button.texture = self:GetIconInfo(button.globalID) |
if button.globalID == self.selectedID then |
button:SetChecked(true) |
self.selectedButton = button |
end |
if self.showDynamicText then |
if button.textureKind == "Dynamic" then |
if not button.dynamicText then |
button.dynamicText = button:CreateFontString() |
button.dynamicText:SetFontObject("GameFontNormalSmall") |
button.dynamicText:SetPoint("BOTTOM", button, "BOTTOM", 0, 2) |
button.dynamicText:SetText("(dynamic)") |
end |
button.dynamicText:Show() |
else |
if button.dynamicText then button.dynamicText:Hide() end |
end |
end |
else |
button.globalID = nil |
button.textureID = nil |
button.texture = nil |
button.textureKind = nil |
button:SetChecked(false) |
if button.dynamicText then button.dynamicText:Hide() end |
end |
if button.texture then |
button:SetNormalTexture("Interface\\Icons\\" .. button.texture) |
else |
button:SetNormalTexture(nil) |
end |
-- Hook for the icon keyword editor (IKE) overlay |
if self.OnButtonUpdated then self.OnButtonUpdated(button) end |
end |
end |
end |
end |
-- ======================================================================================== |
-- HELPER FUNCTIONS |
-- To prevent slow loading time, don't have WoW traverse the icons directory until an |
-- icon selector is actually created. |
function Helpers.InitialInit() |
if not initialized then |
initialized = true |
GetMacroIcons(MACRO_ICON_FILENAMES) |
GetMacroItemIcons(ITEM_ICON_FILENAMES) |
end |
end |
-- Creates a new object that is the overlay of "options" onto "defaults", and applies |
-- a few dynamic defaults as well. |
function Helpers.ApplyDefaults(options, defaults) |
if not options then options = { } end -- (yes, some addons pass no options) |
local result = { } |
setmetatable(result, { |
__index = function(t, k) -- (note: do NOT index t from __index or it may loop) |
local r = options[k] |
if r ~= nil then -- (don't use "options[k] or defaults[k]" or false won't work) |
return r |
else |
return defaults[k] |
end |
end |
}) |
-- Create any sections that weren't explicitly defined by the user. |
for _, sectionName in ipairs(result.sectionOrder) do |
if not result.sections[sectionName] then |
result.sections[sectionName] = Helpers.CreateDefaultSection(sectionName) |
end |
end |
-- 2012-03-20 COMPATIBILITY ISSUE: LibAdvancedIconSelector-1.0 revision 7 (v1.0.4) and above vs. AdvancedIconSelector v1.0.3 and below |
-- Old versions of AdvancedIconSelector relied on options.width and options.height being set to the default width / height upon return. |
-- THIS BEHAVIOR WILL BE REMOVED IN THE FUTURE, so don't rely on it or your addon WILL break. |
if not options.width then options.width = defaults.width end |
if not options.height then options.height = defaults.height end |
return result |
end |
-- Returns the strings that have the given tag (filters out the tags if nil) |
function Helpers.GetTaggedStrings(str, tag) |
if not str then return nil end |
if not tag then |
return gsub(str, "[^ ]+:[^ ]+[ ]*", "") |
else |
local result = nil -- (nil is by far the most common result - so don't return an empty table) |
for match in gmatch(" " .. str, " " .. tag .. ":([^ ]+)") do if not result then result = { match } else tinsert(result, match) end end |
return result |
end |
end |
-- Re-capitalizes each word of a string, excluding certain prepositions. This is needed 'cause |
-- all keyword data is lowercase (for quick searching). |
function Helpers.Capitalize(str) |
local s, e, cap = 0, 0 |
repeat |
s, e, cap = strfind(str, "([^ %-%.%(%)]+)[ %-%.%(%)]*", e + 1) |
if e then |
if s == 1 or not PREPS[cap] then |
str = strsub(str, 1, s - 1) .. strupper(strsub(str, s, s)) .. strsub(str, s + 1) |
end |
end |
until not e |
return str |
end |
-- Converts a keyword string like "ice_lance(mage)" into "Ice Lance (Mage)", r, g, b |
function Helpers.MakeSpellTooltipString(str, defaultColor) |
str = gsub(str, "_", " ") -- Re-insert spaces |
local spellName, className = strmatch(str, "^(.+)%(([^%)]+)%)$") |
if not className then spellName = str end -- Sometimes class isn't specified. |
if spellName then |
-- Capitalize the first letter of each word, excluding certain prepositions. |
spellName = Helpers.Capitalize(spellName) |
if className then -- (note: class name is sometimes also a profession name, race name, etc.) |
className = Helpers.Capitalize(className) |
local classColorIndex = strupper(className) |
if classColorIndex == "DEATH KNIGHT" then classColorIndex = "DEATHKNIGHT" end |
local classColor = RAID_CLASS_COLORS[classColorIndex] |
if classColor then |
return spellName .. " (" .. className .. ")", classColor.r, classColor.g, classColor.b |
else |
return spellName .. " (" .. className .. ")", defaultColor.r, defaultColor.g, defaultColor.b -- invalid / multiple classes |
end |
end |
return spellName, defaultColor.r, defaultColor.g, defaultColor.b |
end |
end |
function Helpers.AddTaggedInformationToTooltip(keywordString, tag, tooltipTag, defaultColor) |
local spellNames = Helpers.GetTaggedStrings(keywordString, tag) |
if spellNames then |
for _,spellBundle in ipairs(spellNames) do |
local spellName, classR, classG, classB = Helpers.MakeSpellTooltipString(spellBundle, defaultColor) |
GameTooltip:AddLine(tooltipTag .. tostring(spellName), classR, classG, classB, false) |
end |
end |
end |
-- Creates one of several sections that don't have to be defined by the user. |
function Helpers.CreateDefaultSection(name) |
if name == "DynamicIcon" then |
return { count = 1, GetIconInfo = function(index) return index, "Dynamic", "INV_Misc_QuestionMark" end } |
elseif name == "MacroIcons" then |
return { count = #MACRO_ICON_FILENAMES, GetIconInfo = function(index) return index, "Macro", lib:ConvertMacroTexture(MACRO_ICON_FILENAMES, index) end } |
elseif name == "ItemIcons" then |
return { count = #ITEM_ICON_FILENAMES, GetIconInfo = function(index) return index, "Item", lib:ConvertMacroTexture(ITEM_ICON_FILENAMES, index) end } |
end |
end |
-- ================================================================ |
-- SEARCH OBJECT IMPLEMENTATION |
-- Creates a new search object based on the specified options. |
function SearchObject:Create(options) |
options = Helpers.ApplyDefaults(options, defaults) |
local search = SearchObject:Derive() |
search.options = options |
search.sections = options.sections |
search.sectionOrder = options.sectionOrder |
search.firstSearch = true |
search.shouldSkip = { } |
return search |
end |
-- Provides a callback for an event, with error checking. |
function SearchObject:SetScript(script, callback) |
if script == "BeforeSearchStarted" then -- Called just before the search is (re)started. Parameters: (search) |
self.BeforeSearchStarted = callback |
elseif script == "OnSearchStarted" then -- Called when the search is (re)started. Parameters: (search) |
self.OnSearchStarted = callback |
elseif script == "OnSearchResultAdded" then -- Called for each search result found. Parameters: (search, texture, globalID, localID, kind) |
self.OnSearchResultAdded = callback |
elseif script == "OnSearchComplete" then -- Called when the search is completed. Parameters: (search) |
self.OnSearchComplete = callback |
elseif script == "OnIconScanned" then -- Called for each icon scanned. Parameters: (search, texture, globalID, localID, kind) |
self.OnIconScanned = callback |
elseif script == "OnSearchTick" then -- Called after each search tick (or at a constant rate, if the search is not tick-based). Parameters: (search) |
self.OnSearchTick = callback |
else |
error("Unsupported script type") |
end |
end |
-- Sets the search parameter and restarts the search. Since this is generally called many times in a row |
-- (whenever a textbox is changed), the search is delayed by about half a second unless immediateResults is |
-- set to true, except for the first search performed with this search object. |
function SearchObject:SetSearchParameter(searchText, immediateResults) |
self.searchParameter = searchText |
if self.initiateSearchTimer then lib:CancelTimer(self.initiateSearchTimer) end |
local delay = (immediateResults or self.firstSearch) and 0 or INITIATE_SEARCH_DELAY |
self.firstSearch = false |
self.initiateSearchTimer = lib:ScheduleTimer(self.private_OnInitiateSearchTimerElapsed, delay, self) |
end |
-- Returns the current search parameter. |
function SearchObject:GetSearchParameter() |
return self.searchParameter |
end |
-- Replaces the specified section of icons. Useful to change the icons within a custom section. |
-- Also, causes the search to start over. |
-- (see CreateDefaultSection for example section definitions) |
-- (also, see EquipmentSetPopup.lua and MacroPopup.lua of AdvancedIconSelector for an example of actual use) |
function SearchObject:ReplaceSection(sectionName, section) |
self.sections[sectionName] = section |
self:RestartSearch() |
end |
-- Sets whether or not icons of the given section will be skipped. This does not restart the search, so you |
-- may wish to call RestartSearch() afterward. |
function SearchObject:ExcludeSection(sectionName, exclude) |
self.shouldSkip[sectionName] = exclude |
end |
-- Returns whether or not the icons of the given section will be skipped. |
function SearchObject:IsSectionExcluded(sectionName) |
return self.shouldSkip[sectionName] |
end |
-- Called when the search actually starts - usually about half a second after the search parameter is changed. |
function SearchObject:private_OnInitiateSearchTimerElapsed() |
self.initiateSearchTimer = nil -- (single-shot timer handles become invalid IMMEDIATELY after elapsed) |
self:RestartSearch() |
end |
-- Restarts the search. |
function SearchObject:RestartSearch() |
if self.BeforeSearchStarted then self.BeforeSearchStarted(self) end |
-- Load / reload the keyword library. |
-- (if keywordAddonName isn't specified, the default will be loaded) |
lib:LoadKeywords(self.options.keywordAddonName) |
-- Cancel any pending restart; we don't want to start twice. |
if self.initiateSearchTimer then |
lib:CancelTimer(self.initiateSearchTimer) |
self.initiateSearchTimer = nil |
end |
-- Parse the search parameter |
if self.searchParameter then |
local tmp = self:private_FixSearchParameter(self.searchParameter) |
local parts = { strsplit(";", tmp) } |
for i = 1, #parts do |
local tmp = strtrim(parts[i]) |
parts[i] = { } |
for v in gmatch(tmp, "[^ ,]+") do |
tinsert(parts[i], v) |
end |
end |
self.parsedParameter = parts |
else |
self.parsedParameter = nil |
end |
-- Start at the icon with global ID of 1 |
self.searchIndex = 0 |
if self.OnSearchStarted then self.OnSearchStarted(self) end |
if not self.searchTimer then |
self.searchTimer = lib:ScheduleRepeatingTimer(self.private_OnSearchTick, SCAN_TICK, self) |
end |
end |
-- Immediately terminates any running search. You can restart the search by calling RestartSearch(), |
-- but it will start at the beginning. |
function SearchObject:Stop() |
-- Stop any pending search. |
if self.initiateSearchTimer then |
lib:CancelTimer(self.initiateSearchTimer) |
self.initiateSearchTimer = nil -- (timer handles are invalid once canceled) |
end |
-- Cancel any occurring search. |
if self.searchTimer then |
lib:CancelTimer(self.searchTimer) |
self.searchTimer = nil -- (timer handles are invalid once canceled) |
end |
end |
-- Called on every tick of a search. |
function SearchObject:private_OnSearchTick() |
for entriesScanned = 0,SCAN_PER_TICK do |
self.searchIndex = self.searchIndex + 1 |
self.searchIndex = self:private_Skip(self.searchIndex) |
-- Is the search complete? |
if not self.searchIndex then |
lib:CancelTimer(self.searchTimer) |
self.searchTimer = nil -- timer handles are invalid once canceled |
if self.OnSearchComplete then self:OnSearchComplete() end |
break |
end |
local id, kind, texture = self:GetIconInfo(self.searchIndex) |
if self.OnIconScanned then self:OnIconScanned(texture, self.searchIndex, id, kind) end |
if texture then |
local keywordString = lib:LookupKeywords(texture) |
if self:private_Matches(texture, keywordString, self.parsedParameter) then |
if self.OnSearchResultAdded then self:OnSearchResultAdded(texture, self.searchIndex, id, kind) end |
end |
end |
end |
-- Notify that a search tick has occurred. |
if self.OnSearchTick then self:OnSearchTick() end |
end |
-- Returns the given global ID after skipping the designated categories, or nil if past the max global id. |
function SearchObject:private_Skip(id) |
local origID = id |
if not id or id < 1 then |
return nil |
end |
local sectionStart = 1 |
for _, sectionName in ipairs(self.sectionOrder) do |
local section = self.sections[sectionName] |
if section then |
if id >= 1 and id <= section.count then |
if self.shouldSkip[sectionName] then |
id = section.count + 1 |
else |
return sectionStart + (id - 1) |
end |
end |
id = id - section.count |
sectionStart = sectionStart + section.count |
end |
end |
return nil |
end |
-- Lowercases a string, but preserves the case of any characters after a %. |
function SearchObject:private_StrlowerPattern(str) |
local lastE = -1 -- (since -1 + 2 = 1) |
local result = "" |
repeat |
local s, e = strfind(str, "%", lastE + 2, true) |
if e then |
local nextLetter = strsub(str, e + 1, e + 1) |
result = result .. strlower(strsub(str, lastE + 2, e - 1)) .. "%" .. nextLetter |
else |
result = result .. strlower(strsub(str, lastE + 2)) |
end |
lastE = e |
until not e |
return result |
end |
-- This function makes up for LUA's primitive string matching support and replaces all occurrances of a word |
-- in a string, regardless of whether it's at the beginning, middle, or end. This is extremely inefficient, so you |
-- should only do it once: when the search parameter changes. |
function SearchObject:private_ReplaceWord(str, word, replacement) |
local n |
str = gsub(str, "^" .. word .. "$", replacement) -- (entire string) |
str = gsub(str, "^" .. word .. " ", replacement .. " ") -- (beginning of string) |
str = gsub(str, " " .. word .. "$", " " .. replacement) -- (end of string) |
repeat |
str, n = gsub(str, " " .. word .. " ", " " .. replacement .. " ") -- (middle of string) |
until not n or n == 0 |
return str |
end |
-- Coerces a search parameter to something useable. Replaces NOT with !, etc. |
function SearchObject:private_FixSearchParameter(parameter) |
-- Trim the parameter |
parameter = strtrim(parameter) |
-- Lowercase the string except for stuff after % signs. (since you can search by pattern) |
parameter = self:private_StrlowerPattern(parameter) |
-- Replace all "NOT" with ! |
parameter = self:private_ReplaceWord(parameter, "not", "!") |
-- Replace all "AND" with , |
parameter = self:private_ReplaceWord(parameter, "and", " ") |
-- Replace all "OR" with ; |
parameter = self:private_ReplaceWord(parameter, "or", ";") |
-- Join any !s to the word that follows it. (but only if the ! is standing on it's own) |
repeat |
local n1, n2 |
parameter, n1 = gsub(parameter, "^(!+) +", "%1") |
parameter, n2 = gsub(parameter, " (!+) +", " %1") |
until n1 == 0 and n2 == 0 |
-- Get rid of quotes; they have no meaning as of now and are used only to allow searching for "AND", "OR", and "NOT". |
parameter = gsub(parameter, '"+', "") |
-- Finally, get rid of any extra spaces |
parameter = gsub(strtrim(parameter), " +", " ") |
return parameter |
end |
-- Returns true if the given texture / keywords matches the search parameter |
function SearchObject:private_Matches(texture, keywords, parameter) |
if not parameter then return true end |
texture = strlower(texture) |
keywords = keywords and strlower(keywords) |
for i = 1, #parameter do -- OR parameters |
local p_i = parameter[i] |
local termFailed = false |
for j = 1, #p_i do -- AND parameters |
local s = p_i[j] |
if #s > 0 then |
local xor = 0 |
local plainText = true |
while strsub(s, 1, 1) == "!" do -- ! indicates negation of this term |
s = strsub(s, 2) |
xor = bit.bxor(xor, 1) |
end |
if strsub(s, 1, 1) == "=" then -- = indicates pattern matching for this term |
s = strsub(s, 2) |
plainText = false |
end |
local ok1, result1 = pcall(strfind, texture, s, 1, plainText) |
local result2 = false |
if keywords then result2 = select(2, pcall(strfind, keywords, s, 1, plainText)) end |
if not ok1 or bit.bxor((result1 or result2) and 1 or 0, xor) == 0 then |
termFailed = true |
break |
end |
end |
end |
if not termFailed then |
return true |
end |
end |
end |
-- Returns information about the icon at the given global index, or nil if out of range. |
-- (Returns a tuple of id (within section), kind, texture) |
function SearchObject:GetIconInfo(id) |
if not id or id < 1 then |
return nil |
end |
for _, sectionName in ipairs(self.sectionOrder) do |
local section = self.sections[sectionName] |
if section then |
if id >= 1 and id <= section.count then |
return section.GetIconInfo(id) -- (returns 3 values: id, kind, texture) |
else |
id = id - section.count |
end |
end |
end |
return nil |
end |
--[[======================================================================================== |
LibAdvancedIconSelector provides a searchable icon selection GUI to World |
of Warcraft addons. |
Copyright (c) 2011 David Forrester (Darthyl of Bronzebeard-US) |
Email: darthyl@hotmail.com |
Permission is hereby granted, free of charge, to any person obtaining a copy |
of this software and associated documentation files (the "Software"), to deal |
in the Software without restriction, including without limitation the rights |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
copies of the Software, and to permit persons to whom the Software is |
furnished to do so, subject to the following conditions: |
The above copyright notice and this permission notice shall be included in |
all copies or substantial portions of the Software. |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
THE SOFTWARE. |
========================================================================================]] |
--[[======================================================================================== |
Please notify me if you wish to make use of this library in your addon! |
I need to know for testing purposes - to make sure that any changes I make |
aren't going to break someone else's addon! |
========================================================================================]] |
local L = LibStub:GetLibrary("AceLocale-3.0"):NewLocale("LibAdvancedIconSelector-1.0", "enUS", true) |
if not L then return end |
-- Note: Although the icon selector may be localized, the search feature will still operate on english filenames and keywords. |
-- Main window |
L["FRAME_TITLE"] = "Icon Browser" |
L["Search:"] = true |
L["Okay"] = true |
L["Cancel"] = true |
L["Close"] = true |
-- Tooltips |
L["Macro texture:"] = true |
L["Item texture:"] = true |
L["Equipped item texture:"] = true |
L["Spell texture:"] = true |
L["Default / dynamic texture:"] = true |
L["Additional keywords: "] = true |
L["Spell: "] = true |
L["Companion: "] = true |
L["Mount: "] = true |
L["Talent: "] = true |
L["Tab: "] = true |
L["Passive: "] = true |
L["Racial: "] = true |
L["Racial Passive: "] = true |
L["Mastery: "] = true |
L["Professions: "] = true |
L["Pet Command: "] = true |
L["Pet Stance: "] = true |
## Interfact: 60200 |
## Interface: 60200 |
## Title: Bear in Mind |
## Author: Seerah |
## Notes: An addon to remind you of things. |
## Version: 0.0.1 |
## OptionalDeps: Ace3, LibSharedMedia-3.0, AceGUI-3.0-SharedMediaWidgets |
## Version: 0.1 |
## OptionalDeps: Ace3, LibSharedMedia-3.0, AceGUI-3.0-SharedMediaWidgets, LibAdvancedIconSelector-1.0 |
## SavedVariables: BearInMindDB |
## SavedVariablesPerCharacter: BearInMindPCDB |
libs\AceConfig-3.0\AceConfig-3.0.xml |
libs\LibSharedMedia-3.0\lib.xml |
libs\AceGUI-3.0-SharedMediaWidgets\widget.xml |
libs\AceLocale-3.0\AceLocale-3.0.xml |
libs\AceTimer-3.0\AceTimer-3.0.xml |
libs\LibAdvancedIconSelector-1.0\LibAdvancedIconSelector-1.0.xml |
BiM_options.lua |
BiM.lua |
local addonName, BiM = ... |
local f = CreateFrame("Frame") |
local LSM = LibStub("LibSharedMedia-3.0") |
local widgetLists = AceGUIWidgetLSMlists |
local AIS = LibStub("LibAdvancedIconSelector-1.0") |
local AISoptions, AISwindow = {} |
local ACR = LibStub("AceConfigRegistry-3.0") |
local tinsert = table.insert |
local tremove = table.remove |
local strformat = string.format |
local anchor, eventFrame, lines, db, pcdb |
local zr, er, dr, tr, lr, cr --shortcuts to db.reminders.zone, etc. |
local zre, ere, dre, tre, lre, cre --save which reminder is currently being edited |
local dropdownContents = {} --what to display in the edit dropdown on the current tab |
BiM.dayMap = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"} |
local sampleData = { |
icon = "Interface\\AddOns\\Bear in Mind\\BiM-icon", |
text = "Remember to be awesome!", |
--sound |
} |
local defaults = { --lock, scale, font, color |
lock = false, |
anchor = "TOPLEFT", |
offsetX = 15, |
offsetY = -100, |
scale = 1, |
font = "Friz Quadrata TT", |
fSize = 14, |
fColor = {r=1, g=1, b=1}, |
} |
local function SaveIcon(reminder) |
local id = AISwindow.icons:GetSelectedIcon() |
if id then |
local id, kind, texture = AISwindow.icons:GetIconInfo(id) |
reminder.data.icon = strformat("Interface\\Icons\\%s", texture) |
reminder.data.iconID = id --saved for selecting current icon when editing a reminder later |
AISwindow:Hide() |
ACR:NotifyChange(addonName) |
end |
end |
local function OpenIconBrowser(reminder) |
if not AISwindow then |
AISwindow = AIS:CreateIconSelectorWindow("BearinMind_IconSelection", InterfaceOptionsFrame, AISoptions) |
AISwindow.icons = BearinMind_IconSelection_IconsFrame |
AISwindow:SetPoint("CENTER") |
AISwindow:SetFrameStrata("DIALOG") |
AISwindow:SetScript("OnOkayClicked", function() |
SaveIcon(reminder) |
end) |
end |
AISwindow:Show() |
if reminder.data.iconID then |
AISwindow.icons:SetSelectedIcon(reminder.data.iconID) |
end |
end |
local options = { |
name = "Bear in Mind Options", |
type = "group", |
childGroups = "tab", |
args = { |
sample = { |
name = "Sample Reminder", |
desc = "See an example reminder in your list if you have none showing.", |
type = "execute", |
func = function() |
BiM.DisplayReminder(sampleData, true) |
end, |
order = 1, |
}, |
lock = { |
name = "Lock Anchor", |
desc = "If unlocked, an anchor will be shown allowing you to move the reminder list.", |
type = "toggle", |
get = function() return db.lock end, |
set = function() |
db.lock = not db.lock |
if db.lock then |
anchor.bg:SetTexture(0,0,1,0) |
anchor:EnableMouse(false) |
anchor:SetMovable(true) |
else |
anchor.bg:SetTexture(0,0,1,.4) |
anchor:EnableMouse(true) |
anchor:SetMovable(true) |
end |
end, |
order = 2, |
}, |
scale = { |
name = "Scale", |
desc = "Scale the reminders list up or down.", |
type = "range", |
min = 0.5, |
max = 2, |
step = 0.1, |
get = function() return db.scale end, |
set = function(_, scale) |
db.scale = scale |
anchor:SetScale(scale) |
end, |
order = 3, |
}, |
font = { |
name = "Font", |
desc = "Select the font for the reminder text.", |
type = "select", |
dialogControl = "LSM30_Font", |
values = widgetLists.font, |
get = function() |
return db.font |
end, |
set = function(_, font) |
db.font = font |
for i = 1, #lines do |
lines[i].text:SetFont(LSM:Fetch("font", db.font), db.fSize) |
end |
end, |
order = 4, |
}, |
fSize = { |
name = "Font Size", |
desc = "Font size for the reminder text.", |
type = "range", |
min = 8, |
max = 32, |
step = 1, |
get = function() return db.fSize end, |
set = function(_, fSize) |
db.fSize = fSize |
for i = 1, #lines do |
lines[i].text:SetFont(LSM:Fetch("font", db.font), db.fSize) |
end |
end, |
order = 5, |
}, |
fColor = { |
name = "Text Color", |
desc = "The color of the reminder text.", |
type = "color", |
get = function() return db.fColor.r, db.fColor.g, db.fColor.b end, |
set = function(_,r,g,b) |
db.fColor.r = r |
db.fColor.g = g |
db.fColor.b = b |
for i = 1, #lines do |
lines[i].text:SetTextColor(r,g,b) |
end |
end, |
order = 6, |
}, |
zone = { |
name = "Zone", |
type = "group", |
args = { |
use = { |
name = "Configure zone-based reminders.", |
type = "description", |
width = "full", |
order = 1, |
}, |
edit = { |
name = "Edit a Reminder", |
desc = "Select a reminder to edit.", |
type = "select", |
values = function() |
wipe(dropdownContents) |
for i = 1, #zr do |
tinsert(dropdownContents, zr[i].name) |
end |
return dropdownContents |
end, |
get = function() return zre end, |
set = function(_,rID) |
zre = rID |
end, |
order = 2, |
}, |
addNew = { |
name = "New Reminder", |
desc = "Create a name for your new zone-based reminder.", |
type = "input", |
width = "double", |
get = function() return "" end, |
set = function(_, name) |
tinsert(zr, {name = name, data = {}}) |
zre = #zr |
end, |
order = 3, |
}, |
configure = { |
name = "Configure your reminder.", |
type = "group", |
inline = true, |
disabled = function() if zre then return false else return true end end, |
args = { |
icon = { |
name = "Icon", |
desc = "The icon to display with your reminder text.", |
type = "execute", |
func = function() |
OpenIconBrowser(zr[zre]) |
end, |
image = function() |
if zre and zr[zre].data.icon then |
return zr[zre].data.icon |
else |
return sampleData.icon --or empty image? |
end |
end, |
order = 1, |
}, |
text = { |
name = "Reminder text", |
desc = "The text to display for your reminder.", |
type = "input", |
width = "double", |
get = function() return zre and zr[zre].data.text or "" end, |
set = function(_, text) |
zr[zre].data.text = text |
end, |
order = 2, |
}, |
playSound = { |
name = "Play sound", |
desc = "If checked, a sound will play when your reminder goes off.", |
type = "toggle", |
get = function() return (zre and zr[zre].data.sound) and true or false end, |
set = function() end, --need this? |
order = 3, |
}, |
sound = { |
name = "Sound", |
desc = "What sound to play for your reminder.", |
type = "select", |
dialogControl = "LSM30_Sound", |
values = widgetLists.sound, |
get = function() return zre and zr[zre].data.sound end, |
set = function(_, sound) |
zr[zre].data.sound = sound |
end, |
order = 4, |
}, |
spacer1 = { |
name = "", |
type = "description", |
width = "full", |
order = 5, |
}, |
zone = { |
name = "Remind when in zone...", |
desc = function() |
return strformat('What zone should your reminder display in? Will look for the "real" zone, the sub-zone, or the main zone. Note: make sure you spell your zone name correctly.\n\nFor example, you are currently in...\n"Real" zone: %s\nSubzone: %s\nZone: %s', GetRealZoneText()or"", GetSubZoneText()or"", GetZoneText()or"") |
end, |
type = "input", |
width = "double", |
get = function() return zre and zr[zre].zone or "" end, |
set = function(_, zone) |
zr[zre].zone = zone |
end, |
order = 6, |
}, |
showAlways = { |
name = "Always show", |
desc = "Show every time you enter this zone, or just once per day?", |
type = "toggle", |
get = function() return zre and zr[zre].showAlways end, |
set = function() |
zr[zre].showAlways = not zr[zre].showAlways |
end, |
order = 7, |
}, |
spacer2 = { |
name = " \n \n ", --or make a header? |
type = "description", |
width = "full", |
order = 8, |
}, |
delete = { |
name = "Delete", |
desc = "Remove this reminder.", |
type = "execute", |
func = function() |
tremove(zr, zre) |
zre = nil |
end, |
order = 9, |
}, |
}, |
}, |
}, |
}, |
event = { |
name = "Event", |
type = "group", |
args = { |
use = { |
name = "Configure event-based reminders.", |
type = "description", |
width = "full", |
order = 1, |
}, |
edit = { |
name = "Edit a Reminder", |
desc = "Select a reminder to edit.", |
type = "select", |
values = function() |
wipe(dropdownContents) |
for i = 1, #er do |
tinsert(dropdownContents, er[i].name) |
end |
return dropdownContents |
end, |
get = function() return ere end, |
set = function(_,rID) |
ere = rID |
end, |
order = 2, |
}, |
addNew = { |
name = "New Reminder", |
desc = "Create a name for your new event-based reminder.", |
type = "input", |
width = "double", |
get = function() return "" end, |
set = function(_, name) |
tinsert(er, {name = name, data = {}}) |
ere = #er |
end, |
order = 3, |
}, |
configure = { |
name = "Configure your reminder.", |
type = "group", |
inline = true, |
disabled = function() if ere then return false else return true end end, |
args = { |
icon = { |
name = "Icon", |
desc = "The icon to display with your reminder text.", |
type = "execute", |
func = function() |
OpenIconBrowser(er[ere]) |
end, |
image = function() |
if ere and er[ere].data.icon then |
return er[ere].data.icon |
else |
return sampleData.icon --or empty image? |
end |
end, |
order = 1, |
}, |
text = { |
name = "Reminder text", |
desc = "The text to display for your reminder.", |
type = "input", |
width = "double", |
get = function() return ere and er[ere].data.text or "" end, |
set = function(_, text) |
er[ere].data.text = text |
end, |
order = 2, |
}, |
playSound = { |
name = "Play sound", |
desc = "If checked, a sound will play when your reminder goes off.", |
type = "toggle", |
get = function() return (ere and er[ere].data.sound) and true or false end, |
set = function() end, --need this? |
order = 3, |
}, |
sound = { |
name = "Sound", |
desc = "What sound to play for your reminder.", |
type = "select", |
dialogControl = "LSM30_Sound", |
values = widgetLists.sound, |
get = function() return ere and er[ere].data.sound end, |
set = function(_, sound) |
er[ere].data.sound = sound |
end, |
order = 4, |
}, |
spacer1 = { |
name = "", |
type = "description", |
width = "full", |
order = 5, |
}, |
event = { |
name = "Remind on which event?", |
desc = "What event should cause your reminder to display? Note: make sure you spell your event name correctly and in all caps. If you want your reminder to fire at PLAYER_LOGIN, set up a login-based reminder instead." |
type = "input", |
width = "double", |
get = function() return ere and er[ere].event or "" end, |
set = function(_, event) |
er[ere].event = event |
eventFrame:RegisterEvent(event) |
end, |
order = 6, |
}, |
showAlways = { |
name = "Always show", |
desc = "Show every time this event fires, or just once per day?", |
type = "toggle", |
get = function() return ere and er[ere].showAlways end, |
set = function() |
er[ere].showAlways = not er[ere].showAlways |
end, |
order = 7, |
}, |
spacer2 = { |
name = " \n \n ", --or make a header? |
type = "description", |
width = "full", |
order = 8, |
}, |
delete = { |
name = "Delete", |
desc = "Remove this reminder.", |
type = "execute", |
func = function() |
tremove(er, ere) |
ere = nil |
end, |
order = 9, |
}, |
}, |
}, |
}, |
}, |
login = { |
name = "Login", |
type = "group", |
args = { |
use = { |
name = "Configure login-based reminders.", |
type = "description", |
width = "full", |
order = 1, |
}, |
edit = { |
name = "Edit a Reminder", |
desc = "Select a reminder to edit.", |
type = "select", |
values = function() |
wipe(dropdownContents) |
for i = 1, #er do |
tinsert(dropdownContents, lr[i].name) |
end |
return dropdownContents |
end, |
get = function() return lre end, |
set = function(_,rID) |
lre = rID |
end, |
order = 2, |
}, |
addNew = { |
name = "New Reminder", |
desc = "Create a name for your new login-based reminder.", |
type = "input", |
width = "double", |
get = function() return "" end, |
set = function(_, name) |
tinsert(lr, {name = name, data = {}}) |
lre = #lr |
end, |
order = 3, |
}, |
configure = { |
name = "Configure your reminder.", |
type = "group", |
inline = true, |
disabled = function() if lre then return false else return true end end, |
args = { |
icon = { |
name = "Icon", |
desc = "The icon to display with your reminder text.", |
type = "execute", |
func = function() |
OpenIconBrowser(lr[lre]) |
end, |
image = function() |
if ere and lr[lre].data.icon then |
return lr[lre].data.icon |
else |
return sampleData.icon --or empty image? |
end |
end, |
order = 1, |
}, |
text = { |
name = "Reminder text", |
desc = "The text to display for your reminder.", |
type = "input", |
width = "double", |
get = function() return lre and lr[lre].data.text or "" end, |
set = function(_, text) |
lr[lre].data.text = text |
end, |
order = 2, |
}, |
playSound = { |
name = "Play sound", |
desc = "If checked, a sound will play when your reminder goes off.", |
type = "toggle", |
get = function() return (lre and lr[lre].data.sound) and true or false end, |
set = function() end, --need this? |
order = 3, |
}, |
sound = { |
name = "Sound", |
desc = "What sound to play for your reminder.", |
type = "select", |
dialogControl = "LSM30_Sound", |
values = widgetLists.sound, |
get = function() return lre and lr[lre].data.sound end, |
set = function(_, sound) |
lr[lre].data.sound = sound |
end, |
order = 4, |
}, |
spacer1 = { |
name = "", |
type = "description", |
width = "full", |
order = 5, |
}, |
showAlways = { |
name = "Always show", |
desc = "Show every time you login, or just once per day? Note: if you wish to only show on a certain day, set up a day-based reminder.", |
type = "toggle", |
get = function() return lre and lr[lre].showAlways end, |
set = function() |
lr[lre].showAlways = not lr[lre].showAlways |
end, |
order = 7, |
}, |
spacer2 = { |
name = " \n \n ", --or make a header? |
type = "description", |
width = "full", |
order = 8, |
}, |
delete = { |
name = "Delete", |
desc = "Remove this reminder.", |
type = "execute", |
func = function() |
tremove(lr, lre) |
lre = nil |
end, |
order = 9, |
}, |
}, |
}, |
}, |
}, |
day = { |
name = "Day", |
type = "group", |
args = { |
use = { |
name = "Configure day-based reminders.", |
type = "description", |
width = "full", |
order = 1, |
}, |
edit = { |
name = "Edit a Reminder", |
desc = "Select a reminder to edit.", |
type = "select", |
values = function() |
wipe(dropdownContents) |
for i = 1, #dr do |
tinsert(dropdownContents, dr[i].name) |
end |
return dropdownContents |
end, |
get = function() return dre end, |
set = function(_,rID) |
dre = rID |
end, |
order = 2, |
}, |
addNew = { |
name = "New Reminder", |
desc = "Create a name for your new event-based reminder.", |
type = "input", |
width = "double", |
get = function() return "" end, |
set = function(_, name) |
tinsert(dr, {name = name, data = {}}) |
dre = #dr |
end, |
order = 3, |
}, |
configure = { |
name = "Configure your reminder.", |
type = "group", |
inline = true, |
disabled = function() if dre then return false else return true end end, |
args = { |
icon = { |
name = "Icon", |
desc = "The icon to display with your reminder text.", |
type = "execute", |
func = function() |
OpenIconBrowser(dr[dre]) |
end, |
image = function() |
if dre and dr[dre].data.icon then |
return dr[dre].data.icon |
else |
return sampleData.icon --or empty image? |
end |
end, |
order = 1, |
}, |
text = { |
name = "Reminder text", |
desc = "The text to display for your reminder.", |
type = "input", |
width = "double", |
get = function() return dre and dr[dre].data.text or "" end, |
set = function(_, text) |
dr[dre].data.text = text |
end, |
order = 2, |
}, |
playSound = { |
name = "Play sound", |
desc = "If checked, a sound will play when your reminder goes off.", |
type = "toggle", |
get = function() return (dre and dr[dre].data.sound) and true or false end, |
set = function() end, --need this? |
order = 3, |
}, |
sound = { |
name = "Sound", |
desc = "What sound to play for your reminder.", |
type = "select", |
dialogControl = "LSM30_Sound", |
values = widgetLists.sound, |
get = function() return dre and dr[dre].data.sound end, |
set = function(_, sound) |
dr[dre].data.sound = sound |
end, |
order = 4, |
}, |
spacer1 = { |
name = "", |
type = "description", |
width = "full", |
order = 5, |
}, |
day = { |
name = "Remind on which day?", |
desc = "Logging in on which day should cause your reminder to display?" |
type = "select", |
values = BiM.dayMap |
get = function() return dre and dr[dre].day or nil end, |
set = function(_, dayID) |
dr[dre].day = BiM.dayMap[dayID] |
end, |
order = 6, |
}, |
showAlways = { |
name = "Always show", |
desc = "Show every time you log in on this day, or just once per day?", |
type = "toggle", |
get = function() return dre and dr[dre].showAlways end, |
set = function() |
dr[dre].showAlways = not dr[dre].showAlways |
end, |
order = 7, |
}, |
spacer2 = { |
name = " \n \n ", --or make a header? |
type = "description", |
width = "full", |
order = 8, |
}, |
delete = { |
name = "Delete", |
desc = "Remove this reminder.", |
type = "execute", |
func = function() |
tremove(dr, dre) |
dre = nil |
end, |
order = 9, |
}, |
}, |
}, |
}, |
}, |
time = { |
name = "Time", |
type = "group", |
args = { |
use = { |
name = "Configure time-based reminders.", |
type = "description", |
width = "full", |
order = 1, |
}, |
edit = { |
name = "Edit a Reminder", |
desc = "Select a reminder to edit.", |
type = "select", |
values = function() |
wipe(dropdownContents) |
for i = 1, #er do |
tinsert(dropdownContents, tr[i].name) |
end |
return dropdownContents |
end, |
get = function() return tre end, |
set = function(_,rID) |
tre = rID |
end, |
order = 2, |
}, |
addNew = { |
name = "New Reminder", |
desc = "Create a name for your new time-based reminder.", |
type = "input", |
width = "double", |
get = function() return "" end, |
set = function(_, name) |
tinsert(tr, {name = name, data = {}}) |
tre = #tr |
end, |
order = 3, |
}, |
configure = { |
name = "Configure your reminder.", |
type = "group", |
inline = true, |
disabled = function() if tre then return false else return true end end, |
args = { |
icon = { |
name = "Icon", |
desc = "The icon to display with your reminder text.", |
type = "execute", |
func = function() |
OpenIconBrowser(tr[tre]) |
end, |
image = function() |
if tre and tr[tre].data.icon then |
return tr[tre].data.icon |
else |
return sampleData.icon --or empty image? |
end |
end, |
order = 1, |
}, |
text = { |
name = "Reminder text", |
desc = "The text to display for your reminder.", |
type = "input", |
width = "double", |
get = function() return tre and tr[tre].data.text or "" end, |
set = function(_, text) |
tr[tre].data.text = text |
end, |
order = 2, |
}, |
playSound = { |
name = "Play sound", |
desc = "If checked, a sound will play when your reminder goes off.", |
type = "toggle", |
get = function() return (tre and tr[tre].data.sound) and true or false end, |
set = function() end, --need this? |
order = 3, |
}, |
sound = { |
name = "Sound", |
desc = "What sound to play for your reminder.", |
type = "select", |
dialogControl = "LSM30_Sound", |
values = widgetLists.sound, |
get = function() return tre and tr[tre].data.sound end, |
set = function(_, sound) |
tr[tre].data.sound = sound |
end, |
order = 4, |
}, |
spacer1 = { |
name = "", |
type = "description", |
width = "full", |
order = 5, |
}, |
time = { |
name = "Remind at what time?", |
desc = "What time of day should cause your reminder to display? Note: Time should be in 12-hour format, followed by a space, then either AM or PM (capitals). Examples: 11:45 AM or 3:30 PM" |
type = "input", |
get = function() return tre and tr[tre].time or "" end, |
set = function(_, time) |
tr[tre].time = time |
end, |
order = 6, |
}, |
spacer2 = { |
name = " \n \n ", --or make a header? |
type = "description", |
width = "full", |
order = 8, |
}, |
delete = { |
name = "Delete", |
desc = "Remove this reminder.", |
type = "execute", |
func = function() |
tremove(tr, tre) |
tre = nil |
end, |
order = 9, |
}, |
}, |
}, |
}, |
}, |
}, |
} |
local function OnInitialize() |
BearInMindDB = BearInMindDB or {} |
db = BearInMindDB |
BearInMindPCDB = BearInMindPCDB or {} |
pcdb = BearInMindPCDB |
for k,v in pairs(defaults) do |
if type(db[k]) == "nil" then |
db[k] = v |
end |
end |
if not db.reminders or #db.reminders == 0 then |
db.reminders = { |
zone = {}, |
event = {}, |
day = {}, |
time = {}, |
login = {}, |
custom = {}, |
}, |
end |
LibStub("AceConfig-3.0"):RegisterOptionsTable(addonName, options) |
BiM.optionsFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions(addonName, addonName) |
SlashCmdList["BEAR_IN_MIND"] = function() |
InterfaceOptionsFrame_OpenToCategory(addonName) |
InterfaceOptionsFrame_OpenToCategory(addonName) |
end |
SLASH_BEAR_IN_MIND1 = "/bearinmind" |
SLASH_BEAR_IN_MIND2 = "/bim" |
anchor = BiM.anchor |
eventFrame = BiM.eventFrame |
lines = BiM.lines |
zr = db.reminders.zone |
er = db.reminders.event |
dr = db.reminders.day |
tr = db.reminders.time |
lr = db.reminders.login |
--cr = db.reminders.custom |
BiM.PlayerLogin() |
end |
f:RegisterEvent("PLAYER_LOGIN") |
f:SetScript("OnEvent", function(self, event) |
OnInitialize() |
end) |