-- if you change something here you probably want to change the frame version too
local next = next;
local function SecureNext(elements, key)
-- not totally necessary in all cases in this file (since Interface Options are independent), but
-- it's used anyway to keep things consistent, plus it's not a huge performance hindrance
return securecall(next, elements, key);
end
-- [[ functions for OptionsFrameTemplates controls ]] --
function OptionsList_OnLoad (self, buttonTemplate)
local name = self:GetName();
--Setup random things!
self.scrollFrame = _G[name .. "List"];
self:SetBackdropBorderColor(.6, .6, .6, 1);
_G[name.."Bottom"]:SetVertexColor(.66, .66, .66);
--Create buttons for scrolling
local buttons = {};
local button = CreateFrame("BUTTON", name .. "Button1", self, buttonTemplate or "OptionsListButtonTemplate");
button:SetPoint("TOPLEFT", self, 0, -8);
self.buttonHeight = button:GetHeight();
tinsert(buttons, button);
local maxButtons = (self:GetHeight() - 8) / self.buttonHeight;
for i = 2, maxButtons do
button = CreateFrame("BUTTON", name .. "Button" .. i, self, buttonTemplate or "OptionsListButtonTemplate");
button:SetPoint("TOPLEFT", buttons[#buttons], "BOTTOMLEFT");
tinsert(buttons, button);
end
self.buttonHeight = button:GetHeight();
self.buttons = buttons;
end
function OptionsList_DisplayScrollBar (frame)
local list = frame.scrollFrame;
list:Show();
local listWidth = list:GetWidth();
for _, button in SecureNext, frame.buttons do
button:SetWidth(button:GetWidth() - listWidth);
end
end
function OptionsList_HideScrollBar (frame)
local list = frame.scrollFrame;
list:Hide();
local listWidth = list:GetWidth();
for _, button in SecureNext, frame.buttons do
button:SetWidth(button:GetWidth() + listWidth);
end
end
function OptionsList_HideButton (button)
-- Sparse for now, who knows what will end up here?
button:Hide();
end
function OptionsList_DisplayButton (button, element)
-- Do display things
button:Show();
button.element = element;
if (element.parent) then
button:SetNormalFontObject(GlueFontHighlightSmall);
button:SetHighlightFontObject(GlueFontHighlightSmall);
button.text:SetPoint("LEFT", 16, 2);
else
button:SetNormalFontObject(GlueFontNormal);
button:SetHighlightFontObject(GlueFontHighlight);
button.text:SetPoint("LEFT", 8, 2);
end
button.text:SetText(element.name);
if (element.hasChildren) then
if (element.collapsed) then
button.toggle:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-UP");
button.toggle:SetPushedTexture("Interface\\Buttons\\UI-PlusButton-DOWN");
else
button.toggle:SetNormalTexture("Interface\\Buttons\\UI-MinusButton-UP");
button.toggle:SetPushedTexture("Interface\\Buttons\\UI-MinusButton-DOWN");
end
button.toggle:Show();
else
button.toggle:Hide();
end
end
function OptionsList_DisplayPanel (panel)
local panelContainer = panel:GetParent();
if ( panelContainer.displayedPanel ) then
panelContainer.displayedPanel:Hide();
end
panelContainer.displayedPanel = panel;
panel:ClearAllPoints();
panel:SetPoint("TOPLEFT", panelContainer, "TOPLEFT");
panel:SetPoint("BOTTOMRIGHT", panelContainer, "BOTTOMRIGHT");
panel:Show();
end
function OptionsList_ClearSelection (listFrame, buttons)
for _, button in SecureNext, buttons do
button:UnlockHighlight();
end
listFrame.selection = nil;
end
function OptionsList_SelectButton (listFrame, button)
button:LockHighlight()
listFrame.selection = button.element;
end
function OptionsListScroll_Update (frame)
local parent = frame:GetParent();
parent:update();
end
function OptionsListButton_OnLoad (self, toggleFunc)
self.text = _G[self:GetName() .. "Text"];
self.highlight = self:GetHighlightTexture();
self.highlight:SetVertexColor(.196, .388, .8);
self.text:SetPoint("RIGHT", "$parentToggle", "LEFT", -2, 0);
self:RegisterForClicks("LeftButtonUp", "RightButtonUp");
self.toggleFunc = toggleFunc or OptionsListButton_ToggleSubCategories;
end
function OptionsListButton_OnClick (self, mouseButton)
if ( mouseButton == "RightButton" ) then
if ( self.element.hasChildren ) then
OptionsListButtonToggle_OnClick(self.toggle);
end
return;
end
local listFrame = self:GetParent();
OptionsList_ClearSelection(listFrame, listFrame.buttons);
OptionsList_SelectButton(listFrame, self);
OptionsList_DisplayPanel(self.element);
end
function OptionsListButton_ToggleSubCategories (button)
local element = self.element;
element.collapsed = not element.collapsed;
local collapsed = element.collapsed;
local categoryFrame = self:GetParent();
local optionsFrame = categoryFrame:GetParent();
local categoryList = optionsFrame.categoryList;
for _, category in SecureNext, categoryList do
if ( category.parent == element.name ) then
if ( collapsed ) then
category.hidden = true;
else
category.hidden = false;
end
end
end
categoryFrame:update();
end
function OptionsListButtonToggle_OnClick (self)
local button = self:GetParent();
button:toggleFunc();
end
-- [[ OptionsFrameTemplate ]] --
-- NOTE: the difference between "category" and "panel" is mostly a terminology difference
-- let's say we have a set of options called foo
-- "category" is used when referring to foo as data
-- "panel" is used when referring to the frame that displays foo
function OptionsFrame_OnLoad(self)
self.categoryFrame = _G[self:GetName().."CategoryFrame"];
self.panelContainer = _G[self:GetName().."PanelContainer"];
self.okay = _G[self:GetName().."Okay"];
self.cancel = _G[self:GetName().."Cancel"];
self.apply = _G[self:GetName().."Apply"];
self.default = _G[self:GetName().."Default"];
self.categoryList = { };
end
function OptionsFrame_OnShow (self)
if ( self.lastFrame ) then
self.lastFrame:Hide();
end
--Refresh the category frames and display the first category if nothing is displayed.
self.categoryFrame:update();
if ( not self.panelContainer.displayedPanel ) then
OptionsListButton_OnClick(self.categoryFrame.buttons[1]);
end
--Refresh the categories to pick up changes made while the options frame was hidden.
OptionsFrame_RefreshCategories(self);
end
function OptionsFrame_OnHide (self)
PlaySound("gsTitleOptionExit");
if ( self.lastFrame ) then
self.lastFrame:Show();
self.lastFrame = nil;
end
end
function OptionsFrame_OnKeyDown(self, key)
if ( key == "ESCAPE" ) then
self.cancel:Click();
elseif ( key == "PRINTSCREEN" ) then
Screenshot();
end
end
local function OptionsFrame_RunOkayForCategory (category)
pcall(category.okay, category);
end
local function OptionsFrame_RunCancelForCategory (category)
pcall(category.cancel, category);
end
local function OptionsFrame_RunDefaultForCategory (category)
pcall(category.default, category);
end
local function OptionsFrame_RunRefreshForCategory (category)
pcall(category.refresh, category);
end
function OptionsFrameOkay_OnClick (self, apply)
--Iterate through registered panels and run their okay methods in a taint-safe fashion
for _, category in SecureNext, self.categoryList do
-- But if the apply button was used, category has to support it
if ( not apply or category.hasApply ) then
securecall(OptionsFrame_RunOkayForCategory, category);
end
end
end
function OptionsFrameCancel_OnClick (self)
--Iterate through registered panels and run their cancel methods in a taint-safe fashion
for _, category in SecureNext, self.categoryList do
securecall(OptionsFrame_RunCancelForCategory, category);
end
end
function OptionsFrameDefault_OnClick (self)
-- NOTE: defer setting defaults until a popup dialog button is clicked
end
function OptionsFrame_SetAllToDefaults (self)
--Iterate through registered panels and run their default methods in a taint-safe fashion
for _, category in SecureNext, self.categoryList do
securecall("pcall", category.default, category);
end
--Refresh the categories to pick up changes made.
OptionsFrame_RefreshCategories(self);
end
function OptionsFrame_SetCurrentToDefaults (self)
local displayedPanel = self.panelContainer.displayedPanel;
if ( not displayedPanel or not displayedPanel.default ) then
return;
end
displayedPanel.default(displayedPanel);
--Run the refresh method to refresh any values that were changed.
displayedPanel.refresh(displayedPanel);
end
function OptionsFrame_RefreshCategories (self)
for _, category in SecureNext, self.categoryList do
securecall(OptionsFrame_RunRefreshForCategory, category);
end
end
--Table to reuse! Yay reuse!
local displayedElements = {};
function OptionsCategoryFrame_Update (self)
--Redraw the scroll lists
local categoryList = self:GetParent().categoryList;
local element;
for i, element in SecureNext, displayedElements do
displayedElements[i] = nil;
end
for i, element in SecureNext, categoryList do
if ( not element.hidden ) then
tinsert(displayedElements, element);
end
end
local scrollFrame = self.scrollFrame;
local buttons = self.buttons;
local numButtons = #buttons;
local numCategories = #displayedElements;
if ( numCategories > numButtons and ( not scrollFrame:IsShown() ) ) then
OptionsList_DisplayScrollBar(self);
elseif ( numCategories <= numButtons and ( scrollFrame:IsShown() ) ) then
OptionsList_HideScrollBar(self);
end
FauxScrollFrame_Update(scrollFrame, numCategories, numButtons, buttons[1]:GetHeight());
local selection = self.selection;
if ( selection ) then
-- Store the currently selected element and clear all the buttons, we're redrawing.
OptionsList_ClearSelection(self, self.buttons);
end
local offset = FauxScrollFrame_GetOffset(scrollFrame);
for i = 1, numButtons do
element = displayedElements[i + offset];
if ( not element ) then
OptionsList_HideButton(buttons[i]);
else
OptionsList_DisplayButton(buttons[i], element);
if ( selection and selection == element and not self.selection ) then
OptionsList_SelectButton(self, buttons[i]);
end
end
end
if ( selection ) then
-- If there was a selected element before we cleared the button highlights, restore it, 'cause we're done.
-- Note: This theoretically might already have been done by OptionsList_SelectButton, but in the event that the selected button hasn't been drawn, this is still necessary.
self.selection = selection;
end
end
function OptionsFrame_OpenToCategory (self, panel)
local panelName;
if ( type(panel) == "string" ) then
panelName = panel;
panel = nil;
end
if ( not panelName or panel ) then
return;
end
local categoryList = self.categoryList;
local elementToDisplay;
for i, element in SecureNext, categoryList do
if ( element == panel or (panelName and element.name and element.name == panelName) ) then
elementToDisplay = element;
break;
end
end
if ( not elementToDisplay ) then
return;
end
local buttons = self.categoryFrame.buttons
for i, button in SecureNext, buttons do
if ( button.element == elementToDisplay ) then
button:Click();
elseif ( elementToDisplay.parent and button.element and (button.element.name == elementToDisplay.parent and button.element.collapsed) ) then
button.toggle:Click();
end
end
if ( not self:IsShown() ) then
self:Show();
end
end
function OptionsFrame_AddCategory (self, panel)
if ( not issecure() ) then
-- disallow any non-blizzard code to enter here...
-- we may want to change this in the future if we merge this with Interface Options
end
local parent = panel.parent;
if ( parent ) then
for i = 1, #self.categoryList do
if ( self.categoryList[i].name == parent ) then
if ( self.categoryList[i].hasChildren ) then
panel.hidden = self.categoryList[i].collapsed;
else
panel.hidden = true;
self.categoryList[i].hasChildren = true;
self.categoryList[i].collapsed = true;
end
tinsert(self.categoryList, i + 1, panel);
self.categoryFrame:update();
return;
end
end
end
tinsert(self.categoryList, panel);
self.categoryFrame:update();
end