local MAJOR, MINOR = "ScrollingTable", tonumber("145") or 40000; if MINOR < 40000 then MINOR = MINOR + 10000; end local ScrollingTable, oldminor = LibStub:NewLibrary(MAJOR, MINOR); if not ScrollingTable then return; -- No Upgrade needed. end do local defaultcolor = { ["r"] = 1.0, ["g"] = 1.0, ["b"] = 1.0, ["a"] = 1.0 }; local defaulthighlight = { ["r"] = 1.0, ["g"] = 0.9, ["b"] = 0.0, ["a"] = 0.5 }; local defaulthighlightblank = { ["r"] = 0.0, ["g"] = 0.0, ["b"] = 0.0, ["a"] = 0.0 }; local lrpadding = 2.5; local ScrollPaneBackdrop = { bgFile = "Interface\\ChatFrame\\ChatFrameBackground", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 16, insets = { left = 3, right = 3, top = 5, bottom = 3 } }; local SetHeight = function(self) self.frame:SetHeight( (self.displayRows * self.rowHeight) + 10); self:Refresh(); end local SetWidth = function(self) local width = 13; for num, col in pairs(self.cols) do width = width + col.width; end self.frame:SetWidth(width+20); self:Refresh(); end --- API for a ScrollingTable table -- @name SetHighLightColor -- @description Set the row highlight color of a frame ( cell or row ) -- @usage st:SetHighLightColor(rowFrame, color) -- @see http://www.wowace.com/addons/lib-st/pages/colors/ local function SetHighLightColor (self, frame, color) if not frame.highlight then frame.highlight = frame:CreateTexture(nil, "OVERLAY"); frame.highlight:SetAllPoints(frame); end frame.highlight:SetTexture(color.r, color.g, color.b, color.a); end local FireUserEvent = function (self, frame, event, handler, ...) if not handler( ...) then if self.DefaultEvents[event] then self.DefaultEvents[event]( ...); end end end --- API for a ScrollingTable table -- @name RegisterEvents -- @description Set the event handlers for various ui events for each cell. -- @usage st:RegisterEvents(events, true) -- @see http://www.wowace.com/addons/lib-st/pages/ui-events/ local function RegisterEvents (self, events, fRemoveOldEvents) local table = self; -- save for closure later for i, row in ipairs(self.rows) do for j, col in ipairs(row.cols) do -- unregister old events. if fRemoveOldEvents and self.events then for event, handler in pairs(self.events) do col:SetScript(event, nil); end end -- register new ones. for event, handler in pairs(events) do col:SetScript(event, function(cellFrame, ...) local realindex = table.filtered[i+table.offset]; table:FireUserEvent(col, event, handler, row, cellFrame, table.data, table.cols, i, realindex, j, table, ... ); end); end end end for j, col in ipairs(self.head.cols) do -- unregister old events. if fRemoveOldEvents and self.events then for event, handler in pairs(self.events) do col:SetScript(event, nil); end end -- register new ones. for event, handler in pairs(events) do col:SetScript(event, function(cellFrame, ...) table:FireUserEvent(col, event, handler, self.head, cellFrame, table.data, table.cols, nil, nil, j, table, ...); end); end end self.events = events; end --- API for a ScrollingTable table -- @name SetDisplayRows -- @description Set the number and height of displayed rows -- @usage st:SetDisplayRows(10, 15) local function SetDisplayRows (self, num, rowHeight) local table = self; -- reference saved for closure -- should always set columns first self.displayRows = num; self.rowHeight = rowHeight; if not self.rows then self.rows = {}; end for i = 1, num do local row = self.rows[i]; if not row then row = CreateFrame("Button", self.frame:GetName().."Row"..i, self.frame); self.rows[i] = row; if i > 1 then row:SetPoint("TOPLEFT", self.rows[i-1], "BOTTOMLEFT", 0, 0); row:SetPoint("TOPRIGHT", self.rows[i-1], "BOTTOMRIGHT", 0, 0); else row:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 4, -5); row:SetPoint("TOPRIGHT", self.frame, "TOPRIGHT", -4, -5); end row:SetHeight(rowHeight); end if not row.cols then row.cols = {}; end for j = 1, #self.cols do local col = row.cols[j]; if not col then col = CreateFrame("Button", row:GetName().."col"..j, row); col.text = row:CreateFontString(col:GetName().."text", "OVERLAY", "GameFontHighlightSmall"); row.cols[j] = col; local align = self.cols[j].align or "LEFT"; col.text:SetJustifyH(align); col:EnableMouse(true); col:RegisterForClicks("AnyUp"); if self.events then for event, handler in pairs(self.events) do col:SetScript(event, function(cellFrame, ...) if table.offset then local realindex = table.filtered[i+table.offset]; table:FireUserEvent(col, event, handler, row, cellFrame, table.data, table.cols, i, realindex, j, table, ... ); end end); end end end if j > 1 then col:SetPoint("LEFT", row.cols[j-1], "RIGHT", 0, 0); else col:SetPoint("LEFT", row, "LEFT", 2, 0); end col:SetHeight(rowHeight); col:SetWidth(self.cols[j].width); col.text:SetPoint("TOP", col, "TOP", 0, 0); col.text:SetPoint("BOTTOM", col, "BOTTOM", 0, 0); col.text:SetWidth(self.cols[j].width - 2*lrpadding); end local j = #self.cols + 1; local col = row.cols[j]; while col do col:Hide(); j = j + 1; col = row.cols[j]; end end for i = num + 1, #self.rows do self.rows[i]:Hide(); end self:SetHeight(); end --- API for a ScrollingTable table -- @name SetDisplayCols -- @description Set the column info for the scrolling table -- @usage st:SetDisplayCols(cols) -- @see http://www.wowace.com/addons/lib-st/pages/create-st/#w-cols local function SetDisplayCols (self, cols) local table = self; -- reference saved for closure self.cols = cols; local row = self.head if not row then row = CreateFrame("Frame", self.frame:GetName().."Head", self.frame); row:SetPoint("BOTTOMLEFT", self.frame, "TOPLEFT", 4, 0); row:SetPoint("BOTTOMRIGHT", self.frame, "TOPRIGHT", -4, 0); row:SetHeight(self.rowHeight); row.cols = {}; self.head = row; end for i = 1, #cols do local colFrameName = row:GetName().."Col"..i; local col = getglobal(colFrameName); if not col then col = CreateFrame("Button", colFrameName, row); col:RegisterForClicks("AnyUp"); -- LS: right clicking on header if self.events then for event, handler in pairs(self.events) do col:SetScript(event, function(cellFrame, ...) table:FireUserEvent(col, event, handler, row, cellFrame, table.data, table.cols, nil, nil, i, table, ...); end); end end end row.cols[i] = col; local fs = col:GetFontString() or col:CreateFontString(col:GetName().."fs", "OVERLAY", "GameFontHighlightSmall"); fs:SetAllPoints(col); fs:SetPoint("LEFT", col, "LEFT", lrpadding, 0); fs:SetPoint("RIGHT", col, "RIGHT", -lrpadding, 0); local align = cols[i].align or "LEFT"; fs:SetJustifyH(align); col:SetFontString(fs); fs:SetText(cols[i].name); fs:SetTextColor(1.0, 1.0, 1.0, 1.0); col:SetPushedTextOffset(0,0); if i > 1 then col:SetPoint("LEFT", row.cols[i-1], "RIGHT", 0, 0); else col:SetPoint("LEFT", row, "LEFT", 2, 0); end col:SetHeight(self.rowHeight); col:SetWidth(cols[i].width); local color = cols[i].bgcolor; if (color) then local colibg = "col"..i.."bg"; local bg = self.frame[colibg]; if not bg then bg = self.frame:CreateTexture(nil, "OVERLAY"); self.frame[colibg] = bg; end bg:SetPoint("BOTTOM", self.frame, "BOTTOM", 0, 4); bg:SetPoint("TOPLEFT", col, "BOTTOMLEFT", 0, -4); bg:SetPoint("TOPRIGHT", col, "BOTTOMRIGHT", 0, -4); bg:SetTexture(color.r, color.g, color.b, color.a); end end self:SetDisplayRows(self.displayRows, self.rowHeight); self:SetWidth(); end --- API for a ScrollingTable table -- @name Show -- @description Used to show the scrolling table when hidden. -- @usage st:Show() local function Show (self) self.frame:Show(); self.scrollframe:Show(); self.showing = true; end --- API for a ScrollingTable table -- @name Hide -- @description Used to hide the scrolling table when shown. -- @usage st:Hide() local function Hide (self) self.frame:Hide(); self.showing = false; end --- API for a ScrollingTable table -- @name SortData -- @description Resorts the table using the rules specified in the table column info. -- @usage st:SortData() -- @see http://www.wowace.com/addons/lib-st/pages/create-st/#w-defaultsort local function SortData (self) -- sanity check if not(self.sorttable) or (#self.sorttable ~= #self.data)then self.sorttable = {}; end if #self.sorttable ~= #self.data then for i = 1, #self.data do self.sorttable[i] = i; end end -- go on sorting local i, sortby = 1, nil; while i <= #self.cols and not sortby do if self.cols[i].sort then sortby = i; end i = i + 1; end if sortby then table.sort(self.sorttable, function(rowa, rowb) local column = self.cols[sortby]; if column.comparesort then return column.comparesort(self, rowa, rowb, sortby); else return self:CompareSort(rowa, rowb, sortby); end end); end self.filtered = self:DoFilter(); self:Refresh(); end local StringToNumber = function(str) if str == "" then return 0; else return tonumber(str) end end --- API for a ScrollingTable table -- @name CompareSort -- @description CompareSort function used to determine how to sort column values. Can be overridden in column data or table data. -- @usage used internally. -- @see Core.lua local function CompareSort (self, rowa, rowb, sortbycol) local cella, cellb = self:GetCell(rowa, sortbycol), self:GetCell(rowb, sortbycol); local a1, b1 = cella, cellb; if type(a1) == 'table' then a1 = a1.value; end if type(b1) == 'table' then b1 = b1.value; end local column = self.cols[sortbycol]; if type(a1) == "function" then if (cella.args) then a1 = a1(unpack(cella.args)) else a1 = a1(self.data, self.cols, rowa, sortbycol, self); end end if type(b1) == "function" then if (cellb.args) then b1 = b1(unpack(cellb.args)) else b1 = b1(self.data, self.cols, rowb, sortbycol, self); end end if type(a1) ~= type(b1) then local typea, typeb = type(a1), type(b1); if typea == "number" and typeb == "string" then if tonumber(b1) then -- is it a number in a string? b1 = StringToNumber(b1); -- "" = 0 else a1 = tostring(a1); end elseif typea == "string" and typeb == "number" then if tonumber(a1) then -- is it a number in a string? a1 = StringToNumber(a1); -- "" = 0 else b1 = tostring(b1); end end end if a1 == b1 then if column.sortnext then local nextcol = self.cols[column.sortnext]; if not(nextcol.sort) then if nextcol.comparesort then return nextcol.comparesort(self, rowa, rowb, column.sortnext); else return self:CompareSort(rowa, rowb, column.sortnext); end else return false; end else return false; end else local direction = column.sort or column.defaultsort or "asc"; if direction:lower() == "asc" then return a1 > b1; else return a1 < b1; end end end local Filter = function(self, rowdata) return true; end --- API for a ScrollingTable table -- @name SetFilter -- @description Set a display filter for the table. -- @usage st:SetFilter( function (self, ...) return true end ) -- @see http://www.wowace.com/addons/lib-st/pages/filtering-the-scrolling-table/ local function SetFilter (self, Filter) self.Filter = Filter; self:SortData(); end local DoFilter = function(self) local result = {}; for row = 1, #self.data do local realrow = self.sorttable[row]; local rowData = self:GetRow(realrow); if self:Filter(rowData) then table.insert(result, realrow); end end return result; end function GetDefaultHighlightBlank(self) return self.defaulthighlightblank; end function SetDefaultHighlightBlank(self, red, green, blue, alpha) if not self.defaulthighlightblank then self.defaulthighlightblank = defaulthighlightblank; end if red then self.defaulthighlightblank["r"] = red; end if green then self.defaulthighlightblank["g"] = green; end if blue then self.defaulthighlightblank["b"] = blue; end if alpha then self.defaulthighlightblank["a"] = alpha; end end function GetDefaultHighlight(self) return self.defaulthighlight; end function SetDefaultHighlight(self, red, green, blue, alpha) if not self.defaulthighlight then self.defaulthighlight = defaulthighlight; end if red then self.defaulthighlight["r"] = red; end if green then self.defaulthighlight["g"] = green; end if blue then self.defaulthighlight["b"] = blue; end if alpha then self.defaulthighlight["a"] = alpha; end end --- API for a ScrollingTable table -- @name EnableSelection -- @description Turn on or off selection on a table according to flag. Will not refresh the table display. -- @usage st:EnableSelection(true) local function EnableSelection(self, flag) self.fSelect = flag; end --- API for a ScrollingTable table -- @name ClearSelection -- @description Clear the currently selected row. You should not need to refresh the table. -- @usage st:ClearSelection() local function ClearSelection(self) self:SetSelection(nil); end --- API for a ScrollingTable table -- @name SetSelection -- @description Sets the currently selected row to 'realrow'. Realrow is the unaltered index of the data row in your table. You should not need to refresh the table. -- @usage st:SetSelection(12) local function SetSelection(self, realrow) self.selected = realrow; self:Refresh(); end --- API for a ScrollingTable table -- @name GetSelection -- @description Gets the currently selected to row. Return will be the unaltered index of the data row that is selected. -- @usage st:GetSelection() local function GetSelection(self) return self.selected; end --- API for a ScrollingTable table -- @name DoCellUpdate -- @description Cell update function used to paint each cell. Can be overridden in column data or table data. -- @usage used internally. -- @see http://www.wowace.com/addons/lib-st/pages/docell-update/ local function DoCellUpdate (rowFrame, cellFrame, data, cols, row, realrow, column, fShow, table, ...) if fShow then local rowdata = table:GetRow(realrow); local celldata = table:GetCell(rowdata, column); local cellvalue = celldata; if type(celldata) == "table" then cellvalue = celldata.value; end if type(cellvalue) == "function" then if celldata.args then cellFrame.text:SetText(cellvalue(unpack(celldata.args))); else cellFrame.text:SetText(cellvalue(data, cols, realrow, column, table)); end else cellFrame.text:SetText(cellvalue); end local color = nil; if type(celldata) == "table" then color = celldata.color; end local colorargs = nil; if not color then color = cols[column].color; if not color then color = rowdata.color; if not color then color = defaultcolor; else colorargs = rowdata.colorargs; end else colorargs = cols[column].colorargs; end else colorargs = celldata.colorargs; end if type(color) == "function" then if colorargs then color = color(unpack(colorargs)); else color = color(data, cols, realrow, column, table); end end cellFrame.text:SetTextColor(color.r, color.g, color.b, color.a); local highlight = nil; if type(celldata) == "table" then highlight = celldata.highlight; end if table.fSelect then if table.selected == realrow then table:SetHighLightColor(rowFrame, highlight or cols[column].highlight or rowdata.highlight or table:GetDefaultHighlight()); else table:SetHighLightColor(rowFrame, table:GetDefaultHighlightBlank()); end end else cellFrame.text:SetText(""); end end --- API for a ScrollingTable table -- @name SetData -- @description Sets the data for the scrolling table -- @usage st:SetData(datatable) -- @see http://www.wowace.com/addons/lib-st/pages/set-data/ local function SetData (self, data, isMinimalDataformat) self.isMinimalDataformat = isMinimalDataformat; self.data = data; self:SortData(); end --- API for a ScrollingTable table -- @name GetRow -- @description Returns the data row of the table from the given data row index -- @usage used internally. local function GetRow(self, realrow) return self.data[realrow]; end --- API for a ScrollingTable table -- @name GetCell -- @description Returns the cell data of the given row from the given row and column index -- @usage used internally. local function GetCell(self, row, col) local rowdata = row; if type(row) == "number" then rowdata = self:GetRow(row); end if self.isMinimalDataformat then return rowdata[col]; else return rowdata.cols[col]; end end --- API for a ScrollingTable table -- @name IsRowVisible -- @description Checks if a row is currently being shown -- @usage st:IsRowVisible(realrow) -- @thanks sapu94 local function IsRowVisible(self, realrow) return (realrow > self.offset and realrow <= (self.displayRows + self.offset)) end function ScrollingTable:CreateST(cols, numRows, rowHeight, highlight, parent) local st = {}; self.framecount = self.framecount or 1; local f = CreateFrame("Frame", "ScrollTable" .. self.framecount, parent or UIParent); self.framecount = self.framecount + 1; st.showing = true; st.frame = f; st.Show = Show; st.Hide = Hide; st.SetDisplayRows = SetDisplayRows; st.SetRowHeight = SetRowHeight; st.SetHeight = SetHeight; st.SetWidth = SetWidth; st.SetDisplayCols = SetDisplayCols; st.SetData = SetData; st.SortData = SortData; st.CompareSort = CompareSort; st.RegisterEvents = RegisterEvents; st.FireUserEvent = FireUserEvent; st.SetDefaultHighlightBlank = SetDefaultHighlightBlank; st.SetDefaultHighlight = SetDefaultHighlight; st.GetDefaultHighlightBlank = GetDefaultHighlightBlank; st.GetDefaultHighlight = GetDefaultHighlight; st.EnableSelection = EnableSelection; st.SetHighLightColor = SetHighLightColor; st.ClearSelection = ClearSelection; st.SetSelection = SetSelection; st.GetSelection = GetSelection; st.GetCell = GetCell; st.GetRow = GetRow; st.DoCellUpdate = DoCellUpdate; st.RowIsVisible = IsRowVisible; st.SetFilter = SetFilter; st.DoFilter = DoFilter; highlight = highlight or {}; st:SetDefaultHighlight(highlight["r"], highlight["g"], highlight["b"], highlight["a"]); -- highlight color st:SetDefaultHighlightBlank(); -- non highlight color st.displayRows = numRows or 12; st.rowHeight = rowHeight or 15; st.cols = cols; st.DefaultEvents = { ["OnEnter"] = function (rowFrame, cellFrame, data, cols, row, realrow, column, table, ...) if row and realrow then local rowdata = table:GetRow(realrow); local celldata = table:GetCell(rowdata, column); local highlight = nil; if type(celldata) == "table" then highlight = celldata.highlight; end table:SetHighLightColor(rowFrame, highlight or cols[column].highlight or rowdata.highlight or table:GetDefaultHighlight()); end return true; end, ["OnLeave"] = function(rowFrame, cellFrame, data, cols, row, realrow, column, table, ...) if row and realrow then local rowdata = table:GetRow(realrow); local celldata = table:GetCell(rowdata, column); if realrow ~= table.selected or not table.fSelect then table:SetHighLightColor(rowFrame, table:GetDefaultHighlightBlank()); end end return true; end, ["OnClick"] = function(rowFrame, cellFrame, data, cols, row, realrow, column, table, button, ...) -- LS: added "button" argument if button == "LeftButton" then -- LS: only handle on LeftButton click (right passes thru) if not (row or realrow) then for i, col in ipairs(st.cols) do if i ~= column then -- clear out all other sort marks cols[i].sort = nil; end end local sortorder = "asc"; if not cols[column].sort and cols[column].defaultsort then sortorder = cols[column].defaultsort; -- sort by columns default sort first; elseif cols[column].sort and cols[column].sort:lower() == "asc" then sortorder = "dsc"; end cols[column].sort = sortorder; table:SortData(); else if table:GetSelection() == realrow then table:ClearSelection(); else table:SetSelection(realrow); end end return true; end end, }; st.data = {}; f:SetBackdrop(ScrollPaneBackdrop); f:SetBackdropColor(0.1,0.1,0.1); f:SetPoint("CENTER",UIParent,"CENTER",0,0); -- build scroll frame local scrollframe = CreateFrame("ScrollFrame", f:GetName().."ScrollFrame", f, "FauxScrollFrameTemplate"); st.scrollframe = scrollframe; scrollframe:Show(); scrollframe:SetScript("OnHide", function(self, ...) self:Show(); end); scrollframe:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -4); scrollframe:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -26, 3); local scrolltrough = CreateFrame("Frame", f:GetName().."ScrollTrough", scrollframe); scrolltrough:SetWidth(17); scrolltrough:SetPoint("TOPRIGHT", f, "TOPRIGHT", -4, -3); scrolltrough:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -4, 4); scrolltrough.background = scrolltrough:CreateTexture(nil, "BACKGROUND"); scrolltrough.background:SetAllPoints(scrolltrough); scrolltrough.background:SetTexture(0.05, 0.05, 0.05, 1.0); local scrolltroughborder = CreateFrame("Frame", f:GetName().."ScrollTroughBorder", scrollframe); scrolltroughborder:SetWidth(1); scrolltroughborder:SetPoint("TOPRIGHT", scrolltrough, "TOPLEFT"); scrolltroughborder:SetPoint("BOTTOMRIGHT", scrolltrough, "BOTTOMLEFT"); scrolltroughborder.background = scrolltrough:CreateTexture(nil, "BACKGROUND"); scrolltroughborder.background:SetAllPoints(scrolltroughborder); scrolltroughborder.background:SetTexture(0.5, 0.5, 0.5, 1.0); st.Refresh = function(self) FauxScrollFrame_Update(scrollframe, #st.filtered, st.displayRows, st.rowHeight); local o = FauxScrollFrame_GetOffset(scrollframe); st.offset = o; for i = 1, st.displayRows do local row = i + o; if st.rows then local rowFrame = st.rows[i]; local realrow = st.filtered[row]; local rowData = st:GetRow(realrow); local fShow = true; for col = 1, #st.cols do local cellFrame = rowFrame.cols[col]; local fnDoCellUpdate = st.DoCellUpdate; if rowData then st.rows[i]:Show(); local cellData = st:GetCell(rowData, col); if type(cellData) == "table" and cellData.DoCellUpdate then fnDoCellUpdate = cellData.DoCellUpdate; elseif st.cols[col].DoCellUpdate then fnDoCellUpdate = st.cols[col].DoCellUpdate; elseif rowData.DoCellUpdate then fnDoCellUpdate = rowData.DoCellUpdate; end else st.rows[i]:Hide(); fShow = false; end fnDoCellUpdate(rowFrame, cellFrame, st.data, st.cols, row, st.filtered[row], col, fShow, st); end end end end scrollframe:SetScript("OnVerticalScroll", function(self, offset) FauxScrollFrame_OnVerticalScroll(self, offset, st.rowHeight, function() st:Refresh() end); -- LS: putting st:Refresh() in a function call passes the st as the 1st arg which lets you reference the st if you decide to hook the refresh end); st:SetFilter(Filter); st:SetDisplayCols(st.cols); st:RegisterEvents(st.DefaultEvents); return st; end end