SecureGroupHeaders.lua

SecureGroupHeaders.lua (8.3.7.35662 vs 8.1.5.29701; click to toggle context display)
  1. --
  2. -- SecurePartyHeader and SecureRaidGroupHeader contributed with permission by: Esamynn, Cide, and Iriel
  3. --
  4. local strsplit = strsplit;
  5. local select = select;
  6. local tonumber = tonumber;
  7. local type = type;
  8. local floor = math.floor;
  9. local ceil = math.ceil;
  10. local min = math.min;
  11. local max = math.max;
  12. local abs = math.abs;
  13. local pairs = pairs;
  14. local ipairs = ipairs;
  15. local strtrim = string.trim;
  16. local unpack = unpack;
  17. local wipe = table.wipe;
  18. local tinsert = table.insert;
  19. local CallRestrictedClosure = CallRestrictedClosure;
  20. local GetManagedEnvironment = GetManagedEnvironment;
  21. local GetFrameHandle = GetFrameHandle;
  22. --[[
  23. List of the various configuration attributes
  24. ======================================================
  25. showRaid = [BOOLEAN] -- true if the header should be shown while in a raid
  26. showParty = [BOOLEAN] -- true if the header should be shown while in a party and not in a raid
  27. showPlayer = [BOOLEAN] -- true if the header should show the player when not in a raid
  28. showSolo = [BOOLEAN] -- true if the header should be shown while not in a group (implies showPlayer)
  29. nameList = [STRING] -- a comma separated list of player names (not used if 'groupFilter' is set)
  30. groupFilter = [1-8, STRING] -- a comma seperated list of raid group numbers and/or uppercase class names and/or uppercase roles
  31. roleFilter = [STRING] -- a comma seperated list of MT/MA/Tank/Healer/DPS role strings
  32. strictFiltering = [BOOLEAN]
  33. -- if true, then
  34. ---- if only groupFilter is specified then characters must match both a group and a class from the groupFilter list
  35. ---- if only roleFilter is specified then characters must match at least one of the specified roles
  36. ---- if both groupFilter and roleFilters are specified then characters must match a group and a class from the groupFilter list and a role from the roleFilter list
  37. point = [STRING] -- a valid XML anchoring point (Default: "TOP")
  38. xOffset = [NUMBER] -- the x-Offset to use when anchoring the unit buttons (Default: 0)
  39. yOffset = [NUMBER] -- the y-Offset to use when anchoring the unit buttons (Default: 0)
  40. sortMethod = ["INDEX", "NAME", "NAMELIST"] -- defines how the group is sorted (Default: "INDEX")
  41. sortDir = ["ASC", "DESC"] -- defines the sort order (Default: "ASC")
  42. template = [STRING] -- the XML template to use for the unit buttons
  43. templateType = [STRING] - specifies the frame type of the managed subframes (Default: "Button")
  44. groupBy = [nil, "GROUP", "CLASS", "ROLE", "ASSIGNEDROLE"] - specifies a "grouping" type to apply before regular sorting (Default: nil)
  45. groupingOrder = [STRING] - specifies the order of the groupings (ie. "1,2,3,4,5,6,7,8")
  46. maxColumns = [NUMBER] - maximum number of columns the header will create (Default: 1)
  47. unitsPerColumn = [NUMBER or nil] - maximum units that will be displayed in a singe column, nil is infinite (Default: nil)
  48. startingIndex = [NUMBER] - the index in the final sorted unit list at which to start displaying units (Default: 1)
  49. columnSpacing = [NUMBER] - the amount of space between the rows/columns (Default: 0)
  50. columnAnchorPoint = [STRING] - the anchor point of each new column (ie. use LEFT for the columns to grow to the right)
  51. --]]
  52. function SecureGroupHeader_OnLoad(self)
  53. self:RegisterEvent("GROUP_ROSTER_UPDATE");
  54. self:RegisterEvent("UNIT_NAME_UPDATE");
  55. end
  56. function SecureGroupHeader_OnEvent(self, event, ...)
  57. if ( (event == "GROUP_ROSTER_UPDATE" or event == "UNIT_NAME_UPDATE") and self:IsVisible() ) then
  58. SecureGroupHeader_Update(self);
  59. end
  60. end
  61. function SecureGroupHeader_OnAttributeChanged(self, name, value)
  62. if ( name == "_ignore" or self:GetAttribute("_ignore" ) ) then
  63. return
  64. end
  65. if ( self:IsVisible() ) then
  66. SecureGroupHeader_Update(self);
  67. end
  68. end
  69. -- relativePoint, xMultiplier, yMultiplier = getRelativePointAnchor( point )
  70. -- Given a point return the opposite point and which axes the point
  71. -- depends on.
  72. local function getRelativePointAnchor( point )
  73. point = point:upper();
  74. if (point == "TOP") then
  75. return "BOTTOM", 0, -1;
  76. elseif (point == "BOTTOM") then
  77. return "TOP", 0, 1;
  78. elseif (point == "LEFT") then
  79. return "RIGHT", 1, 0;
  80. elseif (point == "RIGHT") then
  81. return "LEFT", -1, 0;
  82. elseif (point == "TOPLEFT") then
  83. return "BOTTOMRIGHT", 1, -1;
  84. elseif (point == "TOPRIGHT") then
  85. return "BOTTOMLEFT", -1, -1;
  86. elseif (point == "BOTTOMLEFT") then
  87. return "TOPRIGHT", 1, 1;
  88. elseif (point == "BOTTOMRIGHT") then
  89. return "TOPLEFT", -1, 1;
  90. else
  91. return "CENTER", 0, 0;
  92. end
  93. end
  94. local function setAttributesWithoutResponse(self, ...)
  95. local oldIgnore = self:GetAttribute("_ignore");
  96. self:SetAttribute("_ignore", "attributeChanges");
  97. for i = 1, select('#', ...), 2 do
  98. self:SetAttribute(select(i, ...));
  99. end
  100. self:SetAttribute("_ignore", oldIgnore);
  101. end
  102. local function SetupUnitButtonConfiguration( header, newChild, defaultConfigFunction )
  103. local configCode = header:GetAttribute("initialConfigFunction") or defaultConfigFunction;
  104. if ( type(configCode) == "string" ) then
  105. local selfHandle = GetFrameHandle(newChild);
  106. if ( selfHandle ) then
  107. CallRestrictedClosure(newChild, "self", GetManagedEnvironment(header, true),
  108. selfHandle, configCode, selfHandle);
  109. end
  110. end
  111. local copyAttributes = header:GetAttribute("_initialAttributeNames");
  112. if ( type(copyAttributes) == "string" ) then
  113. for name in copyAttributes:gmatch("[^,]+") do
  114. newChild:SetAttribute(name, scrub(header:GetAttribute("_initialAttribute-" .. name)));
  115. end
  116. end
  117. end
  118. -- creates child frames and finished configuring them
  119. local function configureChildren(self, unitTable)
  120. local point = self:GetAttribute("point") or "TOP"; --default anchor point of "TOP"
  121. local relativePoint, xOffsetMult, yOffsetMult = getRelativePointAnchor(point);
  122. local xMultiplier, yMultiplier = abs(xOffsetMult), abs(yOffsetMult);
  123. local xOffset = self:GetAttribute("xOffset") or 0; --default of 0
  124. local yOffset = self:GetAttribute("yOffset") or 0; --default of 0
  125. local sortDir = self:GetAttribute("sortDir") or "ASC"; --sort ascending by default
  126. local columnSpacing = self:GetAttribute("columnSpacing") or 0;
  127. local startingIndex = self:GetAttribute("startingIndex") or 1;
  128. local unitCount = #unitTable;
  129. local numDisplayed = unitCount - (startingIndex - 1);
  130. local unitsPerColumn = self:GetAttribute("unitsPerColumn");
  131. local numColumns;
  132. if ( unitsPerColumn and numDisplayed > unitsPerColumn ) then
  133. numColumns = min( ceil(numDisplayed / unitsPerColumn), (self:GetAttribute("maxColumns") or 1) );
  134. else
  135. unitsPerColumn = numDisplayed;
  136. numColumns = 1;
  137. end
  138. local loopStart = startingIndex;
  139. local loopFinish = min((startingIndex - 1) + unitsPerColumn * numColumns, unitCount)
  140. local step = 1;
  141. numDisplayed = loopFinish - (loopStart - 1);
  142. if ( sortDir == "DESC" ) then
  143. loopStart = unitCount - (startingIndex - 1);
  144. loopFinish = loopStart - (numDisplayed - 1);
  145. step = -1;
  146. end
  147. -- ensure there are enough buttons
  148. local needButtons = max(1, numDisplayed);
  149. if not ( self:GetAttribute("child"..needButtons) ) then
  150. local buttonTemplate = self:GetAttribute("template");
  151. local templateType = self:GetAttribute("templateType") or "Button";
  152. local name = self:GetName();
  153. for i = 1, needButtons, 1 do
  154. local childAttr = "child" .. i;
  155. if not ( self:GetAttribute(childAttr) ) then
  156. local newButton = CreateFrame(templateType, name and (name.."UnitButton"..i), self, buttonTemplate);
  157. self[i] = newButton;
  158. SetupUnitButtonConfiguration(self, newButton);
  159. setAttributesWithoutResponse(self, childAttr, newButton, "frameref-"..childAttr, GetFrameHandle(newButton));
  160. end
  161. end
  162. end
  163. local columnAnchorPoint, columnRelPoint, colxMulti, colyMulti;
  164. if ( numColumns > 1 ) then
  165. columnAnchorPoint = self:GetAttribute("columnAnchorPoint");
  166. columnRelPoint, colxMulti, colyMulti = getRelativePointAnchor(columnAnchorPoint);
  167. end
  168. local buttonNum = 0;
  169. local columnNum = 1;
  170. local columnUnitCount = 0;
  171. local currentAnchor = self;
  172. for i = loopStart, loopFinish, step do
  173. buttonNum = buttonNum + 1;
  174. columnUnitCount = columnUnitCount + 1;
  175. if ( columnUnitCount > unitsPerColumn ) then
  176. columnUnitCount = 1;
  177. columnNum = columnNum + 1;
  178. end
  179. local unitButton = self:GetAttribute("child"..buttonNum);
  180. if ( buttonNum == 1 ) then
  181. unitButton:SetPoint(point, currentAnchor, point, 0, 0);
  182. if ( columnAnchorPoint ) then
  183. unitButton:SetPoint(columnAnchorPoint, currentAnchor, columnAnchorPoint, 0, 0);
  184. end
  185. elseif ( columnUnitCount == 1 ) then
  186. local columnAnchor = self:GetAttribute("child"..(buttonNum - unitsPerColumn));
  187. unitButton:SetPoint(columnAnchorPoint, columnAnchor, columnRelPoint, colxMulti * columnSpacing, colyMulti * columnSpacing);
  188. else
  189. unitButton:SetPoint(point, currentAnchor, relativePoint, xMultiplier * xOffset, yMultiplier * yOffset);
  190. end
  191. unitButton:SetAttribute("unit", unitTable[i]);
  192. local configCode = unitButton:GetAttribute("refreshUnitChange");
  193. if ( type(configCode) == "string" ) then
  194. local selfHandle = GetFrameHandle(unitButton);
  195. if ( selfHandle ) then
  196. CallRestrictedClosure(unitButton, "self",
  197. GetManagedEnvironment(unitButton, true),
  198. selfHandle, configCode, selfHandle);
  199. end
  200. end
  201. if not unitButton:GetAttribute("statehidden") then
  202. unitButton:Show();
  203. end
  204. currentAnchor = unitButton;
  205. end
  206. repeat
  207. buttonNum = buttonNum + 1;
  208. local unitButton = self:GetAttribute("child"..buttonNum);
  209. if ( unitButton ) then
  210. unitButton:Hide();
  211. unitButton:ClearAllPoints();
  212. unitButton:SetAttribute("unit", nil);
  213. end
  214. until not ( unitButton )
  215. local unitButton = self:GetAttribute("child1");
  216. local unitButtonWidth = unitButton:GetWidth();
  217. local unitButtonHeight = unitButton:GetHeight();
  218. if ( numDisplayed > 0 ) then
  219. local width = xMultiplier * (unitsPerColumn - 1) * unitButtonWidth + ( (unitsPerColumn - 1) * (xOffset * xOffsetMult) ) + unitButtonWidth;
  220. local height = yMultiplier * (unitsPerColumn - 1) * unitButtonHeight + ( (unitsPerColumn - 1) * (yOffset * yOffsetMult) ) + unitButtonHeight;
  221. if ( numColumns > 1 ) then
  222. width = width + ( (numColumns -1) * abs(colxMulti) * (width + columnSpacing) );
  223. height = height + ( (numColumns -1) * abs(colyMulti) * (height + columnSpacing) );
  224. end
  225. self:SetWidth(width);
  226. self:SetHeight(height);
  227. else
  228. local minWidth = self:GetAttribute("minWidth") or (yMultiplier * unitButtonWidth);
  229. local minHeight = self:GetAttribute("minHeight") or (xMultiplier * unitButtonHeight);
  230. self:SetWidth( max(minWidth, 0.1) );
  231. self:SetHeight( max(minHeight, 0.1) );
  232. end
  233. end
  234. local function GetGroupHeaderType(self)
  235. local kind, start, stop;
  236. local nRaid = GetNumGroupMembers();
  237. local nParty = GetNumSubgroupMembers();
  238. if ( IsInRaid() and self:GetAttribute("showRaid") ) then
  239. kind = "RAID";
  240. elseif ( IsInGroup() and self:GetAttribute("showParty") ) then
  241. kind = "PARTY";
  242. elseif ( self:GetAttribute("showSolo") ) then
  243. kind = "SOLO";
  244. end
  245. if ( kind ) then
  246. if ( kind == "RAID" ) then
  247. start = 1;
  248. stop = nRaid;
  249. else
  250. if ( kind == "SOLO" or self:GetAttribute("showPlayer") ) then
  251. start = 0;
  252. else
  253. start = 1;
  254. end
  255. stop = nParty;
  256. end
  257. end
  258. return kind, start, stop;
  259. end
  260. local function GetGroupRosterInfo(kind, index)
  261. local _, unit, name, subgroup, className, role, server, assignedRole;
  262. if ( kind == "RAID" ) then
  263. unit = "raid"..index;
  264. name, _, subgroup, _, _, className, _, _, _, role, _, assignedRole = GetRaidRosterInfo(index);
  265. else
  266. if ( index > 0 ) then
  267. unit = "party"..index;
  268. else
  269. unit = "player";
  270. end
  271. if ( UnitExists(unit) ) then
  272. name, server = UnitName(unit);
  273. if (server and server ~= "") then
  274. name = name.."-"..server
  275. end
  276. _, className = UnitClass(unit);
  277. if ( GetPartyAssignment("MAINTANK", unit) ) then
  278. role = "MAINTANK";
  279. elseif ( GetPartyAssignment("MAINASSIST", unit) ) then
  280. role = "MAINASSIST";
  281. end
  282. assignedRole = UnitGroupRolesAssigned(unit)
  283. end
  284. subgroup = 1;
  285. end
  286. return unit, name, subgroup, className, role, assignedRole;
  287. end
  288. -- empties tbl and assigns the value true to each key passed as part of ...
  289. local function fillTable( tbl, ... )
  290. for i = 1, select("#", ...), 1 do
  291. local key = select(i, ...);
  292. key = tonumber(key) or strtrim(key);
  293. tbl[key] = i;
  294. end
  295. end
  296. -- same as fillTable() except that each key is also stored in
  297. -- the array portion of the table in order
  298. local function doubleFillTable( tbl, ... )
  299. fillTable(tbl, ...);
  300. for i = 1, select("#", ...), 1 do
  301. local key = select(i, ...)
  302. tbl[i] = strtrim(key)
  303. end
  304. end
  305. --working tables
  306. local tokenTable = {};
  307. local sortingTable = {};
  308. local groupingTable = {};
  309. local tempTable = {};
  310. local function sortOnGroupWithNames(a, b)
  311. local order1 = tokenTable[ groupingTable[a] ];
  312. local order2 = tokenTable[ groupingTable[b] ];
  313. if ( order1 ) then
  314. if ( not order2 ) then
  315. return true;
  316. else
  317. if ( order1 == order2 ) then
  318. return sortingTable[a] < sortingTable[b];
  319. else
  320. return order1 < order2;
  321. end
  322. end
  323. else
  324. if ( order2 ) then
  325. return false;
  326. else
  327. return sortingTable[a] < sortingTable[b];
  328. end
  329. end
  330. end
  331. local function sortOnGroupWithIDs(a, b)
  332. local order1 = tokenTable[ groupingTable[a] ];
  333. local order2 = tokenTable[ groupingTable[b] ];
  334. if ( order1 ) then
  335. if ( not order2 ) then
  336. return true;
  337. else
  338. if ( order1 == order2 ) then
  339. return tonumber(a:match("%d+") or -1) < tonumber(b:match("%d+") or -1);
  340. else
  341. return order1 < order2;
  342. end
  343. end
  344. else
  345. if ( order2 ) then
  346. return false;
  347. else
  348. return tonumber(a:match("%d+") or -1) < tonumber(b:match("%d+") or -1);
  349. end
  350. end
  351. end
  352. local function sortOnNames(a, b)
  353. return sortingTable[a] < sortingTable[b];
  354. end
  355. local function sortOnNameList(a, b)
  356. return tokenTable[ sortingTable[a] ] < tokenTable[ sortingTable[b] ];
  357. end
  358. function SecureGroupHeader_Update(self)
  359. local nameList = self:GetAttribute("nameList");
  360. local groupFilter = self:GetAttribute("groupFilter");
  361. local roleFilter = self:GetAttribute("roleFilter");
  362. local sortMethod = self:GetAttribute("sortMethod");
  363. local groupBy = self:GetAttribute("groupBy");
  364. wipe(sortingTable);
  365. -- See if this header should be shown
  366. local kind, start, stop = GetGroupHeaderType(self);
  367. if ( not kind ) then
  368. configureChildren(self, sortingTable);
  369. return;
  370. end
  371. if ( not groupFilter and not roleFilter and not nameList ) then
  372. groupFilter = "1,2,3,4,5,6,7,8";
  373. end
  374. if ( groupFilter or roleFilter ) then
  375. local strictFiltering = self:GetAttribute("strictFiltering"); -- non-strict by default
  376. wipe(tokenTable)
  377. if ( groupFilter and not roleFilter ) then
  378. -- filtering by a list of group numbers and/or classes
  379. fillTable(tokenTable, strsplit(",", groupFilter));
  380. if ( strictFiltering ) then
  381. fillTable(tokenTable, "MAINTANK", "MAINASSIST", "TANK", "HEALER", "DAMAGER", "NONE")
  382. end
  383. elseif ( roleFilter and not groupFilter ) then
  384. -- filtering by role (of either type)
  385. fillTable(tokenTable, strsplit(",", roleFilter));
  386. if ( strictFiltering ) then
  387. fillTable(tokenTable, 1, 2, 3, 4, 5, 6, 7, 8, unpack(CLASS_SORT_ORDER))
  388. end
  389. else
  390. -- filtering by group, class and/or role
  391. fillTable(tokenTable, strsplit(",", groupFilter));
  392. fillTable(tokenTable, strsplit(",", roleFilter));
  393. end
  394. for i = start, stop, 1 do
  395. local unit, name, subgroup, className, role, assignedRole = GetGroupRosterInfo(kind, i);
  396. if ( name and
  397. ((not strictFiltering) and
  398. ( tokenTable[subgroup] or tokenTable[className] or (role and tokenTable[role]) or tokenTable[assignedRole] ) -- non-strict filtering
  399. ) or
  400. ( tokenTable[subgroup] and tokenTable[className] and ((role and tokenTable[role]) or tokenTable[assignedRole]) ) -- strict filtering
  401. ) then
  402. tinsert(sortingTable, unit);
  403. sortingTable[unit] = name;
  404. if ( groupBy == "GROUP" ) then
  405. groupingTable[unit] = subgroup;
  406. elseif ( groupBy == "CLASS" ) then
  407. groupingTable[unit] = className;
  408. elseif ( groupBy == "ROLE" ) then
  409. groupingTable[unit] = role;
  410. elseif ( groupBy == "ASSIGNEDROLE" ) then
  411. groupingTable[unit] = assignedRole;
  412. end
  413. end
  414. end
  415. if ( groupBy ) then
  416. local groupingOrder = self:GetAttribute("groupingOrder");
  417. doubleFillTable(wipe(tokenTable), strsplit(",", groupingOrder:gsub("%s+", "")));
  418. if ( sortMethod == "NAME" ) then
  419. table.sort(sortingTable, sortOnGroupWithNames);
  420. else
  421. table.sort(sortingTable, sortOnGroupWithIDs);
  422. end
  423. elseif ( sortMethod == "NAME" ) then -- sort by ID by default
  424. table.sort(sortingTable, sortOnNames);
  425. end
  426. else
  427. -- filtering via a list of names
  428. doubleFillTable(wipe(tokenTable), strsplit(",", nameList));
  429. for i = start, stop, 1 do
  430. local unit, name = GetGroupRosterInfo(kind, i);
  431. if ( tokenTable[name] ) then
  432. tinsert(sortingTable, unit);
  433. sortingTable[unit] = name;
  434. end
  435. end
  436. if ( sortMethod == "NAME" ) then
  437. table.sort(sortingTable, sortOnNames);
  438. elseif ( sortMethod == "NAMELIST" ) then
  439. table.sort(sortingTable, sortOnNameList)
  440. end
  441. end
  442. configureChildren(self, sortingTable);
  443. end
  444. --[[
  445. The Pet Header accepts all of the various configuration attributes of the
  446. regular raid header, as well as the following
  447. ======================================================
  448. useOwnerUnit = [BOOLEAN] - if true, then the owner's unit string is set on managed frames "unit" attribute (instead of pet's)
  449. filterOnPet = [BOOLEAN] - if true, then pet names are used when sorting/filtering the list
  450. --]]
  451. function SecureGroupPetHeader_OnLoad(self)
  452. self:RegisterEvent("GROUP_ROSTER_UPDATE");
  453. self:RegisterEvent("UNIT_NAME_UPDATE");
  454. self:RegisterEvent("UNIT_PET");
  455. end
  456. function SecureGroupPetHeader_OnEvent(self, event, ...)
  457. if ( (event == "GROUP_ROSTER_UPDATE" or event == "UNIT_NAME_UPDATE" or event == "UNIT_PET") and self:IsVisible() ) then
  458. SecureGroupPetHeader_Update(self);
  459. end
  460. end
  461. function SecureGroupPetHeader_OnAttributeChanged(self, name, value)
  462. if ( name == "_ignore" or self:GetAttribute("_ignore" ) ) then
  463. return
  464. end
  465. if ( self:IsVisible() ) then
  466. SecureGroupPetHeader_Update(self);
  467. end
  468. end
  469. local function GetPetUnit(kind, index)
  470. if ( kind == "RAID" ) then
  471. return "raidpet"..index;
  472. elseif ( index > 0 ) then
  473. return "partypet"..index;
  474. else
  475. return "pet";
  476. end
  477. end
  478. function SecureGroupPetHeader_Update(self)
  479. local nameList = self:GetAttribute("nameList");
  480. local groupFilter = self:GetAttribute("groupFilter");
  481. local sortMethod = self:GetAttribute("sortMethod");
  482. local groupBy = self:GetAttribute("groupBy");
  483. local useOwnerUnit = self:GetAttribute("useOwnerUnit");
  484. local filterOnPet = self:GetAttribute("filterOnPet");
  485. wipe(sortingTable);
  486. -- See if this header should be shown
  487. local kind, start, stop = GetGroupHeaderType(self);
  488. if ( not kind ) then
  489. configureChildren(self, sortingTable);
  490. return;
  491. end
  492. if ( not groupFilter and not nameList ) then
  493. groupFilter = "1,2,3,4,5,6,7,8";
  494. end
  495. if ( groupFilter ) then
  496. -- filtering by a list of group numbers and/or classes
  497. fillTable(wipe(tokenTable), strsplit(",", groupFilter));
  498. local strictFiltering = self:GetAttribute("strictFiltering"); -- non-strict by default
  499. for i = start, stop, 1 do
  500. local unit, name, subgroup, className, role = GetGroupRosterInfo(kind, i);
  501. local petUnit = GetPetUnit(kind, i);
  502. if ( filterOnPet ) then
  503. name = UnitName(petUnit);
  504. end
  505. if not ( useOwnerUnit ) then
  506. unit = petUnit;
  507. end
  508. if ( UnitExists(petUnit) ) then
  509. if ( name and
  510. ((not strictFiltering) and
  511. (tokenTable[subgroup] or tokenTable[className] or (role and tokenTable[role])) -- non-strict filtering
  512. ) or
  513. (tokenTable[subgroup] and tokenTable[className]) -- strict filtering
  514. ) then
  515. tinsert(sortingTable, unit);
  516. sortingTable[unit] = name;
  517. if ( groupBy == "GROUP" ) then
  518. groupingTable[unit] = subgroup;
  519. elseif ( groupBy == "CLASS" ) then
  520. groupingTable[unit] = className;
  521. elseif ( groupBy == "ROLE" ) then
  522. groupingTable[unit] = role;
  523. end
  524. end
  525. end
  526. end
  527. if ( groupBy ) then
  528. local groupingOrder = self:GetAttribute("groupingOrder");
  529. doubleFillTable(wipe(tokenTable), strsplit(",", groupingOrder));
  530. if ( sortMethod == "NAME" ) then
  531. table.sort(sortingTable, sortOnGroupWithNames);
  532. else
  533. table.sort(sortingTable, sortOnGroupWithIDs);
  534. end
  535. elseif ( sortMethod == "NAME" ) then -- sort by ID by default
  536. table.sort(sortingTable, sortOnNames);
  537. end
  538. else
  539. -- filtering via a list of names
  540. doubleFillTable(tokenTable, strsplit(",", nameList));
  541. for i = start, stop, 1 do
  542. local unit, name = GetGroupRosterInfo(kind, i);
  543. local petUnit = GetPetUnit(kind, i);
  544. if ( filterOnPet ) then
  545. name = UnitName(petUnit);
  546. end
  547. if not ( useOwnerUnit ) then
  548. unit = petUnit;
  549. end
  550. if ( tokenTable[name] and UnitExists(petUnit) ) then
  551. tinsert(sortingTable, unit);
  552. sortingTable[unit] = name;
  553. end
  554. end
  555. if ( sortMethod == "NAME" ) then
  556. table.sort(sortingTable, sortOnNames);
  557. end
  558. end
  559. configureChildren(self, sortingTable);
  560. end
  561. -- SecureAuraHeader contributed by Nevin Flanagan
  562. --[[
  563. filter = [STRING] -- a pipe-separated list of aura filter options ("RAID" will be ignored)
  564. separateOwn = [NUMBER] -- indicate whether buffs you cast yourself should be separated before (1) or after (-1) others. If 0 or nil, no separation is done.
  565. sortMethod = ["INDEX", "NAME", "TIME"] -- defines how the group is sorted (Default: "INDEX")
  566. sortDirection = ["+", "-"] -- defines the sort order (Default: "+")
  567. groupBy = [nil, auraFilter] -- if present, a series of comma-separated filters, appended to the base filter to separate auras into groups within a single stream
  568. includeWeapons = [nil, NUMBER] -- The aura sub-stream before which to include temporary weapon enchants. If nil or 0, they are ignored.
  569. consolidateTo = [nil, NUMBER] -- The aura sub-stream before which to place a proxy for the consolidated header. If nil or 0, consolidation is ignored.
  570. consolidateDuration = [nil, NUMBER] -- the minimum total duration an aura should have to be considered for consolidation (Default: 30)
  571. consolidateThreshold = [nil, NUMBER] -- buffs with less remaining duration than this many seconds should not be consolidated (Default: 10)
  572. consolidateFraction = [nil, NUMBER] -- The fraction of remaining duration a buff should still have to be eligible for consolidation (Default: .10)
  573. template = [STRING] -- the XML template to use for the unit buttons. If the created widgets should be something other than Buttons, append the Widget name after a comma.
  574. weaponTemplate = [STRING] -- the XML template to use for temporary enchant buttons. Can be nil if you preset the tempEnchant1 and tempEnchant2 attributes, or if you don't include temporary enchants.
  575. consolidateProxy = [STRING|Frame] -- Either the button which represents consolidated buffs, or the name of the template used to construct one.
  576. consolidateHeader = [STRING|Frame] -- Either the aura header which contains consolidated buffs, or the name of the template used to construct one.
  577. point = [STRING] -- a valid XML anchoring point (Default: "TOPRIGHT")
  578. minWidth = [nil, NUMBER] -- the minimum width of the container frame
  579. minHeight = [nil, NUMBER] -- the minimum height of the container frame
  580. xOffset = [NUMBER] -- the x-Offset to use when anchoring the unit buttons. This should typically be set to at least the width of your buff template.
  581. yOffset = [NUMBER] -- the y-Offset to use when anchoring the unit buttons. This should typically be set to at least the height of your buff template.
  582. wrapAfter = [NUMBER] -- begin a new row or column after this many auras. If 0 or nil, never wrap or limit the first row
  583. wrapXOffset = [NUMBER] -- the x-offset from one row or column to the next
  584. wrapYOffset = [NUMBER] -- the y-offset from one row or column to the next
  585. maxWraps = [NUMBER] -- limit the number of rows or columns. If 0 or nil, the number of rows or columns will not be limited.
  586. --]]
  587. local function SetupAuraButtonConfiguration( header, newChild, defaultConfigFunction )
  588. local configCode = newChild:GetAttribute("initialConfigFunction") or header:GetAttribute("initialConfigFunction") or defaultConfigFunction;
  589. if ( type(configCode) == "string" ) then
  590. local selfHandle = GetFrameHandle(newChild);
  591. if ( selfHandle ) then
  592. CallRestrictedClosure(newChild, "self", GetManagedEnvironment(header, true),
  593. selfHandle, configCode, selfHandle);
  594. end
  595. end
  596. end
  597. function SecureAuraHeader_OnLoad(self)
  598. self:RegisterEvent("UNIT_AURA");
  599. end
  600. function SecureAuraHeader_OnUpdate(self)
  601. local hasMainHandEnchant, _, _, _, hasOffHandEnchant = GetWeaponEnchantInfo();
  602. if ( hasMainHandEnchant ~= self:GetAttribute("_mainEnchanted") ) then
  603. self:SetAttribute("_mainEnchanted", hasMainHandEnchant);
  604. end
  605. if ( hasOffHandEnchant ~= self:GetAttribute("_secondaryEnchanted") ) then
  606. self:SetAttribute("_secondaryEnchanted", hasOffHandEnchant);
  607. end
  608. end
  609. function SecureAuraHeader_OnEvent(self, event, ...)
  610. if ( self:IsVisible() ) then
  611. local unit = SecureButton_GetUnit(self);
  612. if ( event == "UNIT_AURA" and ... == unit ) then
  613. SecureAuraHeader_Update(self);
  614. end
  615. end
  616. end
  617. function SecureAuraHeader_OnAttributeChanged(self, name, value)
  618. if ( name == "_ignore" or self:GetAttribute("_ignore") ) then
  619. return;
  620. end
  621. if ( self:IsVisible() ) then
  622. SecureAuraHeader_Update(self);
  623. end
  624. end
  625. local buttons = {};
  626. local function extractTemplateInfo(template, defaultWidget)
  627. local widgetType;
  628. if ( template ) then
  629. template, widgetType = strsplit(",", (tostring(template):trim():gsub("%s*,%s*", ",")) );
  630. if ( template ~= "" ) then
  631. if ( not widgetType or widgetType == "" ) then
  632. widgetType = defaultWidget;
  633. end
  634. return template, widgetType;
  635. end
  636. end
  637. return nil;
  638. end
  639. local function constructChild(kind, name, parent, template)
  640. local new = CreateFrame(kind, name, parent, template);
  641. SetupAuraButtonConfiguration(parent, new);
  642. return new;
  643. end
  644. local enchantableSlots = {
  645. [1] = "MainHandSlot",
  646. [2] = "SecondaryHandSlot"
  647. }
  648. local function configureAuras(self, auraTable, consolidateTable, weaponPosition)
  649. local point = self:GetAttribute("point") or "TOPRIGHT";
  650. local xOffset = tonumber(self:GetAttribute("xOffset")) or 0;
  651. local yOffset = tonumber(self:GetAttribute("yOffset")) or 0;
  652. local wrapXOffset = tonumber(self:GetAttribute("wrapXOffset")) or 0;
  653. local wrapYOffset = tonumber(self:GetAttribute("wrapYOffset")) or 0;
  654. local wrapAfter = tonumber(self:GetAttribute("wrapAfter"));
  655. if ( wrapAfter == 0 ) then wrapAfter = nil; end
  656. local maxWraps = self:GetAttribute("maxWraps");
  657. if ( maxWraps == 0 ) then maxWraps = nil; end
  658. local minWidth = tonumber(self:GetAttribute("minWidth")) or 0;
  659. local minHeight = tonumber(self:GetAttribute("minHeight")) or 0;
  660. if ( consolidateTable and #consolidateTable == 0 ) then
  661. consolidateTable = nil;
  662. end
  663. local name = self:GetName();
  664. wipe(buttons);
  665. local buffTemplate, buffWidget = extractTemplateInfo(self:GetAttribute("template"), "Button");
  666. if ( buffTemplate ) then
  667. for i=1, #auraTable do
  668. local childAttr = "child"..i;
  669. local button = self:GetAttribute("child"..i);
  670. if ( button ) then
  671. button:ClearAllPoints();
  672. else
  673. button = constructChild(buffWidget, name and name.."AuraButton"..i, self, buffTemplate);
  674. setAttributesWithoutResponse(self, childAttr, button, "frameref-"..childAttr, GetFrameHandle(button));
  675. end
  676. local buffInfo = auraTable[i];
  677. button:SetID(buffInfo.index);
  678. button:SetAttribute("index", buffInfo.index);
  679. button:SetAttribute("filter", buffInfo.filter);
  680. buttons[i] = button;
  681. end
  682. end
  683. local consolidateProxy = self:GetAttribute("consolidateProxy");
  684. if ( consolidateTable ) then
  685. if ( type(consolidateProxy) == 'string' ) then
  686. local template, widgetType = extractTemplateInfo(consolidateProxy, "Button");
  687. if ( template ) then
  688. consolidateProxy = constructChild(widgetType, name and name.."ProxyButton", self, template);
  689. setAttributesWithoutResponse(self, "consolidateProxy", consolidateProxy, "frameref-proxy", GetFrameHandle(consolidateProxy));
  690. else
  691. consolidateProxy = nil;
  692. end
  693. end
  694. if ( consolidateProxy ) then
  695. if ( consolidateTable.position ) then
  696. tinsert(buttons, consolidateTable.position, consolidateProxy);
  697. else
  698. tinsert(buttons, consolidateProxy);
  699. end
  700. consolidateProxy:ClearAllPoints();
  701. end
  702. else
  703. if ( consolidateProxy and type(consolidateProxy.Hide) == 'function' ) then
  704. consolidateProxy:Hide();
  705. end
  706. end
  707. if ( weaponPosition ) then
  708. local hasMainHandEnchant, _, _, _, hasOffHandEnchant, _, _, _, hasRangedEnchant = GetWeaponEnchantInfo();
  709. for weapon=2,1,-1 do
  710. local weaponAttr = "tempEnchant"..weapon
  711. local tempEnchant = self:GetAttribute(weaponAttr)
  712. if ( (select(weapon, hasMainHandEnchant, hasOffHandEnchant, hasRangedEnchant)) ) then
  713. if ( not tempEnchant ) then
  714. local template, widgetType = extractTemplateInfo(self:GetAttribute("weaponTemplate"), "Button");
  715. if ( template ) then
  716. tempEnchant = constructChild(widgetType, name and name.."TempEnchant"..weapon, self, template);
  717. setAttributesWithoutResponse(self, weaponAttr, tempEnchant);
  718. end
  719. end
  720. if ( tempEnchant ) then
  721. tempEnchant:ClearAllPoints();
  722. local slot = GetInventorySlotInfo(enchantableSlots[weapon]);
  723. tempEnchant:SetAttribute("target-slot", slot);
  724. tempEnchant:SetID(slot);
  725. if ( weaponPosition == 0 ) then
  726. tinsert(buttons, tempEnchant);
  727. else
  728. tinsert(buttons, weaponPosition, tempEnchant);
  729. end
  730. end
  731. else
  732. if ( tempEnchant and type(tempEnchant.Hide) == 'function' ) then
  733. tempEnchant:Hide();
  734. end
  735. end
  736. end
  737. end
  738. local display = #buttons
  739. if ( wrapAfter and maxWraps ) then
  740. display = min(display, wrapAfter * maxWraps);
  741. end
  742. local left, right, top, bottom = math.huge, -math.huge, -math.huge, math.huge;
  743. for index=1,display do
  744. local button = buttons[index];
  745. local wrapAfter = wrapAfter or index
  746. local tick, cycle = floor((index - 1) % wrapAfter), floor((index - 1) / wrapAfter);
  747. button:SetPoint(point, self, cycle * wrapXOffset + tick * xOffset, cycle * wrapYOffset + tick * yOffset);
  748. button:Show();
  749. left = min(left, button:GetLeft() or math.huge);
  750. right = max(right, button:GetRight() or -math.huge);
  751. top = max(top, button:GetTop() or -math.huge);
  752. bottom = min(bottom, button:GetBottom() or math.huge);
  753. end
  754. local deadIndex = #(auraTable) + 1;
  755. local button = self:GetAttribute("child"..deadIndex);
  756. while ( button ) do
  757. button:Hide();
  758. deadIndex = deadIndex + 1;
  759. button = self:GetAttribute("child"..deadIndex)
  760. end
  761. if ( display >= 1 ) then
  762. self:SetWidth(max(right - left, minWidth));
  763. self:SetHeight(max(top - bottom, minHeight));
  764. else
  765. self:SetWidth(minWidth);
  766. self:SetHeight(minHeight);
  767. end
  768. if ( consolidateTable ) then
  769. local header = self:GetAttribute("consolidateHeader");
  770. if ( type(header) == 'string' ) then
  771. local template, widgetType = extractTemplateInfo(header, "Frame");
  772. if ( template ) then
  773. header = constructChild(widgetType, name and name.."ProxyHeader", consolidateProxy, template);
  774. setAttributesWithoutResponse(self, "consolidateHeader", header);
  775. consolidateProxy:SetAttribute("header", header);
  776. consolidateProxy:SetAttribute("frameref-header", GetFrameHandle(header))
  777. end
  778. end
  779. if ( header ) then
  780. configureAuras(header, consolidateTable);
  781. end
  782. end
  783. end
  784. local tremove = table.remove;
  785. local function stripRAID(filter)
  786. return filter and tostring(filter):upper():gsub("RAID", ""):gsub("|+", "|"):match("^|?(.+[^|])|?$");
  787. end
  788. local freshTable;
  789. local releaseTable;
  790. do
  791. local tableReserve = {};
  792. freshTable = function ()
  793. local t = next(tableReserve) or {};
  794. tableReserve[t] = nil;
  795. return t;
  796. end
  797. releaseTable = function (t)
  798. tableReserve[t] = wipe(t);
  799. end
  800. end
  801. local sorters = {};
  802. local function sortFactory(key, separateOwn, reverse)
  803. if ( separateOwn ~= 0 ) then
  804. if ( reverse ) then
  805. return function (a, b)
  806. if ( groupingTable[a.filter] == groupingTable[b.filter] ) then
  807. local ownA, ownB = a.caster == "player", b.caster == "player";
  808. if ( ownA ~= ownB ) then
  809. return ownA == (separateOwn > 0)
  810. end
  811. return a[key] > b[key];
  812. else
  813. return groupingTable[a.filter] < groupingTable[b.filter];
  814. end
  815. end;
  816. else
  817. return function (a, b)
  818. if ( groupingTable[a.filter] == groupingTable[b.filter] ) then
  819. local ownA, ownB = a.caster == "player", b.caster == "player";
  820. if ( ownA ~= ownB ) then
  821. return ownA == (separateOwn > 0)
  822. end
  823. return a[key] < b[key];
  824. else
  825. return groupingTable[a.filter] < groupingTable[b.filter];
  826. end
  827. end;
  828. end
  829. else
  830. if ( reverse ) then
  831. return function (a, b)
  832. if ( groupingTable[a.filter] == groupingTable[b.filter] ) then
  833. return a[key] > b[key];
  834. else
  835. return groupingTable[a.filter] < groupingTable[b.filter];
  836. end
  837. end;
  838. else
  839. return function (a, b)
  840. if ( groupingTable[a.filter] == groupingTable[b.filter] ) then
  841. return a[key] < b[key];
  842. else
  843. return groupingTable[a.filter] < groupingTable[b.filter];
  844. end
  845. end;
  846. end
  847. end
  848. end
  849. for i, key in ipairs{"index", "name", "expires"} do
  850. local label = key:upper();
  851. sorters[label] = {};
  852. for bool in pairs{[true] = true, [false] = false} do
  853. sorters[label][bool] = {}
  854. for sep=-1,1 do
  855. sorters[label][bool][sep] = sortFactory(key, sep, bool);
  856. end
  857. end
  858. end
  859. sorters.TIME = sorters.EXPIRES;
  860. function SecureAuraHeader_Update(self)
  861. local filter = self:GetAttribute("filter");
  862. local groupBy = self:GetAttribute("groupBy");
  863. local unit = SecureButton_GetUnit(self) or "player";
  864. local includeWeapons = tonumber(self:GetAttribute("includeWeapons"));
  865. if ( includeWeapons == 0 ) then
  866. includeWeapons = nil
  867. end
  868. local consolidateTo = tonumber(self:GetAttribute("consolidateTo"));
  869. local consolidateDuration, consolidateThreshold, consolidateFraction;
  870. if ( consolidateTo ) then
  871. consolidateDuration = tonumber(self:GetAttribute("consolidateDuration")) or 30;
  872. consolidateThreshold = tonumber(self:GetAttribute("consolidateThreshold")) or 10;
  873. consolidateFraction = tonumber(self:GetAttribute("consolidateFraction")) or 0.1;
  874. end
  875. local sortDirection = self:GetAttribute("sortDirection");
  876. local separateOwn = tonumber(self:GetAttribute("separateOwn")) or 0;
  877. if ( separateOwn > 0 ) then
  878. separateOwn = 1;
  879. elseif (separateOwn < 0 ) then
  880. separateOwn = -1;
  881. end
  882. local sortMethod = (sorters[tostring(self:GetAttribute("sortMethod")):upper()] or sorters["INDEX"])[sortDirection == "-"][separateOwn];
  883. local time = GetTime();
  884. local consolidateTable;
  885. if ( consolidateTo and consolidateTo ~= 0 ) then
  886. consolidateTable = wipe(tokenTable);
  887. end
  888. wipe(sortingTable);
  889. wipe(groupingTable);
  890. if ( groupBy ) then
  891. local i = 1;
  892. for subFilter in groupBy:gmatch("[^,]+") do
  893. if ( filter ) then
  894. subFilter = stripRAID(filter.."|"..subFilter);
  895. else
  896. subFilter = stripRAID(subFilter);
  897. end
  898. groupingTable[subFilter], groupingTable[i] = i, subFilter;
  899. i = i + 1;
  900. end
  901. else
  902. filter = stripRAID(filter);
  903. groupingTable[filter], groupingTable[1] = 1, filter;
  904. end
  905. if ( consolidateTable and consolidateTo < 0 ) then
  906. consolidateTo = #groupingTable + consolidateTo + 1;
  907. end
  908. if ( includeWeapons and includeWeapons < 0 ) then
  909. includeWeapons = #groupingTable + includeWeapons + 1;
  910. end
  911. local weaponPosition;
  912. for filterIndex, fullFilter in ipairs(groupingTable) do
  913. if ( consolidateTable and not consolidateTable.position and filterIndex >= consolidateTo ) then
  914. consolidateTable.position = #sortingTable + 1;
  915. end
  916. if ( includeWeapons and not weaponPosition and filterIndex >= includeWeapons ) then
  917. weaponPosition = #sortingTable + 1;
  918. end
  919. local i = 1;
    repeat
  920. AuraUtil.ForEachAura(unit, fullFilter, nil, function(...)
  921. local aura, _, duration = freshTable();
    aura.name, _, _, _, duration, aura.expires, aura.caster, _, aura.shouldConsolidate, _ = UnitAura(unit, i, fullFilter);
    if ( aura.name ) then
  922. aura.name, _, _, _, duration, aura.expires, aura.caster, _, aura.shouldConsolidate, _ = ...;
  923. aura.filter = fullFilter;
  924. aura.index = i;
  925. local targetList = sortingTable;
  926. if ( consolidateTable and aura.shouldConsolidate ) then
  927. if ( not aura.expires or duration > consolidateDuration or (aura.expires - time >= max(consolidateThreshold, duration * consolidateFraction)) ) then
  928. targetList = consolidateTable;
  929. end
  930. end
  931. tinsert(targetList, aura);
    else
    releaseTable(aura);
    end
  932. i = i + 1;
    until ( not aura.name );
  933. return false;
  934. end);
  935. end
  936. if ( includeWeapons and not weaponPosition ) then
  937. weaponPosition = 0;
  938. end
  939. table.sort(sortingTable, sortMethod);
  940. if ( consolidateTable ) then
  941. table.sort(consolidateTable, sortMethod);
  942. end
  943. configureAuras(self, sortingTable, consolidateTable, weaponPosition);
  944. while ( sortingTable[1] ) do
  945. releaseTable(tremove(sortingTable));
  946. end
  947. while ( consolidateTable and consolidateTable[1] ) do
  948. releaseTable(tremove(consolidateTable));
  949. end
  950. end