WoWInterface SVN BearinMind

Compare Revisions

  • This comparison shows the changes necessary to convert path
    /trunk
    from Rev 2 to Rev 3
    Reverse comparison

Rev 2 → Rev 3

Bear in Mind/BiM.lua
1,?rev1len? → 229,?rev2len?
local addonName, BiM = ...
\ No newline at end of file +local addonName, BiM = ... +BiM.anchor = CreateFrame("Frame", "BearInMindAnchor", UIParent) +local anchor = BiM.anchor +BiM.eventFrame = CreateFrame("Frame") +local eventFrame = BiM.eventFrame +local LSM = LibStub("LibSharedMedia-3.0") + +local tinsert = table.insert +local tremove = table.remove +local strformat = string.format +local db, pcdb + +numLines = 0 --just #lines ? +inUse = 0 +BiM.lines = {} +local lines = BiM.lines +sRD = {} --shownReminderData (as reminders in our DB get shown, their data is added to this table) + +local function CreateNew() + --factory function to make a new line + local line = CreateFrame("Frame", nil, anchor) + if inUse >1 then + line:SetPoint("TOPLEFT", lines[inUse-1], "BOTTOMLEFT", 0, -5) + else + line:SetPoint("TOPLEFT", anchor, "TOPLEFT", 5, -5) + end + line:SetSize(270, 40) + + line.icon = CreateFrame("Button", nil, line) --need to test to make sure it's created properly + local icon = line.icon + icon:SetPoint("LEFT") + icon:SetSize(40, 40) + icon:RegisterForClicks("AnyUp") + icon:SetScript("OnClick", function() BiM.HideReminder(line) end) + + line.text = line:CreateFontString() + local text = line.text + text:SetPoint("LEFT", icon, "RIGHT", 5, 0) + text:SetSize(225, 40) + text:SetJustifyH("LEFT") + text:SetFont(LSM:Fetch("font", db.font), db.fSize) + text:SetTextColor(db.fColor.r, db.fColor.g, db.fColor.b) + + tinsert(lines, line) --insert our new line at the end of the table + numLines = #lines --now we have this many lines created + line.index = numLines --index will increase as lines are created and added to our table + + return line +end + +function BiM.ConfigureLine(line, data) + line.data = data + line.icon:SetNormalTexture(data.icon) + line.text:SetText(data.text) + if data.sound then + PlaySoundFile(LSM:Fetch("sound", data.sound)) + end + data.shown = true +end + +function BiM.DisplayReminder(data, sample) + --if this is a sample, let's put it in the table to avoid errors + if sample then tinsert(sRD, data) end + + inUse = inUse + 1 --we have one more used + local line + --get a line to display + if numLines < inUse then + line = CreateNew() --we need to make a new line + else + line = lines[inUse] --use the next available line (if 2 were in use, then 3 is what we're on now) + line:Show() + end + BiM.ConfigureLine(line, data) --moved to own function for reuse +end + +function BiM.HideReminder(line) + lines[inUse]:Hide() --hide last one + inUse = inUse - 1 --now we're using one less + line.data.shown = false --the reminder associated with this line is no longer shown + tremove(sRD, line.index) + --reconfigure all lines so that the reminders still showing are in the right spots + --(in case the 1st of 3 was dismissed, for example) + for i = 1, inUse do + BiM.ConfigureLine(lines[i], sRD[i]) --make them all match up + end +end + +local function ShouldShowReminder(reminder) + local show = false + if not reminder.data.shown then --if not currently shown + local _, m, d, y = CalendarGetDate() + local curDate = strformat("%d-%d-%d", m, d, y) + --if show always or haven't shown yet today + if reminder.showAlways or reminder.lastShown ~= curDate then + show = true + reminder.lastShown = curDate + end + end + return show +end + +local function CheckForZoneReminders() + --will fire for ZONE_CHANGED, ZONE_CHANGED_NEW_AREA, ZONE_CHANGED_INDOORS and PLAYER_ENTERING_WORLD + local realZone = GetRealZoneText() + local subZone = GetSubZoneText() + local zone = GetZoneText() + + local zoneReminders = db.reminders.zone + for i = 1, #zoneReminders do + local reminder = zoneReminders[i] + local remindAt = reminder.zone + if remindAt == realZone or remindAt == subZone or remindAt == zone then + --found a reminder, now should we show it? + if ShouldShowReminder(reminder) then + tinsert(sRD, reminder.data) --backup which reminders are showing (data.icon, data.text, data.sound) + BiM.DisplayReminder(reminder.data) + end + end + end +end + +local function CheckForOnEventReminders(event) + --will fire for any additional events registered + local eventReminders = db.reminders.event + for i = 1, #eventReminders do + local reminder = eventReminders[i] + if event == reminder.event then + if ShouldShowReminder(reminder) then + tinsert(sRD, reminder.data) + BiM.DisplayReminder(reminder.data) + end + end + end +end + +local function CheckForCertainDayReminders() + --will fire at login to check the day + local dayReminders = db.reminders.day + for i = 1, #dayReminders do + local reminder = dayReminders[i] + if reminder.day == CalendarGetDate() and ShouldShowReminder(reminder)then --stored and recalled as the weekday index (1-7) + tinsert(sRD, reminder.data) + BiM.DisplayReminder(reminder.data) + end + end +end + +local function CheckForCertainTimeReminders() + --will fire at one-minute intervals to check the time + if #db.reminders.time > 0 then + local timeReminders = db.reminders.time + for i = 1, #timeReminders do + local reminder = timeReminders[i] + if reminder.time == GameTime_GetTime(true) then + tinsert(sRD, reminder.data) + BiM.DisplayReminder(reminder.data) + end + end + end + C_Timer.After(60, CheckForCertainTimeReminders) --keep polling every minute +end + +local function CheckForLoginReminders() + --will fire at at login + local loginReminders = db.reminders.login + for i = 1, #loginReminders do + local reminder = loginReminders[i] + if ShouldShowReminder(reminder) then + tinsert(sRD, reminder.data) + BiM.DisplayReminder(reminder.data) + end + end +end + +function BiM.PlayerLogin() + db = BearInMindDB + pcdb = BearInMindPCDB + + --set up anchor frame + anchor:SetSize(280, 50) + anchor:SetPoint(db.anchor, UIParent, db.anchor, db.offsetX, db.offsetY) + anchor:SetScale(db.scale) + anchor.bg = anchor:CreateTexture() + anchor.bg:SetAllPoints(anchor) + anchor:SetClampedToScreen(true) + if db.lock then + anchor.bg:SetTexture(0,0,1,0) + else + anchor.bg:SetTexture(0,0,1,.4) + anchor:EnableMouse(true) + anchor:SetMovable(true) + end + anchor:SetScript("OnMouseDown", function(self) anchor:StartMoving() end) + anchor:SetScript("OnMouseUp", function(self) + self:StopMovingOrSizing() + db.anchor, _, _, db.offsetX, db.offsetY = anchor:GetPoint() + end) + + --check for at login reminders + CheckForLoginReminders() + --check for any reminders for today specifically + CheckForCertainDayReminders() + --check for login/current zone reminders + CheckForZoneReminders() + --listen for event reminders + local eventReminders = db.reminders.event + for i = 1, #eventReminders do + eventFrame:RegisterEvent(eventReminders[i].event) + end + --check for any reminders for a certain time and start C_Timer.After + CheckForCertainTimeReminders() +end + +local zoneEvents = { + ["ZONE_CHANGED"] = true, + ["ZONE_CHANGED_NEW_AREA"] = true, + ["ZONE_CHANGED_INDOORS"] = true, + ["PLAYER_ENTERING_WORLD"] = true, +} +for k,v in pairs(zoneEvents) do + eventFrame:RegisterEvent(k) +end +eventFrame:SetScript("OnEvent", function(self, event, ...) + if zoneEvents[event] then + CheckForZoneReminders() + end + CheckForOnEventReminders(event) +end) \ No newline at end of file
Bear in Mind/BiM-icon.blp Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Property changes : Added: svn:mime-type + application/octet-stream
Bear in Mind/libs/AceLocale-3.0/AceLocale-3.0.xml New file
0,0 → 1,4
<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>
\ No newline at end of file
Bear in Mind/libs/AceLocale-3.0/AceLocale-3.0.lua New file
0,0 → 1,137
--- **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
Bear in Mind/libs/AceTimer-3.0/AceTimer-3.0.xml New file
0,0 → 1,4
<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>
\ No newline at end of file
Bear in Mind/libs/AceTimer-3.0/AceTimer-3.0.lua New file
0,0 → 1,276
--- **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
Bear in Mind/libs/LibAdvancedIconSelector-1.0/Readme.txt New file
0,0 → 1,352
========================================================================================
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.
Bear in Mind/libs/LibAdvancedIconSelector-1.0/LibAdvancedIconSelector-1.0.xml New file
0,0 → 1,5
<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>
Bear in Mind/libs/LibAdvancedIconSelector-1.0/LibAdvancedIconSelector-1.0.lua New file
0,0 → 1,1374
--[[========================================================================================
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
Bear in Mind/libs/LibAdvancedIconSelector-1.0/Locales/enUS.lua New file
0,0 → 1,63
--[[========================================================================================
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
Bear in Mind/Bear in Mind.toc
1,9 → 1,9
## 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
 
13,5 → 13,9
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
\ No newline at end of file
Bear in Mind/BiM_options.lua New file
0,0 → 1,883
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)
\ No newline at end of file
Bear in Mind Property changes : Added: svn:ignore + *.png *.psd