· 5 years ago · Apr 21, 2020, 11:44 PM
1local addonName, addon = ...
2local L = addon.L
3local utils = addon.utils
4
5local LBF = LibStub:GetLibrary('LibButtonFacade', true)
6
7local _G = _G
8_G['KTracker'] = addon
9
10--
11-- default addon options
12--
13local def = {
14 -- maximum allowed groups, columns and rows
15 maxGroups = 64,
16 maxColumns = 12,
17 maxRows = 12,
18
19 -- account DB template
20 DB = {sync = true, groups = {}},
21
22 -- character DB template
23 CharDB = {
24 enabled = true,
25 locked = false,
26 groups = {}
27 },
28
29 -- default group template
30 group = {
31 id = '',
32 enabled = true,
33 name = '',
34 spec = 0,
35 columns = 4,
36 rows = 1,
37 spacing = 0,
38 scale = 2,
39 combat = false,
40 icons = {},
41 position = {},
42 style = {}
43 },
44
45 -- default icon template
46 icon = {
47 enabled = false,
48 name = '',
49 type = '',
50 subtype = '',
51 when = 1,
52 unit = 'player',
53 mine = false,
54 timer = false,
55 effect = 'none'
56 },
57
58 -- group parts, account's and character's.
59 DBGroup = {
60 id = '',
61 name = '',
62 columns = 4,
63 rows = 1,
64 icons = {},
65 },
66 CharDBGroup = {
67 enabled = false,
68 spacing = 0,
69 scale = 2,
70 spec = 0,
71 combat = false,
72 position = {},
73 style = {}
74 }
75}
76
77--
78-- SavedVariables
79--
80KTrackerDB = {}
81KTrackerCharDB = {}
82
83--
84-- simple title holder
85--
86local titleString = '|cfff58cbaKader|r|caaf49141Tracker|r'
87
88--
89-- addon synchronization
90--
91local syncPrefix = 'KaderTracker'
92local syncHandlers = {}
93
94--
95-- textures to be used
96--
97local textures = {
98 'Interface\\Icons\\INV_Misc_QuestionMark',
99 'Interface\\Icons\\INV_Misc_PocketWatch_01'
100}
101
102local Group, Icon = {}, {}
103local groups, numGroups = {}, 0
104local minimapButton
105
106local Icon_EffectTrigger, Icon_EffectReset
107
108--
109-- cache some globals
110--
111
112local tinsert, tremove = _G.table.insert, _G.table.remove
113local twipe, tsort = _G.table.wipe, _G.table.sort
114local pairs, ipairs = _G.pairs, _G.ipairs
115local type, select = _G.type, _G.select
116local find, format, gsub = _G.string.find, _G.string.format, _G.string.gsub
117local tostring, tonumber = _G.tostring, _G.tonumber
118local getmetatable = _G.getmetatable
119
120local unitName = UnitName('player')
121local CreateFrame = _G.CreateFrame
122
123local GetInventorySlotInfo = _G.GetInventorySlotInfo
124local GetInventoryItemTexture = _G.GetInventoryItemTexture
125
126local GetWeaponEnchantInfo = _G.GetWeaponEnchantInfo
127local GetTotemInfo = _G.GetTotemInfo
128local CooldownFrame_SetTimer = _G.CooldownFrame_SetTimer
129local GetCursorPosition = _G.GetCursorPosition
130local IsSpellInRange, IsUsableSpell = _G.IsSpellInRange, _G.IsUsableSpell
131
132local UnitName, UnitClass, UnitGUID = _G.UnitName, _G.UnitClass, _G.UnitGUID
133local UnitExists, UnitIsDead = _G.UnitExists, _G.UnitIsDead
134local UnitAura, UnitReaction = _G.UnitAura, _G.UnitReaction
135
136local GetSpellInfo = _G.GetSpellInfo
137local GetSpellTexture = _G.GetSpellTexture
138local GetSpellCooldown = _G.GetSpellCooldown
139
140local GetItemInfo = _G.GetItemInfo
141local GetItemCooldown = _G.GetItemCooldown
142
143local LiCD = LibStub:GetLibrary('LibInternalCooldowns', true)
144if LiCD and LiCD.GetItemCooldown then
145 GetItemCooldown = function(...)
146 return LiCD:GetItemCooldown(...)
147 end
148end
149
150--------------------------------------------------------------------------
151-- AddOn initialization
152
153local mainFrame, LoadDatabase = CreateFrame('Frame')
154do
155
156 --
157 -- makes sure to properly setup or load database
158 --
159 function LoadDatabase()
160 -- we fill the account's DB if empty.
161 if utils.isEmpty(KTrackerDB) then
162 utils.fillTable(KTrackerDB, def.DB)
163 end
164
165 -- we fill the character's DB if empty.
166 if utils.isEmpty(KTrackerCharDB) then
167 utils.fillTable(KTrackerCharDB, def.CharDB)
168 end
169
170 -- keep reference of addon enable and lock statuses
171 addon.sync = KTrackerDB.sync
172 addon.enabled = KTrackerCharDB.enabled
173 addon.locked = KTrackerCharDB.locked
174
175 -- minimap button
176 if KTrackerCharDB.minimap == nil then
177 KTrackerCharDB.minimap = true
178 end
179 addon.minimap = KTrackerCharDB.minimap
180
181 if not addon.minimap then
182 addon:HideMinimapButton()
183 end
184
185 -- this step is crucial. If the account has not groups
186 -- or all groups were deleted we make sure to create
187 -- the default group.
188 if utils.isEmpty(KTrackerDB.groups) then
189 local group = utils.deepCopy(def.group)
190 group.name = L['Default Group']
191 group.enabled = true
192 Group:Save(group)
193 end
194
195 -- check if the character has all groups added to his/her table
196 Group:Check()
197 end
198
199 --
200 -- addon's slash command handler
201 --
202 local function SlashCommandHandler(cmd)
203 if cmd == 'config' or cmd == 'options' then
204 addon:Config()
205 elseif cmd == 'reset' then
206 StaticPopup_Show('KTRACKER_DIALOG_RESET')
207 else
208 addon:Toggle()
209 end
210 CloseDropDownMenus() -- always close them.
211 end
212
213 --
214 -- handles main frame events
215 --
216 local function EventHandler(self, event, ...)
217 -- on ADDON_LOADED event.
218 if event == 'ADDON_LOADED' then
219 local name = ...
220 if name ==addonName then
221 mainFrame:UnregisterEvent('ADDON_LOADED')
222 mainFrame:RegisterEvent('PLAYER_LOGIN')
223 mainFrame:RegisterEvent('PLAYER_ENTERING_WORLD')
224 mainFrame:RegisterEvent('CHAT_MSG_ADDON')
225
226 LoadDatabase()
227
228 SlashCmdList['KTRACKER'] = SlashCommandHandler
229 SLASH_KTRACKER1, SLASH_KTRACKER2 = '/ktracker', '/kt'
230
231 -- ButtonFacade calllback
232 if LBF then
233 LBF:RegisterSkinCallback(addonName, addon.OnSkin, addon)
234 end
235
236 addon:Print(L['addon loaded'])
237 end
238
239 elseif event == 'PLAYER_LOGIN' then
240 mainFrame:RegisterEvent('PLAYER_TALENT_UPDATE')
241 addon:Initialize(true)
242
243 elseif event == 'PLAYER_LOGIN' or event == 'PLAYER_ENTERING_WORLD' then
244 mainFrame:RegisterEvent('PLAYER_TALENT_UPDATE')
245 addon:Initialize()
246
247 elseif event == 'PLAYER_TALENT_UPDATE' then
248 addon:SetCurrentSpec()
249 addon:Initialize()
250
251 -- addon messages
252 elseif event == 'CHAT_MSG_ADDON' and addon.sync then
253 local prefix, msg, channel, sender = ...
254
255 if msg and prefix ==syncPrefix and sender ~= unitName then
256 local handler = syncHandlers[prefix]
257 if handler and type(handler) == 'function' then
258 handler(msg, channel, sender)
259 end
260 end
261 end
262 end
263
264 -- register required event and script
265 mainFrame:RegisterEvent('ADDON_LOADED')
266 mainFrame:SetScript('OnEvent', EventHandler)
267
268end
269
270--
271-- called when the addon needs to be initialized
272--
273function addon:Load()
274 Group:Check()
275 wipe(groups)
276 for i = 1, def.maxGroups do
277 Group:Load(i)
278 end
279 numGroups = #groups
280end
281
282--
283-- toggle addon's locked/unlocked status
284--
285function addon:Toggle()
286 PlaySound('UChatScrollButton')
287 StaticPopup_Hide('KTRACKER_DIALOG_RESET')
288 StaticPopup_Hide('KTRACKER_DIALOG_CLEAR')
289 StaticPopup_Hide('KTRACKER_DIALOG_NAME')
290 StaticPopup_Hide('KTRACKER_DIALOG_UNITNAME')
291 StaticPopup_Hide('KTRACKER_DIALOG_SHARE_SEND')
292 StaticPopup_Hide('KTRACKER_DIALOG_SHARE_RECEIVE')
293 CloseDropDownMenus()
294 KTrackerCharDB.locked = not KTrackerCharDB.locked
295 self.locked = KTrackerCharDB.locked
296 self:Initialize()
297end
298
299--
300-- addon synchronization
301--
302function addon:Sync(msg, channel, target)
303 if self.sync then
304 utils.sync(syncPrefix, msg, channel, target)
305 end
306end
307
308--
309-- ButtonFacade skin handler
310--
311function addon:OnSkin(skin, glossAlpha, gloss, group, _, colors)
312 local style
313
314 if not utils.isEmpty(groups) then
315 for k, v in pairs(groups) do
316 if v.name ==group then
317 style = KTrackerCharDB.groups[v.id].style
318 break
319 end
320 end
321 end
322
323 if style then
324 style[1] = skin
325 style[2] = glossAlpha
326 style[3] = gloss
327 style[4] = colors
328 end
329end
330
331--------------------------------------------------------------------------
332-- Groups functions
333
334--
335-- this function is useful and makes sure the character has
336-- all groups references and options added to his/her table.
337--
338function Group:Check()
339 local DBGroups = KTrackerDB.groups
340 local CharDBGroups = KTrackerCharDB.groups
341
342 -- first step: delete groups that were probably deleted but
343 -- their data accidentally remained in character's database
344 for k, v in pairs(CharDBGroups) do
345 local found = false
346 for _, obj in ipairs(DBGroups) do
347 if obj.id ==k then
348 found = true
349 break
350 end
351 end
352 if not found then v = nil end
353 end
354
355 -- second step: add missing groups to character
356 for _, obj in ipairs(DBGroups) do
357 if not CharDBGroups[obj.id] then
358 CharDBGroups[obj.id] = utils.deepCopy(def.CharDBGroup)
359 end
360 end
361end
362
363--
364-- creates a new group from the def table
365--
366function Group:Save(obj, id)
367
368 -- are we updating and existing group?
369 if id and KTrackerDB.groups[id] then
370 local DB = KTrackerDB.groups[id]
371
372 -- repair the ID, just in case
373 if not DB.id or DB.id == '' then
374 DB.id = 'KTrackerGroup'..id
375 end
376 obj.id = DB.id -- hotfix
377
378 -- check character's database
379 local db = KTrackerCharDB.groups[DB.id]
380 if not db then
381 KTrackerCharDB.groups[DB.id] = utils.deepCopy(def.CharDBGroup)
382 KTrackerCharDB.groups[DB.id].enabled = true
383 db = KTrackerCharDB.groups[DB.id]
384 end
385
386 -- we proceed to update
387 for k, v in pairs(obj) do
388 if def.DBGroup[k] ~= nil then
389 DB[k] = v -- account
390 elseif def.CharDBGroup[k] ~= nil then
391 db[k] = v -- character
392 end
393 end
394
395 return true
396 end
397
398 -- creating a new group
399
400 obj = obj or {}
401 if type(obj) == 'string' then obj = {name = obj} end
402
403 -- prepare account and character tables
404 local DB, db = utils.deepCopy(def.DBGroup), utils.deepCopy(def.CharDBGroup)
405 for k, v in pairs(obj) do
406 if def.DBGroup[k] ~= nil then
407 DB[k] = v -- account
408 elseif def.CharDBGroup[k] ~= nil then
409 db[k] = v -- character
410 end
411 end
412
413 -- fill the group with required number of icons
414 local num = DB.columns*DB.rows
415 for i = 1, num do
416 local icon = utils.deepCopy(def.icon)
417 tinsert(DB.icons, icon)
418 end
419
420 -- prepare the group to store the rest into the character's table
421 DB.id = 'KTrackerGroup'..(#KTrackerDB.groups+1)
422
423 -- save the final results to tables.
424 tinsert(KTrackerDB.groups, DB)
425 KTrackerCharDB.groups[DB.id] = db
426end
427
428do
429 --
430 -- resize button OnMouseDown and OnMouseUp functions
431 --
432 local Sizer_OnMouseDown, Sizer_OnMouseUp
433
434 do
435 --
436 -- handles group resizing
437 --
438 local function Sizer_OnUpdate(self)
439 local uiScale = UIParent:GetScale()
440 local f = self:GetParent()
441 local cursorX, cursorY = GetCursorPosition(UIParent)
442
443 -- calculate the new scale
444 local newXScale = f.oldScale * (cursorX/uiScale - f.oldX*f.oldScale) / (self.oldCursorX/uiScale - f.oldX*f.oldScale)
445 local newYScale = f.oldScale * (cursorY/uiScale - f.oldY*f.oldScale) / (self.oldCursorY/uiScale - f.oldY*f.oldScale)
446 local newScale = math.max(0.6, newXScale, newYScale)
447 f:SetScale(newScale)
448
449 -- calculate new frame position
450 local newX = f.oldX * f.oldScale / newScale
451 local newY = f.oldY * f.oldScale / newScale
452 f:SetPoint('TOPLEFT', UIParent, 'BOTTOMLEFT', newX, newY)
453 end
454
455 --
456 -- called on OnMouseDown event
457 --
458 function Sizer_OnMouseDown(self, button)
459 -- resize only if the addon is not locked
460 if addon.locked then return end
461
462 if button == 'LeftButton' then
463 local f = self:GetParent()
464 f.oldScale = f:GetScale()
465 self.oldCursorX, self.oldCursorY = GetCursorPosition(UIParent)
466 f.oldX, f.oldY = f:GetLeft(), f:GetTop()
467 self:SetScript('OnUpdate', Sizer_OnUpdate)
468 end
469 end
470
471 --
472 -- called on OnMouseUp event
473 --
474 function Sizer_OnMouseUp(self, button)
475 self:SetScript('OnUpdate', nil)
476 if addon.locked then return end
477
478 -- Left button released? save scale
479 if button == 'LeftButton' then
480 local f = self:GetParent()
481 local id = f:GetID()
482 local DB = KTrackerDB.groups[id]
483 local db = KTrackerCharDB.groups[DB.id]
484 if DB and db then
485 db.scale = f:GetScale()
486 Group:Load(id)
487 end
488 end
489 end
490 end
491
492 do
493 --
494 -- hide all group icons before loading -- hotfix
495 --
496 local function ResetGroupIcons(id)
497 local i = 1
498 local btn = _G['KTrackerGroup'..id..'Icon'..i]
499 while btn ~= nil do
500 btn:Hide()
501 i = i + 1
502 btn = _G['KTrackerGroup'..id..'Icon'..i]
503 end
504 end
505
506 --
507 -- load a group and draws it into screen
508 --
509 function Group:Load(id)
510
511 -- make sure the the group exists
512 local obj
513 if KTrackerDB.groups[id] then
514 obj = utils.deepCopy(KTrackerDB.groups[id])
515 end
516 if not obj then return end
517
518 ResetGroupIcons(id)
519
520 -- complete obj table
521 utils.mixTable(obj, KTrackerCharDB.groups[obj.id])
522 if not groups[id] then
523 tinsert(groups, id, obj)
524 end
525
526 -- we create the group frame
527 local group = _G[obj.id]
528 if not group then
529 group = CreateFrame('Frame', obj.id, UIParent, 'KTrackerGroupTemplate')
530 end
531 group:SetID(id)
532
533 if LBF then
534 LBF:Group(addonName, obj.name):Skin(unpack(obj.style))
535 end
536
537 -- set the group title
538 group.title = _G[obj.id..'Title']
539 group.title:SetText(obj.name)
540
541 -- hold group resize button
542 group.sizer = _G[obj.id..'Resizer']
543 group.sizer:RegisterForClicks('AnyUp')
544 local sizerTexture = _G[obj.id..'ResizerTexture']
545 sizerTexture:SetVertexColor(0.6, 0.6, 0.6)
546
547 if addon.locked then
548 local spec = addon:GetCurrentSpec()
549 if obj.spec > 0 and obj.spec ~= spec then
550 obj.enabled = false
551 end
552
553 group.title:Hide()
554 group.sizer:Hide()
555 else
556 group.title:Show()
557 group.sizer:Show()
558
559 -- set resize button tooltip and scripts.
560 utils.setTooltip(group.sizer, L['Click and drag to change size.'], nil, L['Resize'])
561 group.sizer:SetScript('OnMouseDown', Sizer_OnMouseDown)
562 group.sizer:SetScript('OnMouseUp', Sizer_OnMouseUp)
563 end
564
565 if obj.enabled then
566 -- draw icons
567 for r = 1, obj.rows do
568 for c = 1, obj.columns do
569 local i = (r-1)*obj.columns+c
570 local iconName = obj.id..'Icon'..i
571 local icon = _G[iconName]
572 if not icon then
573 icon = CreateFrame('Button', iconName, group, 'KTrackerIconTemplate')
574 end
575 icon:SetID(i)
576
577 if c > 1 then
578 icon:SetPoint('TOPLEFT', _G[obj.id..'Icon'..(i-1)], 'TOPRIGHT', obj.spacing, 0)
579 elseif r > 1 and c ==1 then
580 icon:SetPoint('TOPLEFT', _G[obj.id..'Icon'..(i-obj.columns)], 'BOTTOMLEFT', 0, -obj.spacing)
581 elseif i ==1 then
582 icon:SetPoint('TOPLEFT', group, 'TOPLEFT')
583 end
584
585 -- we update the icon now
586 if not obj.enabled then Icon:ClearScripts(icon) end
587 Icon:Load(icon, id, i)
588
589 if LBF then
590 LBF:Group(addonName, obj.name):AddButton(icon)
591 else
592 icon:SetNormalTexture(nil)
593 icon.texture:SetTexCoord(0.07, 0.93, 0.07, 0.93)
594 icon:SetBackdrop({
595 bgFile = 'Interface\\DialogFrame\\UI-DialogBox-Background',
596 edgeFile = 'Interface\\Tooltips\\UI-Tooltip-Border',
597 tile = true, tileSize = 2, edgeSize = 4,
598 insets = {left = 0, right = 0, top = 0, bottom = 0}
599 })
600 end
601 end
602 end
603
604 -- we make sure to change group size in order to be fully
605 -- clamped to screen, then we set its scale.
606 local width = (36*obj.columns)+(obj.spacing*(obj.columns-1))
607 local height = (36*obj.rows)+(obj.spacing*(obj.rows-1))
608 group:SetSize(width, height)
609 group:SetScale(obj.scale)
610
611 -- we position the group only if it was moved
612 if not utils.isEmpty(obj.position) then
613 group:ClearAllPoints()
614 group:SetPoint(
615 obj.position.point or 'CENTER',
616 obj.position.relativeTo or UIParent,
617 obj.position.relativePoint or 'CENTER',
618 obj.position.xOfs or 0,
619 obj.position.yOfs or 0
620 )
621 end
622 end
623
624 -- register/unregister group events
625 if obj.combat and obj.enabled and addon.locked then
626 group:RegisterEvent('PLAYER_REGEN_ENABLED')
627 group:RegisterEvent('PLAYER_REGEN_DISABLED')
628 group:SetScript('OnEvent', function(self, event)
629 if event == 'PLAYER_REGEN_ENABLED' then
630 self:Hide()
631 elseif event == 'PLAYER_REGEN_DISABLED' then
632 self:Show()
633 end
634 end)
635 group:Hide()
636 else
637 group:UnregisterEvent('PLAYER_REGEN_ENABLED')
638 group:UnregisterEvent('PLAYER_REGEN_DISABLED')
639 group:SetScript('OnEvent', nil)
640 utils.showHide(group, obj.enabled)
641 end
642 end
643 end
644end
645
646--------------------------------------------------------------------------
647-- Icons functions
648
649do
650 --
651 -- current selected icon and menu
652 --
653 local current, menu = {}
654
655 --
656 -- icon general, reactive and aura checkers
657 ---
658 local Icon_ReactiveCheck
659
660 --
661 -- opens the menu for the current icon
662 --
663 local Icon_OpenMenu
664 do
665 --
666 -- icon menu list
667 --
668 local menuList, menu = {
669 -- icon type --
670 IconType = {
671 {text = L['Cooldown'], arg1 = 'type', arg2 = 'cooldown', value = 'spell'},
672 {text = L['Buff or Debuff'], arg1 = 'type', arg2 = 'aura', value = 'HELPFUL'},
673 {text = L['Reactive spell or ability'], arg1 = 'type', arg2 = 'reactive', value = 'spell'},
674 {text = L['Temporary weapon enchant'], arg1 = 'type', arg2 = 'wpnenchant', value = 'mainhand'},
675 {text = L['Totem/non-MoG Ghoul'], arg1 = 'type', arg2 = 'totem', value = ''},
676 },
677 SpellType = {
678 {text = L['Spell'], arg1 = 'subtype', arg2 = 'spell'},
679 {text = L['Item'], arg1 = 'subtype', arg2 = 'item'}
680 },
681 AuraType = {
682 {text = L['Buff'], arg1 = 'subtype', arg2 = 'HELPFUL'},
683 {text = L['Debuff'], arg1 = 'subtype', arg2 = 'HARMFUL'}
684 },
685 WpnEnchantType = {
686 {text = L['Main Hand'], arg1 = 'subtype', arg2 = 'mainhand'},
687 {text = L['Off-Hand'], arg1 = 'subtype', arg2 = 'offhand'}
688 },
689 SpellWhen = {
690 {text = L['Usable'], arg1 = 'when', arg2 = 1},
691 {text = L['Unusable'], arg1 = 'when', arg2 = -1},
692 {text = L['Always'], arg1 = 'when', arg2 = 0}
693 },
694 AuraWhen = {
695 {text = L['Present'], arg1 = 'when', arg2 = 1},
696 {text = L['Absent'], arg1 = 'when', arg2 = -1},
697 {text = L['Always'], arg1 = 'when', arg2 = 0}
698 },
699 Unit = {
700 {text = L['Player'], arg1 = 'unit', arg2 = 'player'},
701 {text = L['Target'], arg1 = 'unit', arg2 = 'target'},
702 {text = L['Target\'s Target'], arg1 = 'unit', arg2 = 'targettarget'},
703 {text = L['Focus'], arg1 = 'unit', arg2 = 'focus'},
704 {text = L['Focus Target'], arg1 = 'unit', arg2 = 'focustarget'},
705 {text = L['Pet'], arg1 = 'unit', arg2 = 'pet'},
706 {text = L['Pet\'s Target'], arg1 = 'unit', arg2 = 'pettarget'},
707 {disabled = true},
708 {text = L['Party Unit'], hasArrow = true, value = 'UnitParty'},
709 {text = L['Arena Unit'], hasArrow = true, value = 'UnitArena'},
710 {disabled = true}
711 },
712 UnitParty = {
713 {text = L:F('Party %d', 1), arg1 = 'unit', arg2 = 'party1'},
714 {text = L:F('Party %d', 2), arg1 = 'unit', arg2 = 'party2'},
715 {text = L:F('Party %d', 3), arg1 = 'unit', arg2 = 'party3'},
716 {text = L:F('Party %d', 4), arg1 = 'unit', arg2 = 'party4'}
717 },
718 UnitArena = {
719 {text = L:F('Arena %d', 1), arg1 = 'unit', arg2 = 'arena1'},
720 {text = L:F('Arena %d', 2), arg1 = 'unit', arg2 = 'arena2'},
721 {text = L:F('Arena %d', 3), arg1 = 'unit', arg2 = 'arena3'},
722 {text = L:F('Arena %d', 4), arg1 = 'unit', arg2 = 'arena4'},
723 {text = L:F('Arena %d', 5), arg1 = 'unit', arg2 = 'arena5'}
724 }
725 }
726
727 --
728 -- used for true and false values
729 --
730 local function Icon_OptionToggle()
731 local g, i = current.group, current.icon
732 local obj = KTrackerDB.groups[g].icons[i]
733 if obj and obj[this.value] ~= nil then
734 KTrackerDB.groups[g].icons[i][this.value] = this.checked
735 Icon:Load(_G['KTrackerGroup'..g..'Icon'..i], g, i)
736 end
737 end
738
739 --
740 -- used to set strings and numbers
741 --
742 local function Icon_OptionChoose(self, arg1, arg2)
743 local g, i = current.group, current.icon
744 local obj = KTrackerDB.groups[g].icons[i]
745 -- double check the icon
746 if obj and obj[arg1] ~= nil then
747 KTrackerDB.groups[g].icons[i][arg1] = arg2
748 if arg1 == 'type' then
749 if this.value == 'spell' or this.value == 'HELPFUL' or this.value == 'mainhand' or this.value == 'none' then
750 KTrackerDB.groups[g].icons[i].subtype = this.value
751 end
752 CloseDropDownMenus()
753 end
754 Icon:Load(_G['KTrackerGroup'..g..'Icon'..i], g, i)
755 return
756 end
757 CloseDropDownMenus()
758 end
759
760 --
761 -- clear the selected icon
762 --
763 local function Icon_OptionClear()
764 local i, g = current.icon, current.group
765 if KTrackerDB.groups[g].icons[i] then
766 KTrackerDB.groups[g].icons[i] = CopyTable(def.icon)
767 Icon:Load(_G['KTrackerGroup'..g..'Icon'..i], g, i)
768 end
769 CloseDropDownMenus()
770 end
771
772 --
773 -- the main menu handler function
774 --
775 function Icon_OpenMenu(icon)
776 if not icon then return end
777 local i, g = icon:GetID(), icon:GetParent():GetID()
778 local obj = KTrackerDB.groups[g].icons[i]
779 if not obj then return end
780 current.icon, current.group = i, g
781
782 if addon.effects and not menuList.Effects then
783 menuList.Effects = {{
784 text = L['None'],
785 arg1 = 'effect',
786 arg2 = 'none'
787 }}
788 for i, effect in ipairs(addon.effects) do
789 tinsert(menuList.Effects, i+1, {
790 text = effect.name,
791 arg1 = 'effect',
792 arg2 = effect.id
793 })
794 end
795 end
796
797 -- generate the menu
798 if not menu then
799 menu = CreateFrame('Frame', 'KTrackerIconMenu')
800 end
801 menu.displayMode = 'MENU'
802 menu.initialize = function(self, level)
803 local info = UIDropDownMenu_CreateInfo()
804 level = level or 1
805
806 if level >= 2 then
807
808 local tar = UIDROPDOWNMENU_MENU_VALUE
809 local menuItems = {}
810 if tar == 'Unit' then
811 menuItems = utils.deepCopy(menuList.Unit)
812 tinsert(menuItems, {
813 text = L['Custom Unit'],
814 func = function() StaticPopup_Show('KTRACKER_DIALOG_UNITNAME', nil, nil, icon) end
815 })
816 elseif menuList[tar] then
817 menuItems = utils.deepCopy(menuList[tar])
818 end
819
820 for _, v in ipairs(menuItems) do
821 info = utils.deepCopy(v)
822 info.checked = (v.arg2 and obj[v.arg1] == v.arg2)
823 info.func = v.func and v.func or Icon_OptionChoose
824 UIDropDownMenu_AddButton(info, level)
825 wipe(info)
826 end
827
828 return
829 end
830
831 -- display icon's name if set
832 if obj.name and obj.name ~= '' then
833 info.text = obj.name
834 info.isTitle = true
835 UIDropDownMenu_AddButton(info, level)
836 wipe(info)
837 end
838
839 -- let the player choose the name if the icon
840 -- type is not set to weapon enchant.
841 if obj.type ~= 'wpnenchant' then
842 info.text = L['Choose Name']
843 info.func = function() StaticPopup_Show('KTRACKER_DIALOG_NAME') end
844 UIDropDownMenu_AddButton(info, level)
845 wipe(info)
846 end
847
848 -- toggle icon enable status
849 info.text = L['Enabled']
850 info.value = 'enabled'
851 info.checked = obj.enabled
852 info.func = Icon_OptionToggle
853 info.keepShownOnClick = true
854 UIDropDownMenu_AddButton(info, level)
855 wipe(info)
856
857 -- icon type selection
858 info.text = L['Icon Type']
859 info.value = 'IconType'
860 info.hasArrow = true
861 UIDropDownMenu_AddButton(info, level)
862 wipe(info)
863
864 -- in case no type is set
865 if obj.type == '' then
866 info.text = L['More Options']
867 info.disabled = true
868 UIDropDownMenu_AddButton(info)
869 wipe(info)
870 else
871
872 -- icon effect (animation)
873 if (addon.effects and menuList.Effects) then
874 info.text = L['Icon Effect']
875 info.value = 'Effects'
876 info.hasArrow = true
877 UIDropDownMenu_AddButton(info, level)
878 wipe(info)
879 end
880
881 info.disabled = true
882 UIDropDownMenu_AddButton(info)
883 wipe(info)
884
885 if obj.type == 'cooldown' then
886
887 info.text = L['Cooldown Type']
888 info.value = 'SpellType'
889 info.hasArrow = true
890 UIDropDownMenu_AddButton(info, level)
891 wipe(info)
892
893 info.text = L['Show When']
894 info.value = 'SpellWhen'
895 info.hasArrow = true
896 UIDropDownMenu_AddButton(info, level)
897 wipe(info)
898
899 info.text = L['Show Timer']
900 info.value = 'timer'
901 info.checked = obj.timer
902 info.func = Icon_OptionToggle
903 info.keepShownOnClick = true
904 UIDropDownMenu_AddButton(info, level)
905 wipe(info)
906
907 elseif obj.type == 'aura' then
908
909 info.text = L['Buff or Debuff']
910 info.value = 'AuraType'
911 info.hasArrow = true
912 UIDropDownMenu_AddButton(info, level)
913 wipe(info)
914
915 info.text = L['Unit to Watch']
916 info.value = 'Unit'
917 info.hasArrow = true
918 UIDropDownMenu_AddButton(info, level)
919 wipe(info)
920
921 info.text = L['Show When']
922 info.value = 'AuraWhen'
923 info.hasArrow = true
924 UIDropDownMenu_AddButton(info, level)
925 wipe(info)
926
927 info.text = L['Show Timer']
928 info.value = 'timer'
929 info.checked = obj.timer
930 info.func = Icon_OptionToggle
931 info.keepShownOnClick = true
932 UIDropDownMenu_AddButton(info, level)
933 wipe(info)
934
935 info.text = L['Only Mine']
936 info.value = 'mine'
937 info.checked = icon.mine
938 info.func = Icon_OptionToggle
939 info.keepShownOnClick = true
940 UIDropDownMenu_AddButton(info, level)
941 wipe(info)
942
943 elseif obj.type == 'reactive' then
944
945 info.text = L['Show When']
946 info.value = 'SpellWhen'
947 info.hasArrow = true
948 UIDropDownMenu_AddButton(info, level)
949 wipe(info)
950
951 info.text = L['Show Timer']
952 info.value = 'timer'
953 info.checked = obj.timer
954 info.func = Icon_OptionToggle
955 info.keepShownOnClick = true
956 UIDropDownMenu_AddButton(info, level)
957 wipe(info)
958
959 elseif obj.type == 'wpnenchant' then
960
961 info.text = L['Weapon Slot']
962 info.value = 'WpnEnchantType'
963 info.hasArrow = true
964 info.keepShownOnClick = true
965 UIDropDownMenu_AddButton(info, level)
966 wipe(info)
967
968 info.text = L['Show When']
969 info.value = 'AuraWhen'
970 info.hasArrow = true
971 UIDropDownMenu_AddButton(info, level)
972 wipe(info)
973
974 info.text = L['Show Timer']
975 info.value = 'timer'
976 info.checked = obj.timer
977 info.func = Icon_OptionToggle
978 info.keepShownOnClick = true
979 UIDropDownMenu_AddButton(info, level)
980 wipe(info)
981
982 elseif obj.type == 'totem' then
983
984 info.text = L['Unit']
985 info.value = 'Unit'
986 info.hasArrow = true
987 info.keepShownOnClick = true
988 UIDropDownMenu_AddButton(info, level)
989 wipe(info)
990
991 info.text = L['Show When']
992 info.value = 'AuraWhen'
993 info.hasArrow = true
994 UIDropDownMenu_AddButton(info, level)
995 wipe(info)
996
997 info.text = L['Show Timer']
998 info.value = 'timer'
999 info.checked = obj.timer
1000 info.func = Icon_OptionToggle
1001 info.keepShownOnClick = true
1002 UIDropDownMenu_AddButton(info, level)
1003 wipe(info)
1004
1005 end
1006 end
1007
1008 -- adding the clear settings button
1009 if (obj.name and obj.name ~= '') or obj.type ~= '' then
1010 -- separator
1011 info.disabled = true
1012 UIDropDownMenu_AddButton(info)
1013 wipe(info)
1014
1015 -- clear settings button
1016 info.text = L['Clear Settings']
1017 info.func = Icon_OptionClear
1018 UIDropDownMenu_AddButton(info)
1019 wipe(info)
1020 end
1021 end
1022
1023 ToggleDropDownMenu(1, nil, menu, 'cursor', 0, 0)
1024 end
1025 end
1026
1027 --
1028 -- handles icon's OnMouseDown event
1029 --
1030 local function Icon_OnMouseDown(self, button)
1031 if button == 'LeftButton' then
1032 local f = self:GetParent()
1033 f:StartMoving()
1034 end
1035 end
1036 --
1037 -- hands icon's OnMouseUp event
1038 --
1039 local function Icon_OnMouseUp(self, button)
1040 -- opening the menu
1041 if button == 'RightButton' then
1042 StaticPopup_Hide('KTRACKER_DIALOG_RESET')
1043 StaticPopup_Hide('KTRACKER_DIALOG_CLEAR')
1044 StaticPopup_Hide('KTRACKER_DIALOG_NAME')
1045 StaticPopup_Hide('KTRACKER_DIALOG_UNITNAME')
1046 StaticPopup_Hide('KTRACKER_DIALOG_SHARE_SEND')
1047 StaticPopup_Hide('KTRACKER_DIALOG_SHARE_RECEIVE')
1048 PlaySound('UChatScrollButton')
1049 Icon_OpenMenu(self)
1050
1051 elseif button == 'MiddleButton' then
1052 local parent = self:GetParent()
1053 local g, i = parent:GetID(), self:GetID()
1054 local DB = KTrackerDB.groups[g].icons[i]
1055 if DB then
1056 DB.enabled = not DB.enabled
1057 CloseDropDownMenus()
1058 Icon:Load(self, g, i)
1059 end
1060
1061 elseif button == 'LeftButton' then
1062 local f = self:GetParent()
1063 f:StopMovingOrSizing()
1064
1065 local id = 'KTrackerGroup'..f:GetID()
1066 local obj = KTrackerCharDB.groups[id]
1067 if obj then
1068 local point, _, relativePoint, xOfs, yOfs = f:GetPoint()
1069 obj.position.xOfs = xOfs
1070 obj.position.yOfs = yOfs
1071 obj.position.point = point
1072 obj.position.relativePoint = relativePoint
1073 Group:Load(id)
1074 end
1075 end
1076 end
1077
1078 --
1079 -- called whenever an icon needs to be updated
1080 --
1081 function Icon:Load(icon, groupID, iconID)
1082 -- we make sure the icon frame exists.
1083 if not icon then return end
1084
1085 -- hold the reference so we call later update the icon.
1086 local obj = KTrackerDB.groups[groupID].icons[iconID]
1087 -- we make sure to create the icon in case it was missing
1088 if not obj then
1089 KTrackerDB.groups[groupID].icons[iconID] = utils.deepCopy(def.icon)
1090 obj = KTrackerDB.groups[groupID].icons[iconID]
1091 end
1092 utils.mixTable(icon, obj)
1093
1094 local iconName = icon:GetName()
1095 icon.texture = _G[iconName..'Icon']
1096 icon.stacks = _G[iconName..'Stacks']
1097 icon.cooldown = _G[iconName..'Cooldown']
1098 icon.coords = {groupID, iconID} -- used to alter database
1099
1100 icon.stacks:Hide()
1101 icon.cooldown:Hide()
1102
1103 if obj.enabled then
1104 icon:SetAlpha(1)
1105 else
1106 icon:SetAlpha(0.4)
1107 self:ClearScripts(icon)
1108 end
1109
1110 utils.show(icon)
1111 Icon:LoadTexture(icon)
1112
1113 if addon.locked then
1114 icon:EnableMouse(0)
1115
1116 if icon.name == '' and icon.type ~= 'wpnenchant' then
1117 icon.enabled = false
1118 end
1119
1120 if icon.enabled then
1121 Icon:SetScripts(icon)
1122 Icon:Check(icon)
1123 else
1124 Icon:ClearScripts(icon)
1125 utils.hide(icon)
1126 end
1127 else
1128 icon:EnableMouse(1)
1129 Icon:ClearScripts(icon)
1130 icon.texture:SetVertexColor(1, 1, 1, 1)
1131
1132 -- set icon tooltip and needed scripts
1133 utils.setTooltip(icon, {
1134 L['Right-click for icon options.'],
1135 L['Left-click to move the group.'],
1136 L['Middle-click to enable/disable.'],
1137 L['Type "|caaf49141/kt config|r" for addon config']
1138 }, nil, titleString)
1139 icon:SetScript('OnMouseDown', Icon_OnMouseDown)
1140 icon:SetScript('OnMouseUp', Icon_OnMouseUp)
1141 end
1142 end
1143
1144 --
1145 -- pop up dialog that allows the user to set the name
1146 --
1147 StaticPopupDialogs['KTRACKER_DIALOG_NAME'] = {
1148 text = L['Enter the name or ID of the spell. You can add multiple buffs or debuffs by separatig them with semicolons.'],
1149 button1 = SAVE,
1150 button2 = CANCEL,
1151 hasEditBox = true,
1152 hasWideEditBox = true,
1153 maxLetters = 254,
1154 timeout = 0,
1155 whileDead = true,
1156 hideOnEscape = true,
1157 OnShow = function(self)
1158 local g, i = current.group, current.icon
1159 local obj = KTrackerDB.groups[g].icons[i]
1160 if not obj then self:Hide() end
1161 _G[self:GetName()..'WideEditBox']:SetText(obj.name)
1162 _G[self:GetName()..'WideEditBox']:SetFocus()
1163 end,
1164 OnHide = function(self)
1165 if ChatFrameEditBox and ChatFrameEditBox:IsVisible() then
1166 ChatFrameEditBox:SetFocus()
1167 end
1168 _G[self:GetName()..'WideEditBox']:SetText('')
1169 _G[self:GetName()..'WideEditBox']:ClearFocus()
1170 wipe(current)
1171 end,
1172 OnAccept = function(self)
1173 local text = _G[self:GetName()..'WideEditBox']:GetText()
1174 local g, i = current.group, current.icon
1175 local obj = KTrackerDB.groups[g].icons[i]
1176 if obj then
1177 obj.name = text:trim()
1178 wipe(current)
1179 Icon:Load(_G['KTrackerGroup'..g..'Icon'..i], g, i)
1180 end
1181 self:Hide()
1182 end,
1183 EditBoxOnEnterPressed = function(self)
1184 local parent = self:GetParent()
1185 local text = _G[parent:GetName()..'WideEditBox']:GetText()
1186 local g, i = current.group, current.icon
1187 local obj = KTrackerDB.groups[g].icons[i]
1188 if obj then
1189 obj.name = text:trim()
1190 wipe(current)
1191 Icon:Load(_G['KTrackerGroup'..g..'Icon'..i], g, i)
1192 end
1193 parent:Hide()
1194 end,
1195 EditBoxOnEscapePressed = function(self)
1196 wipe(current)
1197 self:GetParent():Hide()
1198 end
1199 }
1200
1201 --
1202 -- pop up dialog that allows the user to set custom icon unit
1203 --
1204 StaticPopupDialogs['KTRACKER_DIALOG_UNITNAME'] = {
1205 text = L['Enter the unit name on which you want to track the aura.'],
1206 button1 = SAVE,
1207 button2 = CANCEL,
1208 hasEditBox = true,
1209 maxLetters = 12,
1210 timeout = 0,
1211 whileDead = true,
1212 hideOnEscape = true,
1213 OnShow = function(self, icon)
1214 if icon then
1215 _G[self:GetName()..'EditBox']:SetText(icon.unit)
1216 _G[self:GetName()..'EditBox']:SetFocus()
1217 end
1218 end,
1219 OnHide = function(self)
1220 if ChatFrameEditBox and ChatFrameEditBox:IsVisible() then
1221 ChatFrameEditBox:SetFocus()
1222 end
1223 _G[self:GetName()..'EditBox']:SetText('')
1224 _G[self:GetName()..'EditBox']:ClearFocus()
1225 end,
1226 OnAccept = function(self)
1227 local unit = _G[self:GetName()..'EditBox']:GetText():trim()
1228 if unit == '' then
1229 unit = 'player'
1230 elseif not UnitExists(unit) then
1231 addon:PrintError(L['Could not find that unit.'])
1232 return
1233 end
1234 local g, i = current.group, current.icon
1235 local obj = KTrackerDB.groups[g].icons[i]
1236 if obj then
1237 obj.unit = unit
1238 wipe(current)
1239 Icon:Load(_G['KTrackerGroup'..g..'Icon'..i], g, i)
1240 end
1241 self:Hide()
1242 end,
1243 EditBoxOnEnterPressed = function(self)
1244 local parent = self:GetParent()
1245 local unit = _G[parent:GetName()..'EditBox']:GetText()
1246 if unit == '' then
1247 unit = 'player'
1248 elseif not UnitExists(unit) then
1249 addon:PrintError(L['Could not find that unit.'])
1250 return
1251 end
1252 local g, i = current.group, current.icon
1253 local obj = KTrackerDB.groups[g].icons[i]
1254 if obj then
1255 obj.unit = unit
1256 wipe(current)
1257 Icon:Load(_G['KTrackerGroup'..g..'Icon'..i], g, i)
1258 end
1259 parent:Hide()
1260 end,
1261 EditBoxOnEscapePressed = function(self)
1262 wipe(current)
1263 self:GetParent():Hide()
1264 end
1265 }
1266end
1267
1268--
1269-- called whenever an icon needs to has its scripts removed
1270--
1271function Icon:ClearScripts(icon)
1272 if not icon then return end
1273 -- remove scripts
1274 icon:SetScript('OnEvent', nil)
1275 icon:SetScript('OnUpdate', nil)
1276 -- unregister events
1277 icon:UnregisterEvent('ACTIONBAR_UPDATE_COOLDOWN')
1278 icon:UnregisterEvent('ACTIONBAR_UPDATE_USABLE')
1279 icon:UnregisterEvent('BAG_UPDATE_COOLDOWN')
1280 icon:UnregisterEvent('COMBAT_LOG_EVENT_UNFILTERED')
1281 icon:UnregisterEvent('PLAYER_FOCUS_CHANGED')
1282 icon:UnregisterEvent('PLAYER_TARGET')
1283 icon:UnregisterEvent('PLAYER_TARGET_CHANGED')
1284 icon:UnregisterEvent('PLAYER_TOTEM_UPDATE')
1285 icon:UnregisterEvent('UNIT_AURA')
1286 icon:UnregisterEvent('UNIT_INVENTORY_CHANGED')
1287end
1288
1289--
1290-- simply changes the icon texture
1291--
1292function Icon:LoadTexture(icon)
1293 if not icon then return end
1294
1295 local noTexture, texture
1296
1297 if icon.type == 'cooldown' or icon.type == 'reactive' then
1298
1299 noTexture = textures[1]
1300
1301 if icon.subtype == 'spell' then
1302 local name = addon:GetSpellNames(icon.name, true)
1303 texture = GetSpellTexture(name)
1304 elseif icon.subtype == 'item' then
1305 local name = addon:GetItemNames(icon.name, true)
1306 texture = select(10, GetItemInfo(name))
1307 end
1308
1309 elseif icon.type == 'aura' then
1310
1311 noTexture = textures[2]
1312 texture = icon.filepath
1313
1314 if not texture and icon.name ~= '' then
1315 local name = addon:GetSpellNames(icon.name, true)
1316 texture = GetSpellTexture(name)
1317 end
1318
1319 elseif icon.type == 'wpnenchant' then
1320
1321 noTexture = textures[1]
1322
1323 local slot
1324 if icon.subtype == 'mainhand' then
1325 slot = select(1, GetInventorySlotInfo('MainHandSlot'))
1326 elseif icon.subtype == 'offhand' then
1327 slot = select(1, GetInventorySlotInfo('SecondaryHandSlot'))
1328 end
1329 texture = GetInventoryItemTexture('player', slot)
1330
1331 elseif icon.type == 'totem' then
1332
1333 noTexture = textures[1]
1334 local name = addon:GetSpellNames(icon.name, true)
1335 texture = GetSpellTexture(name)
1336
1337 end
1338
1339 utils.setTexture(icon.texture, texture, noTexture, texture)
1340end
1341
1342--
1343-- checks the icon
1344--
1345function Icon:Check(icon)
1346 if not icon then return end
1347
1348 -- icon transparency depends on when to use it
1349 if icon.when ==1 then
1350 icon.alphap = 1
1351 icon.alphan = 0
1352 elseif icon.when ==0 then
1353 icon.alphap = 1
1354 icon.alphan = 1
1355 elseif icon.when ==-1 then
1356 icon.alphap = 0
1357 icon.alphan = 1
1358 else
1359 error(L:F('Alpha not assigned: %s', icon.name))
1360 icon.alphap = 1
1361 icon.alphan = 1
1362 end
1363
1364 -- cooldowns or reactive
1365 if icon.type == 'cooldown' or icon.type == 'reactive' then
1366 local startTime, duration
1367
1368 if icon.subtype == 'spell' then
1369 icon.rname = addon:GetSpellNames(icon.name, true)
1370 startTime, duration, _ = GetSpellCooldown(icon.rname)
1371 elseif icon.subtype == 'item' then
1372 icon.rname = addon:GetItemNames(icon.name, true)
1373 startTime, duration, _ = GetItemCooldown(icon.rname)
1374 end
1375
1376 if icon.rname and icon.timer and duration then
1377 if duration > 0 then
1378 CooldownFrame_SetTimer(icon.cooldown, startTime, duration, 1)
1379 else
1380 CooldownFrame_SetTimer(icon.cooldown, 0, 0, 0)
1381 end
1382 end
1383
1384 -- buffs and debuffs
1385 elseif icon.type == 'aura' and UnitExists(icon.unit) then
1386 local auras = addon:GetSpellNames(icon.name)
1387 local i, name, hasAura
1388
1389 local filter = icon.subtype
1390 if icon.mine then filter = filter..'|PLAYER' end
1391
1392 for i, name in ipairs(auras) do
1393 local auraName, _, iconTexture, count, _, duration, expirationTime, unitCaster = UnitAura(icon.unit, name, nil, filter)
1394 if auraName then
1395 if icon.texture:GetTexture() ~= iconTexture then
1396 icon.texture:SetTexture(iconTexture)
1397 KTrackerDB.groups[icon.coords[1]].icons[icon.coords[2]].filepath = iconTexture
1398 end
1399
1400 icon:SetAlpha(icon.alphap)
1401 icon.texture:SetVertexColor(1, 1, 1, 1)
1402
1403 if count > 1 then
1404 icon.stacks:SetText(count)
1405 icon.stacks:Show()
1406 else
1407 icon.stacks:Hide()
1408 end
1409
1410 if icon.timer and not UnitIsDead(icon.unit) then
1411 CooldownFrame_SetTimer(icon.cooldown, expirationTime-duration, duration, 1)
1412 end
1413
1414 -- we need this later
1415 icon.duration = duration
1416 icon.rname = auraName
1417 hasAura = true
1418 end
1419 end
1420
1421 if not hasAura then
1422 icon:SetAlpha(icon.alphan)
1423
1424 if icon.alphap ==1 and icon.alphan ==1 then
1425 icon.texture:SetVertexColor(1, 0.35, 0.35, 1)
1426 end
1427
1428 icon.stacks:Hide()
1429
1430 if icon.timer then
1431 CooldownFrame_SetTimer(icon.cooldown, 0, 0, 0)
1432 end
1433
1434 icon.duration = 0
1435 icon.rname = auras[1]
1436 end
1437
1438 elseif icon.type == 'wpnenchant' then
1439
1440 local now = GetTime()
1441 local duration = 0
1442 local hasMHEnchant, mhExpiration, mhCharges, hasOHEnchant, ohExpiration, ohCharges = GetWeaponEnchantInfo()
1443
1444 if icon.subtype == 'mainhand' and hasMHEnchant then
1445 icon:SetAlpha(icon.alphap)
1446
1447 if mhCharges > 0 then
1448 icon.stacks:SetText(mhCharges)
1449 utils.show(icon.stacks)
1450 else
1451 icon.stacks:SetText('')
1452 utils.hide(icon.stacks)
1453 end
1454
1455 duration = mhExpiration/1000
1456 elseif icon.subtype == 'offhand' and hasOHEnchant then
1457 icon:SetAlpha(icon.alphap)
1458
1459 if ohCharges > 0 then
1460 icon.stacks:SetText(ohCharges)
1461 utils.show(icon.stacks)
1462 else
1463 icon.stacks:SetText('')
1464 utils.hide(icon.stacks)
1465 end
1466
1467 duration = ohExpiration/1000
1468 else
1469 icon:SetAlpha(icon.alphan)
1470 end
1471
1472 if icon.timer and duration > 0 then
1473 icon.texture:SetVertexColor(1, 1, 1, 1)
1474 CooldownFrame_SetTimer(icon.cooldown, now, duration, 1)
1475 elseif icon.timer then
1476 icon.texture:SetVertexColor(1, 0.35, 0.35, 1)
1477 CooldownFrame_SetTimer(icon.cooldown, 0, 0, 0)
1478 end
1479
1480 icon.duration = duration
1481
1482 elseif icon.type == 'totem' then
1483
1484 local totems = addon:GetSpellNames(icon.name)
1485 local found, texture, rname
1486 local startTime, duration = 0, 0
1487 local precise = GetTime()
1488
1489 for i = 1, 4 do
1490 local haveTotem, totemName, totemStartTime, totemDuration, totemTexture = GetTotemInfo(i)
1491 for i, name in ipairs(totems) do
1492 local spellName = select(1, GetSpellInfo(name))
1493 if totemName and totemName:find(name) then
1494 found, texture, rname = true, totemTexture, name
1495
1496 startTime = ((precise-totemStartTime) > 1) and totemStartTime+1 or precise
1497 duration = totemDuration
1498
1499 found = true
1500 break
1501 end
1502 end
1503 end
1504
1505 icon.rname = rname
1506 icon.duration = duration
1507
1508 if found then
1509 icon:SetAlpha(icon.alphap)
1510 icon.texture:SetVertexColor(1, 1, 1, 1)
1511
1512 if icon.timer then
1513 CooldownFrame_SetTimer(icon.cooldown, startTime, duration, 1)
1514 end
1515 else
1516 icon:SetAlpha(icon.alphan)
1517 if icon.alphan ==1 and icon.alphap ==1 then
1518 icon.texture:SetVertexColor(1, 0.35, 0.35, 1)
1519 end
1520 CooldownFrame_SetTimer(icon.cooldown, 0, 0, 0)
1521 end
1522 end
1523end
1524
1525do
1526 -- list of aura events
1527 local auraEvents = {
1528 SPELL_AURA_APPLIED = true,
1529 SPELL_AURA_APPLIED_DOSE = true,
1530 SPELL_AURA_BROKEN = true,
1531 SPELL_AURA_BROKEN_SPELL = true,
1532 SPELL_AURA_REFRESH = true,
1533 SPELL_AURA_REMOVED = true,
1534 SPELL_AURA_REMOVED_DOSE = true
1535 }
1536
1537 -- list of other events to check
1538 local otherEvents = {
1539 ACTIONBAR_UPDATE_COOLDOWN = true,
1540 ACTIONBAR_UPDATE_USABLE = true,
1541 PLAYER_REGEN_DISABLED = true,
1542 PLAYER_REGEN_ENABLED = true,
1543 PLAYER_TOTEM_UPDATE = true
1544 }
1545
1546 --
1547 -- handles icons OnEvent event
1548 --
1549 local function Icon_OnEvent(self, event, ...)
1550 if event == 'COMBAT_LOG_EVENT_UNFILTERED' then
1551 local e = select(2, ...)
1552 if auraEvents[e] then
1553 local destGUID = select(6, ...)
1554 if destGUID ==UnitGUID(self.unit) then
1555 local id, name = select(9, ...)
1556 if self.name:find(id) or self.name:find(name) then
1557 Icon:Check(self)
1558 end
1559 end
1560 end
1561
1562 elseif otherEvents[event] then
1563 Icon:Check(self)
1564 elseif event == 'UNIT_AURA' then
1565 local unit = select(1, ...)
1566 if UnitExists(unit) then
1567 Icon:Check(self)
1568 else
1569 local name = select(1, UnitName(unit))
1570 if name:lower() == self.unit:lower() then
1571 Icon:Check(self)
1572 end
1573 end
1574 elseif event == 'BAG_UPDATE_COOLDOWN' then
1575 Icon:Check(self)
1576 elseif event == 'UNIT_INVENTORY_CHANGED' and select(1, ...) == 'player' then
1577 Icon:Check(self)
1578 elseif event == 'PLAYER_TARGET_CHANGED' or event == 'PLAYER_FOCUS_CHANGED' then
1579 Icon:Check(self)
1580 elseif event == 'UNIT_TARGET' and select(1, ...) == 'target' then
1581 Icon:Check(self)
1582 end
1583 end
1584
1585 --
1586 -- handles icons OnUpdate event
1587 --
1588 local function Icon_OnUpdate(self, elapsed)
1589 if utils.update(self, self:GetName(), 0.1, elapsed) then
1590
1591 -- reactive spell or spell cooldown
1592 if self.rname and self.subtype == 'spell' then
1593
1594 local startTime, duration, _ = GetSpellCooldown(self.rname)
1595 local onGCD = (addon:GetGCD() ==duration and duration > 0)
1596 local usable, noMana = IsUsableSpell(self.rname)
1597 local inRange = IsSpellInRange(self.rname, self.unit)
1598 local _, _, _, _, _, _, _, minRange, maxRange = GetSpellInfo(self.rname)
1599 if not maxRange or inRange ==nil then inRange = 1 end
1600
1601 if self.type == 'reactive' and usable then
1602 if inRange and not noMana then
1603 self.texture:SetVertexColor(1, 1, 1, 1)
1604 self:SetAlpha(self.alphap)
1605 Icon_EffectTrigger(self)
1606 elseif not inRange or noMana then
1607 self.texture:SetVertexColor(0.35, 0.35, 0.35, 1)
1608 self:SetAlpha(self.alphap)
1609 else
1610 self.texture:SetVertexColor(1, 1, 1, 1)
1611 self:SetAlpha(self.alphan)
1612 Icon_EffectReset(self)
1613 end
1614 elseif self.type == 'cooldown' and duration then
1615 if (duration ==0 or onGCD) and inRange ==1 and not noMana then
1616 self.texture:SetVertexColor(1, 1, 1, 1)
1617 self:SetAlpha(self.alphap)
1618 Icon_EffectTrigger(self)
1619 elseif (duration ==0 or onGCD) and self.alphap ==1 then
1620 self.texture:SetVertexColor(0.35, 0.35, 0.35, 1)
1621 self:SetAlpha(self.alphap)
1622 else
1623 self.texture:SetVertexColor(1, 1, 1, 1)
1624 self:SetAlpha(self.alphan)
1625 Icon_EffectReset(self)
1626 end
1627 else
1628 self:SetAlpha(self.alphan)
1629 end
1630
1631 -- item cooldown
1632 elseif self.rname and self.type == 'cooldown' and self.subtype == 'item' then
1633 local _, duration, _ = GetItemCooldown(self.rname)
1634 local onGCD = addon:GetGCD()
1635
1636 if duration then
1637 if duration ==0 or onGCD ==duration then
1638 self:SetAlpha(self.alphap)
1639 Icon_EffectTrigger(self)
1640 elseif duration > 0 and onGCD ~= duration then
1641 self:SetAlpha(self.alphan)
1642 Icon_EffectReset(self)
1643 end
1644 end
1645
1646 -- buffs or debuffs
1647 elseif self.rname and self.type == 'aura' and UnitExists(self.unit) then
1648
1649 local animate
1650
1651 if self.subtype == 'HARMFUL' then
1652 local reaction = UnitReaction('player', self.unit)
1653 if reaction and reaction <= 4 then
1654 animate = (self.when >= 0 and self.duration ==0)
1655 else
1656 animate = (self.when >= 0 and self.duration > 0)
1657 end
1658 elseif self.subtype == 'HELPFUL' then
1659 animate = (self.duration <= 60)
1660 end
1661
1662 if animate then
1663 addon:TriggerEffect(self.cooldown, self.effect)
1664 end
1665
1666 -- weapon enchant/totems
1667 elseif self.type == 'wpnenchant' or (self.rname and self.type == 'totem') then
1668
1669 local animate = (self.when <= 0 and self.duration ==0)
1670
1671 if animate then
1672 addon:TriggerEffect(self.cooldown, self.effect)
1673 end
1674 end
1675 end
1676 end
1677
1678 --
1679 -- sets icon scripts and events
1680 --
1681 function Icon:SetScripts(icon)
1682 if not icon then return end
1683 icon:SetScript('OnEvent', Icon_OnEvent)
1684 icon:SetScript('OnUpdate', Icon_OnUpdate)
1685
1686 if icon.type == 'totem' then
1687 icon:RegisterEvent('PLAYER_TOTEM_UPDATE')
1688
1689 elseif icon.type == 'wpnenchant' then
1690 icon.cooldown:SetReverse(true)
1691 icon:RegisterEvent('UNIT_INVENTORY_CHANGED')
1692
1693 elseif icon.type == 'aura' then
1694 icon.cooldown:SetReverse(true)
1695 icon:RegisterEvent('UNIT_AURA')
1696 if icon.unit == 'target' then
1697 icon:RegisterEvent('PLAYER_TARGET_CHANGED')
1698 elseif icon.unit == 'focus' then
1699 icon:RegisterEvent('PLAYER_FOCUS_CHANGED')
1700 elseif icon.unit == 'targettarget' then
1701 icon:RegisterEvent('PLAYER_TARGET_CHANGED')
1702 icon:RegisterEvent('UNIT_TARGET')
1703 icon:RegisterEvent('COMBAT_LOG_EVENT_UNFILTERED')
1704 end
1705
1706 elseif icon.type == 'cooldown' or icon.type == 'reactive' then
1707 icon.cooldown:SetReverse(false)
1708 if icon.subtype == 'spell' then
1709 icon:RegisterEvent('ACTIONBAR_UPDATE_USABLE')
1710 icon:RegisterEvent('ACTIONBAR_UPDATE_COOLDOWN')
1711 elseif icon.subtype == 'item' then
1712 icon:RegisterEvent('BAG_UPDATE_COOLDOWN')
1713 end
1714 end
1715 end
1716end
1717
1718--------------------------------------------------------------------------
1719-- Talents functions
1720
1721do
1722 -- holds the character's spec
1723 local spec
1724
1725 --
1726 -- caches the character's spec
1727 --
1728 function addon:SetCurrentSpec()
1729 spec = GetActiveTalentGroup()
1730 end
1731
1732 --
1733 -- returns the character's current spec
1734 --
1735 function addon:GetCurrentSpec()
1736 if not spec then self:SetCurrentSpec() end
1737 return spec
1738 end
1739end
1740
1741--------------------------------------------------------------------------
1742-- Spells functions
1743
1744do
1745 --
1746 -- list of terms that can be used by the user to the track
1747 -- multiple buffs or debuffs
1748 --
1749 local equivalents = {
1750 Bleeding = 'Pounce Bleed;Rake;Rip;Lacerate;Rupture;Garrote;Savage Rend;Rend;Deep Wound',
1751 DontMelee = 'Berserk;Evasion;Shield Wall;Retaliation;Dispersion;Hand of Sacrifice;Hand of Protection;Divine Shield;Divine Protection;Ice Block;Icebound Fortitude;Cyclone;Banish',
1752 FaerieFires = 'Faerie Fire;Faerie Fire (Feral)',
1753 ImmuneToMagicCC = 'Divine Shield;Ice Block;The Beast Within;Beastial Wrath;Cyclone;Banish',
1754 ImmuneToStun = 'Divine Shield;Ice Block;The Beast Within;Beastial Wrath;Icebound Fortitude;Hand of Protection;Cyclone;Banish',
1755 Incapacitated = 'Gouge;Maim;Repentance;Reckless Charge;Hungering Cold',
1756 MeleeSlowed = 'Rocket Burst;Infected Wounds;Judgements of the Just;Earth Shock;Thunder Clap;Icy Touch',
1757 MovementSlowed = 'Incapacitating Shout;Chains of Ice;Icy Clutch;Slow;Daze;Hamstring;Piercing Howl;Wing Clip;Frost Trap Aura;Frostbolt;Cone of Cold;Blast Wave;Mind Flay;Crippling Poison;Deadly Throw;Frost Shock;Earthbind;Curse of Exhaustion',
1758 Stunned = 'Reckless Charge;Bash;Maim;Pounce;Starfire Stun;Intimidation;Impact;Hammer of Justice;Stun;Blackout;Kidney Shot;Cheap Shot;Shadowfury;Intercept;Charge Stun;Concussion Blow;War Stomp',
1759 StunnedOrIncapacitated = 'Gouge;Maim;Repentance;Reckless Charge;Hungering Cold;Bash;Pounce;Starfire Stun;Intimidation;Impact;Hammer of Justice;Stun;Blackout;Kidney Shot;Cheap Shot;Shadowfury;Intercept;Charge Stun;Concussion Blow;War Stomp',
1760 VulnerableToBleed = 'Mangle (Cat);Mangle (Bear);Trauma',
1761 WoTLKDebuffs = '71237;71289;71204;72293;69279;69674;72272;73020;70447;70672;70911;72999;71822;70867;71340;71267;70923;70873;70106;69762;69766;70128;70126;70541;70337;69409;69409;73797;73798;74453;74367;74562;74792'
1762 }
1763
1764 --
1765 -- splits the names of spells if separated using semicolons.
1766 --
1767 local function SplitNames(str, item)
1768 local list = {}
1769 if (str:find(';') ~= nil) then
1770 list = {strsplit(';', str)}
1771 else
1772 list = {str}
1773 end
1774 local i, name
1775 for i, name in ipairs(list) do
1776 if tonumber(name) ~= nil then
1777 list[i] = (item == 'item') and GetItemInfo(name) or GetSpellInfo(name)
1778 end
1779 end
1780 return list
1781 end
1782
1783 --
1784 -- returns a single or a list of spells names
1785 --
1786 function addon:GetSpellNames(str, first)
1787 local list = SplitNames(equivalents[str] or str)
1788 return first and list[1] or list
1789 end
1790
1791 --
1792 -- returns a single or a list of item names
1793 --
1794 function addon:GetItemNames(str, first)
1795 local list = SplitNames(str)
1796 return first and list[1] or list
1797 end
1798
1799 --
1800 -- returns the global cooldown if the player using an instant cast spell
1801 --
1802 function addon:GetGCD()
1803 local ver = select(4, GetBuildInfo())
1804 local spells
1805
1806 if ver >= 30000 then
1807 spells = {
1808 ROGUE = GetSpellInfo(1752), -- sinister strike
1809 PRIEST = GetSpellInfo(139), -- renew
1810 DRUID = GetSpellInfo(774), -- rejuvenation
1811 WARRIOR = GetSpellInfo(6673), -- battle shout
1812 MAGE = GetSpellInfo(168), -- frost armor
1813 WARLOCK = GetSpellInfo(1454), -- life tap
1814 PALADIN = GetSpellInfo(1152), -- purify
1815 SHAMAN = GetSpellInfo(324), -- lightning shield
1816 HUNTER = GetSpellInfo(1978), -- serpent sting
1817 DEATHKNIGHT = GetSpellInfo(45462) -- plague strike
1818 }
1819 else
1820 spells = {
1821 ROGUE = GetSpellInfo(1752), -- sinister strike
1822 PRIEST = GetSpellInfo(139), -- renew
1823 DRUID = GetSpellInfo(774), -- rejuvenation
1824 WARRIOR = GetSpellInfo(6673), -- battle shout
1825 MAGE = GetSpellInfo(168), -- frost armor
1826 WARLOCK = GetSpellInfo(1454), -- life tap
1827 PALADIN = GetSpellInfo(1152), -- purify
1828 SHAMAN = GetSpellInfo(324), -- lightning shield
1829 HUNTER = GetSpellInfo(1978) -- serpent sting
1830 }
1831 end
1832 local _, unitClass = UnitClass('player')
1833 return select(2, GetSpellCooldown(self:GetSpellNames(spells[unitClass], true)))
1834 end
1835
1836end
1837
1838--------------------------------------------------------------------------
1839-- Animations functions
1840
1841do
1842 --
1843 -- holds the list of already animated spell icons
1844 --
1845 local animated = {}
1846
1847 --
1848 -- used to trigger icons animation
1849 --
1850 function Icon_EffectTrigger(icon)
1851 if icon and not animated[icon:GetName()] then
1852 addon:TriggerEffect(icon.cooldown, icon.effect)
1853 animated[icon:GetName()] = true
1854 end
1855 end
1856
1857 --
1858 -- resets the icon's animation
1859 --
1860 function Icon_EffectReset(icon)
1861 if icon and animated[icon:GetName()] then
1862 animated[icon:GetName()] = nil
1863 end
1864 end
1865 --
1866 -- register a new animation effect
1867 -- TODO: work more on it.
1868 --
1869 function addon:RegisterEffect(effect)
1870 if not self:GetEffect(effect.id) then
1871 self.effects = self.effects or {}
1872 tinsert(self.effects, effect)
1873 end
1874 end
1875
1876 --
1877 -- retrieves an previously registered effect
1878 --
1879 function addon:GetEffect(id)
1880 if self.effects then
1881 for _, effect in ipairs(self.effects) do
1882 if effect.id ==id then
1883 return effect
1884 end
1885 end
1886 end
1887 end
1888
1889 --
1890 -- triggers an animation effect
1891 --
1892 function addon:TriggerEffect(cooldown, id)
1893 local effect = self:GetEffect(id)
1894 if effect then
1895 effect:Run(cooldown)
1896 end
1897 end
1898
1899 -- --------------- pulse effect --------------- --
1900
1901 do
1902 --
1903 -- we create the pulse table first
1904 --
1905 local Pulse = utils.newClass('Frame')
1906
1907 function Pulse:New(parent)
1908 local f = self:Bind(CreateFrame('Frame', nil, parent))
1909 f:SetAllPoints(parent)
1910 f:SetToplevel(true)
1911 f:Hide()
1912
1913 f.animation = f:CreatePulseAnimation()
1914 f:SetScript('OnHide', f.OnHide)
1915
1916 local icon = f:CreateTexture(nil, 'OVERLAY')
1917 icon:SetPoint('CENTER')
1918 icon:SetBlendMode('ADD')
1919 icon:SetAllPoints(f)
1920 f.icon = icon
1921
1922 return f
1923 end
1924
1925 do
1926 --
1927 -- trigger by the end of the animation
1928 --
1929 local function animation_OnFinished(self)
1930 local parent = self:GetParent()
1931 utils.hide(parent)
1932 end
1933
1934 --
1935 -- creates the pulse animation
1936 --
1937 function Pulse:CreatePulseAnimation()
1938 local g = self:CreateAnimationGroup()
1939 g:SetLooping('NONE')
1940 g:SetScript('OnFinished', animation_OnFinished)
1941
1942 local grow = g:CreateAnimation('scale')
1943 grow:SetScale(1.5, 1.5)
1944 grow:SetOrigin('CENTER', 0, 0)
1945 grow:SetDuration(0.2)
1946 grow:SetOrder(1)
1947
1948 local shrink = g:CreateAnimation('scale')
1949 shrink:SetScale(-1.5, -1.5)
1950 shrink:SetOrigin('CENTER', 0, 0)
1951 shrink:SetDuration(0.2)
1952 shrink:SetOrder(2)
1953
1954 return g
1955 end
1956 end
1957
1958 --
1959 -- handles animation hide
1960 --
1961 function Pulse:OnHide()
1962 self.animation:Finish()
1963 self:Hide()
1964 end
1965
1966 --
1967 -- starts the animation
1968 --
1969 function Pulse:Start(texture)
1970 if not self.animation:IsPlaying() then
1971 local icon = self.icon
1972 local r, g, b = icon:GetVertexColor()
1973 icon:SetVertexColor(r, g, b, 0.7)
1974 icon:SetTexture(texture:GetTexture())
1975 self:Show()
1976 self.animation:Play()
1977 end
1978 end
1979
1980 --
1981 -- we now register the pulse effect
1982 --
1983 do
1984 local pulses = setmetatable({}, {__index = function(t, k)
1985 local f = Pulse:New(k)
1986 t[k] = f
1987 return f
1988 end})
1989
1990 local function getTexture(frame)
1991 if not frame then return end
1992
1993 local icon = frame.icon or frame.texture
1994 if not icon then
1995 local name = frame:GetName()
1996 icon = _G[name..'Icon'] or _G[name..'Texture']
1997 end
1998
1999 if icon and (icon.GetNormalTexture or icon.GetTexture) then
2000 return icon
2001 end
2002 end
2003
2004 -- finish
2005 addon:RegisterEffect{
2006 id = 'pulse',
2007 name = L['Pulse'],
2008 Run = function(self, cooldown)
2009 local p = cooldown:GetParent()
2010 local texture = getTexture(p)
2011 if texture then
2012 pulses[p]:Start(texture)
2013 end
2014 end
2015 }
2016 end
2017 end
2018
2019 -- --------------- shine effect --------------- --
2020
2021 do
2022 --
2023 -- we create the shine table first
2024 --
2025 local Shine = utils.newClass('Frame')
2026
2027 function Shine:New(parent)
2028 local f = self:Bind(CreateFrame('Frame', nil, parent))
2029 f:SetAllPoints(parent)
2030 f:SetToplevel(true)
2031 f:Hide()
2032
2033 f.animation = f:CreateShineAnimation()
2034 f:SetScript('OnHide', f.OnHide)
2035
2036 local icon = f:CreateTexture(nil, 'OVERLAY')
2037 icon:SetPoint('CENTER')
2038 icon:SetBlendMode('ADD')
2039 icon:SetAllPoints(f)
2040 icon:SetTexture('Interface\\Cooldown\\star4')
2041
2042 return f
2043 end
2044
2045 do
2046
2047 local function animation_OnFinished(self)
2048 local parent = self:GetParent()
2049 if parent:IsShown() then
2050 parent:Hide()
2051 end
2052 end
2053
2054 function Shine:CreateShineAnimation()
2055 local g = self:CreateAnimationGroup()
2056 g:SetLooping('NONE')
2057 g:SetScript('OnFinished', animation_OnFinished)
2058
2059 local startAlpha = g:CreateAnimation('Alpha')
2060 startAlpha:SetChange(-1)
2061 startAlpha:SetDuration(0)
2062 startAlpha:SetOrder(0)
2063
2064 local grow = g:CreateAnimation('Scale')
2065 grow:SetOrigin('CENTER', 0, 0)
2066 grow:SetScale(2, 2)
2067 grow:SetDuration(0.2)
2068 grow:SetOrder(1)
2069
2070 local brighten = g:CreateAnimation('Alpha')
2071 brighten:SetChange(1)
2072 brighten:SetDuration(0.2)
2073 brighten:SetOrder(1)
2074
2075 local shrink = g:CreateAnimation('Scale')
2076 shrink:SetOrigin('CENTER', 0, 0)
2077 shrink:SetScale(-2, -2)
2078 shrink:SetDuration(0.2)
2079 shrink:SetOrder(2)
2080
2081 local fade = g:CreateAnimation('Alpha')
2082 fade:SetChange(-1)
2083 fade:SetDuration(0.2)
2084 fade:SetOrder(2)
2085
2086 return g
2087 end
2088
2089 end
2090
2091 function Shine:OnHide()
2092 self.animation:Finish()
2093 self:Hide()
2094 end
2095
2096 function Shine:Start(texture)
2097 if not self.animation:IsPlaying() then
2098 self:Show()
2099 self.animation:Play()
2100 end
2101 end
2102
2103 -- register the effect
2104 do
2105
2106 local shines = setmetatable({}, {__index = function(t, k)
2107 local f = Shine:New(k)
2108 t[k] = f
2109 return f
2110 end})
2111
2112 addon:RegisterEffect{
2113 id = 'shine',
2114 name = L['Shine'],
2115 Run = function(self, cooldown)
2116 local p = cooldown:GetParent()
2117 if p then shines[p]:Start() end
2118 end
2119 }
2120
2121 end
2122 end
2123end
2124
2125--------------------------------------------------------------------------
2126-- Cooldown count functions
2127
2128do
2129 local Cooldown_OnSetCooldown
2130 local font = _G.STANDARD_TEXT_FONT
2131 local min, floor = _G.math.min, _G.math.floor
2132
2133 do
2134 --
2135 -- prepare the text to show, the next update time
2136 -- the text scale and color
2137 --
2138 local function Cooldown_Format(sec)
2139
2140 local text, nextUpdate
2141 local scale, color = 0.7, {1, 1, 1, 1}
2142
2143 if sec >= 86400 then -- days
2144 text = format('%dd', floor(sec/86400 + 0.5))
2145 nextUpdate = sec % 86400
2146 color = {0.7, 0.7, 0.7, 1}
2147
2148 elseif sec >= 3600 then -- hours
2149 text = format('%dh', floor(sec/3600 + 0.5))
2150 nextUpdate = sec % 3600
2151 color = {0.7, 0.7, 0.7, 1}
2152
2153 elseif sec >= 60 then -- minutes
2154 text = format('%dm', floor(sec/60 + 0.5))
2155 nextUpdate = sec % 60
2156 scale = 0.8
2157
2158 else -- seconds
2159 text = floor(sec + 0.5)
2160 nextUpdate = sec - floor(sec)
2161 scale = 1
2162 color = {1, 1, 0, 1}
2163 if text <= 5.5 then
2164 scale = 1.25
2165 color = {1, 0, 0, 1}
2166 end
2167 end
2168
2169 return text, nextUpdate, scale, color
2170 end
2171
2172 --
2173 -- handles the cooldown OnUpdate event
2174 --
2175 local function Cooldown_OnUpdate(self, elapsed)
2176 if self.text:IsShown() then
2177 if self.nextUpdate > 0 then
2178 self.nextUpdate = self.nextUpdate - elapsed
2179 else
2180 local fScale, uiScale = self:GetEffectiveScale(), UIParent:GetEffectiveScale()
2181
2182 if (fScale/uiScale) < 0.3 then
2183 self.text:SetText('')
2184 self.nextUpdate = 1
2185 else
2186 local remain = self.duration - (GetTime() - self.start)
2187 local parent = self:GetParent()
2188 if floor(remain+0.5) > 0 then
2189 local text, nextUpdate, scale, color = Cooldown_Format(remain)
2190 self.text:SetText(text)
2191 if not LBF then
2192 self:SetScale(scale)
2193 elseif parent:GetName() and parent:GetName():find('KTrackerGroup') then
2194 self.text:SetFont(font, scale*16, 'OUTLINE')
2195 end
2196 self.text:SetTextColor(unpack(color))
2197 self.nextUpdate = nextUpdate
2198 else
2199 self.text:Hide()
2200 end
2201 end
2202 end
2203 end
2204 end
2205
2206 --
2207 -- used to created the timer for the given icon/button
2208 --
2209 local function Cooldown_CreateTimer(self)
2210 local scale = min(self:GetParent():GetWidth()/36, 1)
2211 if scale < 0.3 then
2212 self.hideCC = true
2213 else
2214 local text = self:CreateFontString(nil, 'OVERLAY')
2215 text:SetPoint('CENTER', 0, 0)
2216 text:SetJustifyH('CENTER')
2217 text:SetJustifyV('MIDDLE')
2218 text:SetFont(font, scale*19, 'OUTLINE')
2219
2220 self.text = text
2221 self:SetScript('OnUpdate', Cooldown_OnUpdate)
2222 return text
2223 end
2224 end
2225
2226 --
2227 -- starts the cooldown timer for the given icon/button
2228 --
2229 local function Cooldown_StartTimer(self, start, duration)
2230 self.start = start
2231 self.duration = duration
2232 self.nextUpdate = 0
2233
2234 local text = self.text or (not self.hideCC and Cooldown_CreateTimer(self))
2235 if text then text:Show() end
2236 end
2237
2238 --
2239 -- this function is hooked to all actions bars including
2240 -- all icons created by our addon.
2241 --
2242 function Cooldown_OnSetCooldown(self, start, duration)
2243 if start > 0 and duration > 3 then
2244 Cooldown_StartTimer(self, start, duration)
2245 else
2246 local text = self.text
2247 if text then text:Hide() end
2248 end
2249 end
2250 end
2251
2252 do
2253 -- a flag so we don't hook twice
2254 local hooked = false
2255
2256 --
2257 -- called to check if the player is already using a cooldown
2258 -- count addon or not. If he is not/she is not, we set our own
2259 --
2260 function addon:Initialize(all)
2261 if not hooked and (not OmniCC and not CooldownCount) then
2262 local methods = getmetatable(_G['ActionButton1Cooldown']).__index
2263 hooksecurefunc(methods, 'SetCooldown', Cooldown_OnSetCooldown)
2264 hooked = true
2265 end
2266 if not all then self:Load() end
2267 end
2268 end
2269end
2270
2271--------------------------------------------------------------------------
2272-- minimap button
2273
2274do
2275 local menu, dragMode
2276 local abs, sqrt = _G.math.abs, _G.math.sqrt
2277
2278 --
2279 -- handles the minimap button moving
2280 --
2281 local function MoveButton(self)
2282 local centerX, centerY = Minimap:GetCenter()
2283 local x, y = GetCursorPosition()
2284 x, y = x / self:GetEffectiveScale() - centerX, y / self:GetEffectiveScale() - centerY
2285
2286 if dragMode == "free" then
2287 self:ClearAllPoints()
2288 self:SetPoint("CENTER", x, y)
2289 else
2290 centerX, centerY = abs(x), abs(y)
2291 centerX, centerY = (centerX / sqrt(centerX^2 + centerY^2)) * 80, (centerY / sqrt(centerX^2 + centerY^2)) * 80
2292 centerX = x < 0 and -centerX or centerX
2293 centerY = y < 0 and -centerY or centerY
2294 self:ClearAllPoints()
2295 self:SetPoint("CENTER", centerX, centerY)
2296 end
2297 end
2298
2299 --
2300 -- button OnMouseDown
2301 --
2302 local function Minimap_OnMouseDown(self, button)
2303 if IsAltKeyDown() then
2304 dragMode = 'free'
2305 self:SetScript('OnUpdate', MoveButton)
2306 elseif IsShiftKeyDown() then
2307 dragMode = nil
2308 self:SetScript('OnUpdate', MoveButton)
2309 end
2310 end
2311
2312 --
2313 -- button OnMouseUp
2314 --
2315 local function Minimap_OnMouseUp(self, button)
2316 self:SetScript('OnUpdate', nil)
2317 end
2318
2319 --
2320 -- button OnClick
2321 --
2322 local function Minimap_OnClick(self, button)
2323 if IsShiftKeyDown() or IsAltKeyDown() then
2324 return
2325 elseif button == 'RightButton' then
2326 addon:Config()
2327 elseif button == 'LeftButton' then
2328 addon:Toggle()
2329 end
2330 end
2331
2332 --
2333 -- called OnLoad the minimap button
2334 --
2335 function addon:OnLoadMinimap(btn)
2336 if not btn then return end
2337 minimapButton = btn
2338 minimapButton:RegisterForClicks('LeftButtonUp', 'RightButtonUp')
2339 minimapButton:SetUserPlaced(true)
2340 minimapButton:SetScript('OnMouseDown', Minimap_OnMouseDown)
2341 minimapButton:SetScript('OnMouseUp', Minimap_OnMouseUp)
2342 minimapButton:SetScript('OnClick', Minimap_OnClick)
2343 utils.setTooltip(minimapButton, {
2344 L['|cffffd700Left-Click|r to lock/unlock.'],
2345 L['|cffffd700Right-Click|r to access settings.'],
2346 L['|cffffd700Shift+Click|r to move.'],
2347 L['|cffffd700Alt+Click|r for free drag and drop.']
2348 }, nil, titleString)
2349 end
2350
2351 --
2352 -- toggles Minimap button visibility
2353 --
2354 function addon:ToggleMinimapButton()
2355 KTrackerCharDB.minimap = not KTrackerCharDB.minimap
2356 self.minimap = KTrackerCharDB.minimap
2357 utils.showHide(minimapButton, self.minimap)
2358 end
2359
2360 --
2361 -- hide the minimap button
2362 --
2363 function addon:HideMinimapButton()
2364 addon:Print(type(minimapButton))
2365 utils.hide(minimapButton)
2366 end
2367end
2368
2369--------------------------------------------------------------------------
2370-- Config Frame
2371
2372do
2373 --
2374 -- config table, ui frame and frame name
2375 --
2376 local Config = {}
2377 local UIFrame, frameName
2378
2379 --
2380 -- required locals
2381 --
2382 local LocalizeUIFrame, localized
2383 local updateInterval, UpdateUIFrame = 0.05
2384 local FetchGroups, SaveGroup, fetched
2385 local FillFields, ResetFields
2386
2387 --
2388 -- objects used to hold Temporary data or default data
2389 --
2390 local tempObj, defObj = {}, {
2391 id = '',
2392 enabled = true,
2393 name = '',
2394 spec = 0,
2395 columns = 4,
2396 rows = 1,
2397 spacing = 0,
2398 combat = false
2399 }
2400
2401 --
2402 -- useful flags to update the frame
2403 --
2404 local isEdit, currentId, validId
2405
2406 --
2407 -- frame input fields
2408 --
2409 local newID, newName, newSpec, newCols, newRows
2410 local newSpacing, newSpacingBox
2411 local newEnabled, newCombat
2412
2413 --
2414 -- frame buttons
2415 --
2416 local btnSave, btnCancel
2417 local btnReset, btnLock, btnSync, btnMinimap
2418 local btnColumnsLeft, btnColumnsRight
2419 local btnRowsLeft, btnRowsRight
2420
2421 --
2422 -- used to fill frame fields when the user wants
2423 -- to modify an existing group of icons
2424 --
2425 do
2426 local function GroupsUnlockhighlight(except)
2427 for i, _ in ipairs(groups) do
2428 if except and except ==i then
2429 _G[frameName..'GroupBtn'..i]:LockHighlight()
2430 else
2431 _G[frameName..'GroupBtn'..i]:UnlockHighlight()
2432 end
2433 end
2434 end
2435
2436 function FillFields(obj)
2437 if obj then
2438 ResetFields()
2439
2440 GroupsUnlockhighlight(newID:GetNumber())
2441
2442 newName:SetText(obj.name)
2443 newCols:SetText(obj.columns)
2444 newRows:SetText(obj.rows)
2445 newSpacing:SetValue(obj.spacing)
2446 newEnabled:SetChecked(obj.enabled)
2447 newCombat:SetChecked(obj.combat)
2448
2449 -- spec
2450 if obj.spec ==1 then
2451 UIDropDownMenu_SetText(newSpec, L['Primary'])
2452 elseif obj.spec ==2 then
2453 UIDropDownMenu_SetText(newSpec, L['Secondary'])
2454 else
2455 UIDropDownMenu_SetText(newSpec, L['Both'])
2456 end
2457 isEdit = true
2458 end
2459 end
2460
2461 --
2462 -- called after saving fields to reset them
2463 --
2464 function ResetFields()
2465 tempObj = utils.deepCopy(defObj)
2466 newName:SetText('')
2467 newName:ClearFocus()
2468 UIDropDownMenu_SetText(newSpec, L['Both'])
2469 newCols:SetText(tempObj.columns)
2470 newRows:SetText(tempObj.rows)
2471 newSpacingBox:SetNumber(0)
2472 newSpacingBox:ClearFocus()
2473 newSpacing:SetValue(tempObj.spacing)
2474 newEnabled:SetChecked(tempObj.enabled)
2475 newCombat:SetChecked(tempObj.combat)
2476 GroupsUnlockhighlight()
2477 end
2478 end
2479
2480 --
2481 -- handles the edit/save button actions
2482 --
2483 function SaveGroup()
2484 -- name is required though...
2485 if tempObj.name == '' then
2486 addon:PrintError(L['The group name is required'])
2487 return
2488 end
2489
2490 -- we'll see if maxGroups should be changed or not
2491 if numGroups >= def.maxGroups then
2492 addon:PrintError(L['You have reached the maximum allowed groups number'])
2493 ResetFields()
2494 return
2495 end
2496
2497 Group:Save(tempObj, tempObj.id)
2498
2499 -- we reset fields, reload the addon then the list
2500 ResetFields()
2501 addon:Initialize()
2502 fetched, isEdit = false, false
2503 newID:SetNumber(0)
2504 end
2505
2506 do
2507 --
2508 -- called whenever the groups list requires update
2509 --
2510 local function ResetList()
2511 local index = 1
2512 local btn = _G[frameName..'GroupBtn'..index]
2513 while btn ~= nil do
2514 btn:Hide()
2515 index = index + 1
2516 btn = _G[frameName..'GroupBtn'..index]
2517 end
2518 end
2519
2520 do
2521
2522 local Group_OpenMenu, menu
2523 local current = 0
2524 do
2525
2526 local function Group_OptionToggle()
2527 local DB = KTrackerDB.groups[current]
2528 if DB then
2529 local db = KTrackerCharDB.groups[DB.id]
2530 if db then
2531 db[this.value] = this.checked
2532 addon:Initialize()
2533 fetched, isEdit = false, false
2534 return
2535 end
2536 end
2537 CloseDropDownMenus()
2538 end
2539
2540 local function Group_OptionChoose(self, arg1)
2541 local DB = KTrackerDB.groups[current]
2542 if DB then
2543 local db = KTrackerCharDB.groups[DB.id]
2544 local success = false
2545 if db[this.value] ~= nil then
2546 db[this.value] = arg1
2547 if this.value == 'position' then
2548 db.scale = def.CharDBGroup.scale
2549 local f = _G['KTrackerGroup'..current]
2550 f:ClearAllPoints()
2551 f:SetPoint('CENTER', 'UIParent', 'CENTER', 0, 0)
2552 end
2553 success = true
2554 end
2555
2556 if success then
2557 addon:Initialize()
2558 fetched, isEdit = false, false
2559 return
2560 end
2561 end
2562 CloseDropDownMenus()
2563 end
2564
2565 function Group_OpenMenu(self)
2566 local id = self:GetID()
2567 local DB = KTrackerDB.groups[id]
2568 if not DB then return end
2569 local db = KTrackerCharDB.groups[DB.id]
2570 if not db then return end
2571 current = id
2572
2573 if not menu then
2574 menu = CreateFrame('Frame', 'KTrackerGroupMenu')
2575 end
2576
2577 menu.displayMode = 'MENU'
2578 menu.initialize = function(self, level)
2579 local info = UIDropDownMenu_CreateInfo()
2580 level = level or 1
2581
2582 if level ==2 then
2583
2584 if UIDROPDOWNMENU_MENU_VALUE == 'spec' then
2585
2586 info.text = L['Both']
2587 info.checked = (db.spec ==0)
2588 info.value = 'spec'
2589 info.arg1 = 0
2590 info.func = Group_OptionChoose
2591 UIDropDownMenu_AddButton(info, level)
2592 wipe(info)
2593
2594 info.text = L['Primary']
2595 info.checked = (db.spec ==1)
2596 info.value = 'spec'
2597 info.arg1 = 1
2598 info.func = Group_OptionChoose
2599 UIDropDownMenu_AddButton(info, level)
2600 wipe(info)
2601
2602 info.text = L['Secondary']
2603 info.checked = (db.spec ==2)
2604 info.value = 'spec'
2605 info.arg1 = 2
2606 info.func = Group_OptionChoose
2607 UIDropDownMenu_AddButton(info, level)
2608 wipe(info)
2609
2610 elseif UIDROPDOWNMENU_MENU_VALUE == 'reset' then
2611
2612 info.text = L['Icons']
2613 info.func = function()
2614 StaticPopup_Show('KTRACKER_DIALOG_CLEAR', DB.name, nil, id)
2615 end
2616 UIDropDownMenu_AddButton(info, level)
2617 wipe(info)
2618
2619 info.text = L['Position']
2620 info.value = 'position'
2621 info.arg1 = {}
2622 info.func = Group_OptionChoose
2623 UIDropDownMenu_AddButton(info, level)
2624 wipe(info)
2625
2626 end
2627 return
2628 end
2629
2630 info.text = DB.name
2631 info.isTitle = true
2632 UIDropDownMenu_AddButton(info, level)
2633 wipe(info)
2634
2635 info.text = L['Edit']
2636 info.func = function()
2637 local obj = utils.deepCopy(DB)
2638 utils.mixTable(obj, db)
2639 newID:SetNumber(id)
2640 FillFields(obj)
2641 end
2642 UIDropDownMenu_AddButton(info, level)
2643 wipe(info)
2644
2645 info.text = DELETE
2646 info.func = function()
2647 StaticPopup_Show('KTRACKER_DIALOG_DELETE', DB.name, nil, id)
2648 end
2649 UIDropDownMenu_AddButton(info, level)
2650 wipe(info)
2651
2652 info.text = L['Duplicate']
2653 info.func = function()
2654 local newGroup = utils.deepCopy(DB)
2655 utils.fillTable(newGroup, db)
2656 newGroup.id = nil
2657 Group:Save(newGroup)
2658 addon:Initialize()
2659 fetched, isEdit = false, false
2660 end
2661 UIDropDownMenu_AddButton(info, level)
2662 wipe(info)
2663
2664 if addon.sync then
2665 info.text = L['Share']
2666 info.func = function()
2667 StaticPopup_Show('KTRACKER_DIALOG_SHARE_SEND', nil, nil, id)
2668 end
2669 UIDropDownMenu_AddButton(info, level)
2670 wipe(info)
2671 end
2672
2673 info.disabled = true
2674 UIDropDownMenu_AddButton(info, level)
2675 wipe(info)
2676
2677 info.text = L['Group Status']
2678 info.isTitle = true
2679 UIDropDownMenu_AddButton(info, level)
2680 wipe(info)
2681
2682 info.text = L['Enabled']
2683 info.checked = db.enabled
2684 info.value = 'enabled'
2685 info.func = Group_OptionToggle
2686 info.keepShownOnClick = true
2687 UIDropDownMenu_AddButton(info, level)
2688 wipe(info)
2689
2690 info.text = L['In Combat']
2691 info.checked = db.combat
2692 info.value = 'combat'
2693 info.func = Group_OptionToggle
2694 info.keepShownOnClick = true
2695 UIDropDownMenu_AddButton(info, level)
2696 wipe(info)
2697
2698 info.text = L['Talents Spec']
2699 info.value = 'spec'
2700 info.hasArrow = true
2701 info.keepShownOnClick = true
2702 UIDropDownMenu_AddButton(info, level)
2703 wipe(info)
2704
2705 info.text = RESET
2706 info.value = 'reset'
2707 info.hasArrow = true
2708 UIDropDownMenu_AddButton(info, level)
2709 wipe(info)
2710 end
2711
2712 ToggleDropDownMenu(1, nil, menu, 'cursor', 0, 0)
2713 end
2714 end
2715
2716
2717 local function Group_OnClick(self, button)
2718 if button == 'RightButton' then
2719 Group_OpenMenu(self)
2720 elseif button == 'MiddleButton' then
2721 local id = self:GetID()
2722 local DB = KTrackerDB.groups[id]
2723 if not DB then return end
2724 local db = KTrackerCharDB.groups[DB.id]
2725 if db then
2726 db.enabled = not db.enabled
2727 addon:Initialize()
2728 fetched, isEdit = false, false
2729 end
2730 end
2731 end
2732
2733 --
2734 -- fetches all groups and put then on the list
2735 --
2736 function FetchGroups()
2737 if not groups or fetched then return end
2738 ResetList()
2739
2740 _G[frameName..'CountLabel']:SetText(L:F('Groups: %d/%d', numGroups, def.maxGroups))
2741
2742 local scrollFrame = _G[frameName..'ScrollFrame']
2743 local scrollChild = _G[frameName..'ScrollFrameScrollChild']
2744 scrollChild:SetHeight(scrollFrame:GetHeight())
2745 scrollChild:SetWidth(scrollFrame:GetWidth())
2746
2747 local height = 0
2748 local num = #groups
2749
2750 for i = num, 1, -1 do
2751 local group = groups[i]
2752 local btnName = frameName..'GroupBtn'..i
2753 local btn = _G[btnName] or CreateFrame('Button', btnName, scrollChild, 'KTrackerGroupButtonTemplate')
2754 btn:Show()
2755 btn:SetID(i)
2756 btn:SetPoint('TOPLEFT', scrollChild, 'TOPLEFT', 0, -height)
2757 btn:SetPoint('RIGHT', scrollChild, 'RIGHT', 0, 0)
2758 height = height + btn:GetHeight()
2759
2760 -- Set data
2761 _G[btnName..'ID']:SetText(group.id)
2762 _G[btnName..'Name']:SetText(group.name)
2763 _G[btnName..'Size']:SetText(group.columns..'x'..group.rows)
2764
2765 local spec = L['Both']
2766 if group.spec > 0 then
2767 spec = (group.spec ==1) and L['Primary'] or L['Secondary']
2768 end
2769 _G[btnName..'Spec']:SetText(spec)
2770
2771 utils.setText(_G[btnName..'Combat'], YES, NO, group.combat)
2772 utils.setText(_G[btnName..'Enabled'], YES, NO, group.enabled)
2773
2774 btn:SetScript('OnClick', Group_OnClick)
2775 end
2776
2777 fetched = true
2778 end
2779 end
2780 end
2781
2782 do
2783
2784 --
2785 -- simple dummy function that sets text of the spec menu
2786 --
2787 local function ChooseSpec()
2788 local specs = {L['Both'], L['Primary'], L['Secondary']}
2789 UIDropDownMenu_SetText(newSpec, specs[this.value+1])
2790 end
2791
2792 --
2793 -- initializes the spec dropdown menu
2794 --
2795 local function InitializeSpecDropdown(self, level)
2796 UIDropDownMenu_JustifyText(self, 'LEFT')
2797 if level == 1 then
2798 local info = UIDropDownMenu_CreateInfo()
2799
2800 -- both specs
2801 info.text = L['Both']
2802 info.value = 0
2803 info.func = ChooseSpec
2804 info.notCheckable = true
2805 UIDropDownMenu_AddButton(info)
2806 wipe(info)
2807
2808 -- primary
2809 info.text = L['Primary']
2810 info.value = 1
2811 info.func = ChooseSpec
2812 info.notCheckable = true
2813 UIDropDownMenu_AddButton(info)
2814 wipe(info)
2815
2816 -- Secondary
2817 info.text = L['Secondary']
2818 info.value = 2
2819 info.func = ChooseSpec
2820 info.notCheckable = true
2821 UIDropDownMenu_AddButton(info)
2822 wipe(info)
2823
2824 return
2825 end
2826 end
2827
2828 do
2829
2830 local function ColumnsLeft_OnClick(self, button)
2831 tempObj.columns = tempObj.columns-1
2832 if tempObj.columns <= 0 then
2833 tempObj.columns = 1
2834 end
2835 newCols:SetText(tempObj.columns)
2836 end
2837
2838 local function ColumnsRight_OnClick(self, button)
2839 tempObj.columns = tempObj.columns+1
2840 if tempObj.columns >= def.maxColumns then
2841 tempObj.columns = def.maxColumns
2842 end
2843 newCols:SetText(tempObj.columns)
2844 end
2845
2846 local function RowsLeft_OnClick(self, button)
2847 tempObj.rows = tempObj.rows-1
2848 if tempObj.rows <= 0 then
2849 tempObj.rows = 1
2850 end
2851 newRows:SetText(tempObj.rows)
2852 end
2853
2854 local function RowsRight_OnClick(self, button)
2855 tempObj.rows = tempObj.rows+1
2856 if tempObj.rows >= def.maxRows then
2857 tempObj.rows = def.maxRows
2858 end
2859 newRows:SetText(tempObj.rows)
2860 end
2861
2862 local function SpacingBox_OnEnterPressed(self)
2863 local value = self:GetNumber()
2864 if value >= 250 then
2865 value = 250
2866 end
2867 newSpacing:SetValue(value)
2868 self:ClearFocus()
2869 end
2870
2871 local function SyncBtn_OnClick(self)
2872 KTrackerDB.sync = (self:GetChecked() ==1)
2873 addon.sync = KTrackerDB.sync
2874 CloseDropDownMenus()
2875 end
2876
2877 local function MinimapBtn_OnClick(self)
2878 addon:ToggleMinimapButton()
2879 CloseDropDownMenus()
2880 end
2881
2882 --
2883 -- this function is called only once on loading.
2884 -- it simply localizes the frame and registers some
2885 -- required elements.
2886 --
2887 function LocalizeUIFrame()
2888 if localized then return end
2889
2890 -- hold frame form inputs
2891 newID = _G[frameName..'ID']
2892 newName = _G[frameName..'Name']
2893 newSpec = _G[frameName..'SpecDropDown']
2894 newCols = _G[frameName..'ColumnsText']
2895 newRows = _G[frameName..'RowsText']
2896 newSpacing = _G[frameName..'Spacing']
2897 newSpacingBox = _G[frameName..'SpacingValue']
2898 newEnabled = _G[frameName..'Enabled']
2899 newCombat = _G[frameName..'Combat']
2900
2901 UIDropDownMenu_Initialize(newSpec, InitializeSpecDropdown, nil, 1)
2902
2903 -- frame buttons
2904 btnSave = _G[frameName..'SaveBtn']
2905 btnCancel = _G[frameName..'CancelBtn']
2906 btnReset = _G[frameName..'ResetBtn']
2907 btnLock = _G[frameName..'LockBtn']
2908
2909 btnColumnsLeft = _G[frameName..'ColumnsLeft']
2910 btnColumnsRight = _G[frameName..'ColumnsRight']
2911 btnRowsLeft = _G[frameName..'RowsLeft']
2912 btnRowsRight = _G[frameName..'RowsRight']
2913
2914 if GetLocale() ~= 'enUS' and GetLocale() ~= 'enGB' then
2915 btnReset:SetText(L['Reset'])
2916 btnLock:SetText(L['Lock'])
2917 _G[frameName..'NameLabel']:SetText(L['Group Name'])
2918 _G[frameName..'SpecLabel']:SetText(L['Talents Spec'])
2919 _G[frameName..'ColumnsLabel']:SetText(L['Columns'])
2920 _G[frameName..'RowsLabel']:SetText(L['Rows'])
2921 _G[frameName..'SpacingLabel']:SetText(L['Spacing'])
2922 _G[frameName..'EnabledLabel']:SetText(L['Enable Group'])
2923 _G[frameName..'CombatLabel']:SetText(L['Only in Combat'])
2924 _G[frameName..'HeaderSize']:SetText(L['Size'])
2925 _G[frameName..'HeaderSpec']:SetText(L['Spec'])
2926 _G[frameName..'HeaderCombat']:SetText(L['In Combat'])
2927 _G[frameName..'HeaderEnabled']:SetText(L['Enabled'])
2928 end
2929
2930 _G[frameName..'Title']:SetText('|cfff58cbaKader|r|caaf49141Tracker|r - Made with love by |cffff7d0aEarwin|r from |cff40c7ebBlameTheMage|r guild on |cff996019Warmane|r-Icecrown')
2931
2932 -- prepare our temporary group object
2933
2934 tempObj = utils.deepCopy(defObj)
2935 newCols:SetText(tempObj.columns)
2936 newRows:SetText(tempObj.rows)
2937
2938 -- left and right columns buttons functions
2939 btnColumnsLeft:SetScript('OnClick', ColumnsLeft_OnClick)
2940 btnColumnsRight:SetScript('OnClick', ColumnsRight_OnClick)
2941 btnRowsLeft:SetScript('OnClick', RowsLeft_OnClick)
2942 btnRowsRight:SetScript('OnClick', RowsRight_OnClick)
2943
2944 -- name and spacing on enter processed
2945 newName:SetScript('OnEnterPressed', SaveGroup)
2946 newSpacingBox:SetScript('OnEnterPressed', SpacingBox_OnEnterPressed)
2947
2948 -- buttons scripts
2949 btnSave:SetScript('OnClick', SaveGroup)
2950 btnCancel:SetScript('OnClick', ResetFields)
2951 btnReset:SetScript('OnClick', function(self)
2952 StaticPopup_Show('KTRACKER_DIALOG_RESET')
2953 end)
2954
2955 -- Sync button
2956 local syncName = frameName..'SyncBtn'
2957 btnSync = _G[syncName]
2958 _G[syncName..'Text']:SetText(L['Enable Sync'])
2959 btnSync.tooltip = L['Check this you want to enable group sharing.']
2960 btnSync:SetScript('OnClick', SyncBtn_OnClick)
2961
2962 -- Minimap button
2963 local mmbName = frameName..'MinimapBtn'
2964 btnMinimap = _G[mmbName]
2965 _G[mmbName..'Text']:SetText(L['Minimap Button'])
2966 btnMinimap.tooltip = L['Check this you want show the minimap button.']
2967 btnMinimap:SetScript('OnClick', MinimapBtn_OnClick)
2968
2969 localized = true
2970 end
2971 end
2972 end
2973
2974 --
2975 -- handles the config frame OnUpdate event
2976 --
2977 function UpdateUIFrame(self, elapsed)
2978 if utils.update(self, self:GetName(), updateInterval, elapsed) then
2979
2980 FetchGroups()
2981 btnSync:SetChecked(addon.sync)
2982 btnMinimap:SetChecked(addon.minimap)
2983
2984 utils.setText(btnLock, L['Unlock'], L['Lock'], addon.locked)
2985
2986 -- group id
2987 local id = newID:GetNumber()
2988 tempObj.id = (id > 0) and id or nil
2989
2990 -- disable few things
2991 tempObj.name = newName:GetText():trim()
2992 local hasObject = (tempObj.name ~= '')
2993 utils.enableDisable(btnSave, hasObject)
2994 utils.enableDisable(btnCancel, hasObject)
2995 utils.showHide(newSpacingBox, hasObject)
2996 if hasObject then
2997 newSpacing:Enable()
2998 else
2999 newSpacing:Disable()
3000 end
3001
3002 -- spec
3003 if tempObj.name == '' then
3004 UIDropDownMenu_DisableDropDown(newSpec)
3005 else
3006 UIDropDownMenu_EnableDropDown(newSpec)
3007 end
3008
3009 local spec = UIDropDownMenu_GetText(newSpec)
3010 if spec ==L['Primary'] then
3011 tempObj.spec = 1
3012 elseif spec ==L['Secondary'] then
3013 tempObj.spec = 2
3014 else
3015 UIDropDownMenu_SetText(newSpec, L['Both'])
3016 tempObj.spec = 0
3017 end
3018
3019 tempObj.columns = tonumber(newCols:GetText())
3020 tempObj.rows = tonumber(newRows:GetText())
3021 tempObj.spacing = newSpacing:GetValue()
3022
3023 if not newSpacingBox:HasFocus() then
3024 newSpacingBox:SetText(tempObj.spacing)
3025 end
3026
3027
3028 tempObj.hasObject = (newEnabled:GetChecked() ==1)
3029 tempObj.combat = (newCombat:GetChecked() ==1)
3030 utils.enableDisable(newEnabled, hasObject)
3031 utils.enableDisable(newCombat, hasObject)
3032
3033 -- columns and rows
3034 newCols:SetText(tempObj.columns)
3035 newRows:SetText(tempObj.rows)
3036 utils.enableDisable(btnColumnsLeft, (hasObject and tempObj.columns > 1))
3037 utils.enableDisable(btnColumnsRight, (hasObject and tempObj.columns < def.maxColumns))
3038 utils.enableDisable(btnRowsLeft, (hasObject and tempObj.rows > 1))
3039 utils.enableDisable(btnRowsRight, (hasObject and tempObj.rows < def.maxRows))
3040 end
3041 end
3042
3043 --
3044 -- called when the configuration frame loads the first time
3045 --
3046 function addon:ConfigOnLoad(frame)
3047 if not frame then return end
3048 UIFrame = frame
3049 frameName = frame:GetName()
3050 frame:RegisterForDrag('LeftButton')
3051 LocalizeUIFrame()
3052 frame:SetScript('OnUpdate', UpdateUIFrame)
3053 frame:SetScript('OnHide', ResetFields)
3054 utils.hide(frame)
3055 tinsert(UISpecialFrames, frameName)
3056 end
3057
3058 --
3059 -- toggles the configuration frame
3060 --
3061 function addon:Config()
3062 utils.toggle(UIFrame)
3063 end
3064
3065 --
3066 -- groups sorting
3067 --
3068 do
3069 --
3070 -- flag to order groups ascending or not
3071 --
3072 local ascending = false
3073
3074 --
3075 -- groups sortTypes
3076 --
3077 local sortTypes
3078 do
3079 local function SortBy_Name(a, b)
3080 return ascending and (a.name < b.name) or (a.name > b.name)
3081 end
3082
3083 local function SortBy_Size(a, b)
3084 local sizeA = a.columns*a.rows
3085 local sizeB = b.columns*b.rows
3086 return ascending and (sizeA < sizeB) or (sizeA > sizeB)
3087 end
3088
3089 local function SortBy_Spec(a, b)
3090 return ascending and (a.spec < b.spec) or (a.spec > b.spec)
3091 end
3092
3093 local function SortBy_Combat(a, b)
3094 local aON = a.combat and 1 or 0
3095 local bON = b.combat and 1 or 0
3096 return ascending and (aON < bON) or (aON > bON)
3097 end
3098
3099 local function SortBy_Enabled(a, b)
3100 local aON = a.enabled and 1 or 0
3101 local bON = b.enabled and 1 or 0
3102 return ascending and (aON < bON) or (aON > bON)
3103 end
3104
3105 --
3106 -- add functions to sort types table
3107 --
3108 sortTypes = {
3109 name = SortBy_Name,
3110 size = SortBy_Size,
3111 spec = SortBy_Spec,
3112 combat = SortBy_Combat,
3113 enable = SortBy_Enabled
3114 }
3115 end
3116
3117 --
3118 -- handles groups sorting
3119 --
3120 function addon:SortGroups(t)
3121 if not t or not sortTypes[t] then return end
3122 ascending = not ascending
3123 tsort(groups, sortTypes[t])
3124 fetched = false
3125 end
3126 end
3127
3128 --
3129 -- delete a group pop up dialog
3130 --
3131 StaticPopupDialogs['KTRACKER_DIALOG_DELETE'] = {
3132 text = L['|caaf49141%s|r : Are you sure you want to delete this group?'],
3133 button1 = DELETE,
3134 button2 = CANCEL,
3135 timeout = 0,
3136 whileDead = true,
3137 hideOnEscape = true,
3138 OnShow = function(self, id)
3139 utils.highlight(_G[frameName..'GroupBtn'..id], true)
3140 end,
3141 OnHide = function(self, id)
3142 utils.highlight(_G[frameName..'GroupBtn'..id], false)
3143 end,
3144 OnAccept = function(self, id)
3145 if id and KTrackerDB.groups[id] then
3146 local DB = KTrackerDB.groups[id]
3147 if DB then
3148 _G[DB.id]:Hide()
3149 tremove(KTrackerDB.groups, id)
3150 KTrackerCharDB.groups[DB.id] = nil
3151 fetched, isEdit = false, false
3152 addon:Initialize()
3153 end
3154 end
3155 self:Hide()
3156 end
3157 }
3158
3159 --
3160 -- clear all group icons pop up dialog
3161 --
3162 StaticPopupDialogs['KTRACKER_DIALOG_CLEAR'] = {
3163 text = L['|caaf49141%s|r : Are you sure you want to clear all icons?'],
3164 button1 = YES,
3165 button2 = CANCEL,
3166 timeout = 0,
3167 whileDead = true,
3168 hideOnEscape = true,
3169 OnShow = function(self, id)
3170 utils.highlight(_G[frameName..'GroupBtn'..id], true)
3171 end,
3172 OnHide = function(self, id)
3173 utils.highlight(_G[frameName..'GroupBtn'..id], false)
3174 end,
3175 OnAccept = function(self, id)
3176 if id and KTrackerDB.groups[id] then
3177 CloseDropDownMenus()
3178 wipe(KTrackerDB.groups[id].icons)
3179 addon:Initialize()
3180 fetched, isEdit = false, false
3181 end
3182 self:Hide()
3183 end
3184 }
3185
3186 --
3187 -- reset all addon to default
3188 --
3189 StaticPopupDialogs['KTRACKER_DIALOG_RESET'] = {
3190 text = L['You are about to reset everything, all groups, icons and their settings will be deleted. \nDo you want to continue?'],
3191 button1 = YES,
3192 button2 = CANCEL,
3193 timeout = 0,
3194 whileDead = true,
3195 hideOnEscape = true,
3196 OnAccept = function(self)
3197 -- wipe tables?
3198 wipe(KTrackerDB)
3199 wipe(KTrackerCharDB)
3200 wipe(groups)
3201
3202 -- hide groups
3203 for i = 1, def.maxGroups do
3204 utils.hide(_G['KTrackerGroup'..i])
3205 end
3206 LoadDatabase()
3207 addon:Initialize()
3208 fetched, isEdit = false, false
3209 self:Hide()
3210 end
3211 }
3212
3213 --
3214 -- synchronization handler
3215 --
3216 do
3217 --
3218 -- holds messages per character
3219 --
3220 local data = {}
3221
3222 --
3223 -- send new group dialog
3224 --
3225 local function ProcessShare(id, channel, target)
3226 local DB = KTrackerDB.groups[id]
3227 if not DB then return end
3228
3229 local str = 'GroupStart\t'..DB.id..'\t'..DB.name..'\t'..DB.columns..'\t'..DB.rows
3230 addon:Sync(str, channel, target)
3231
3232 local num = DB.columns*DB.rows
3233 for i = 1, num do
3234 local icon = DB.icons[i]
3235 local msg = 'Icon\t'..DB.id..'\t'..icon.name..'\t'..tostring(icon.enabled)
3236 msg = msg..'\t'..icon.type..'\t'..icon.subtype..'\t'..tostring(icon.when)
3237 msg = msg..'\t'..icon.unit..'\t'..tostring(icon.mine)..'\t'..tostring(icon.timer)
3238 msg = msg..'\t'..icon.effect
3239 addon:Sync(msg, channel, target)
3240
3241 -- final icon? Tell to stop
3242 if i ==num then
3243 addon:Sync('GroupEnd\t'..DB.id..'\t'..DB.name, channel, target)
3244 end
3245 end
3246 end
3247
3248
3249 --
3250 -- handles all sync actions
3251 --
3252 local function SyncHandler(msg, channel, sender)
3253 if not msg then return end
3254
3255 local command, other = strsplit('\t', msg, 2)
3256
3257 -- sending share request
3258 if command == 'GroupShare' and other:find('KTrackerGroup') ~= nil then
3259 local str = sender..'\t'..other
3260 StaticPopup_Show('KTRACKER_DIALOG_SHARE_RECEIVE', sender, nil, str)
3261 return
3262 end
3263
3264 -- accepting the group
3265 if command == 'GroupAccept' and other:find('KTrackerGroup') ~= nil then
3266
3267 local id
3268 for i, obj in ipairs(KTrackerDB.groups) do
3269 if obj.id ==other then
3270 id = i
3271 break
3272 end
3273 end
3274
3275 if id then ProcessShare(id, 'WHISPER', sender) end
3276 return
3277 end
3278
3279 -- receiving a new group:
3280 if command == 'GroupStart' then
3281 local id, name, columns, rows = strsplit('\t', other, 4)
3282 columns, rows = tonumber(columns), tonumber(rows)
3283
3284 data = data or {}
3285 data[sender] = data[sender] or {}
3286 data[sender][id] = data[sender][id] or {}
3287
3288 local group = utils.deepCopy(def.DBGroup)
3289 group.id = nil
3290 group.name = name
3291 group.enabled = true
3292 group.columns = columns
3293 group.rows = rows
3294
3295 data[sender][id] = group
3296 return
3297 end
3298
3299 -- receiving group icons
3300 if command == 'Icon' then
3301 -- we make sure the player started sending.
3302 if not data[sender] then return end
3303
3304 local id, name, enabled, iconType, subtype, when, unit, mine, timer, effect = strsplit('\t', other, 10)
3305
3306 if not data[sender][id] then return end
3307
3308
3309 local icon = utils.deepCopy(def.icon)
3310 icon.enabled = (enabled == 'true')
3311 icon.name = name
3312 icon.type = iconType
3313 icon.subtype = subtype
3314 icon.when = tonumber(when)
3315 icon.unit = unit
3316 icon.mine = (mine == 'true')
3317 icon.timer = (timer == 'true')
3318 icon.effect = effect
3319
3320 tinsert(data[sender][id].icons, icon)
3321 return
3322 end
3323
3324 if command == 'GroupEnd' then
3325 local id, name = strsplit('\t', other, 2)
3326 if not id then return end
3327 if data and (not data[sender] or not data[sender][id]) then return end
3328
3329 Group:Save(data[sender][id])
3330 wipe(data[sender])
3331
3332 addon:PrintSuccess(L:F('"%s" group successfully imported.', name))
3333
3334 addon:Initialize()
3335 fetched, isEdit = false, false
3336 return
3337 end
3338 end
3339
3340 syncHandlers[syncPrefix] = SyncHandler
3341
3342 --
3343 -- prompt dialog to share group
3344 --
3345 StaticPopupDialogs['KTRACKER_DIALOG_SHARE_SEND'] = {
3346 text = L['Enter the name of the character to share the group with, or leave empty to share it with your party/raid members.'],
3347 button1 = SEND_LABEL,
3348 button2 = CANCEL,
3349 hasEditBox = true,
3350 timeout = 0,
3351 whileDead = true,
3352 hideOnEscape = true,
3353 OnShow = function(self, id)
3354 utils.highlight(_G[frameName..'GroupBtn'..id], true)
3355 _G[self:GetName()..'EditBox']:SetFocus()
3356 end,
3357 OnHide = function(self, id)
3358 utils.highlight(_G[frameName..'GroupBtn'..id], false)
3359 local box = _G[self:GetName()..'WideEditBox']
3360 box:SetText('')
3361 box:ClearFocus()
3362 end,
3363 OnAccept = function(self, id)
3364 local target = _G[self:GetName()..'EditBox']:GetText():trim()
3365 local channel = 'WHISPER'
3366 if target == '' then
3367 channel, target = nil, nil
3368 end
3369 local DB = KTrackerDB.groups[id]
3370 addon:Sync('GroupShare\t'..DB.id, channel, target)
3371 self:Hide()
3372 end,
3373 EditBoxOnEnterPressed = function(self, id)
3374 local target = _G[self:GetName()]:GetText():trim()
3375 local channel, data = 'WHISPER', ''
3376 if target == '' then
3377 channel, target = nil, nil
3378 end
3379 local DB = KTrackerDB.groups[id]
3380 addon:Sync('GroupShare\t'..DB.id, channel, target)
3381 self:GetParent():Hide()
3382 end,
3383 EditBoxOnEscapePressed = function(self)
3384 self:SetText('')
3385 self:ClearFocus()
3386 self:GetParent():Hide()
3387 end
3388 }
3389
3390 --
3391 -- prompt dialog to ask share permission
3392 --
3393 StaticPopupDialogs['KTRACKER_DIALOG_SHARE_RECEIVE'] = {
3394 text = L['%s wants to share a group of icons with you. Do you want to import it?'],
3395 button1 = ACCEPT,
3396 button2 = CANCEL,
3397 timeout = 0,
3398 whileDead = true,
3399 hideOnEscape = true,
3400 OnAccept = function(self, str)
3401 if str then
3402 local sender, id = strsplit('\t', str)
3403 if sender and id:find('KTrackerGroup') ~= nil then
3404 addon:Sync('GroupAccept\t'..id, 'WHISPER', sender)
3405 end
3406 end
3407 self:Hide()
3408 end
3409 }
3410 end
3411end