-------------------------------------------------------------------------------
-- Title: MSBT Options Popups
-- Author: Mik
-------------------------------------------------------------------------------

-- Create module and set its name.
local module = {};
local moduleName = "Popups";
MSBTOptions[moduleName] = module;


-------------------------------------------------------------------------------
-- Imports.
-------------------------------------------------------------------------------

-- Local references to various modules for faster access.
local MSBTControls = MSBTOptions.Controls;
local MSBTProfiles = MikSBT.Profiles;
local MSBTAnimations = MikSBT.Animations;
local MSBTTriggers = MikSBT.Triggers;
local MSBTParser = MikSBT.Parser;
local MSBTMain = MikSBT.Main;
local MSBTMedia = MikSBT.Media;
local L = MikSBT.translations;

-- Local references to various functions for faster access.
local EraseTable = MikSBT.EraseTable;
local ConvertType = MSBTTriggers.ConvertType;

-- Local references to various variables for faster access.
local fonts = MSBTMedia.fonts;
local sounds = MSBTMedia.sounds;


-------------------------------------------------------------------------------
-- Private constants.
-------------------------------------------------------------------------------

local OUTLINE_MAP = {"", "OUTLINE", "THICKOUTLINE"};
local DEFAULT_TEXT_ALIGN_INDEX = 2;
local DEFAULT_SCROLL_HEIGHT = 260;
local DEFAULT_SCROLL_WIDTH = 40;
local DEFAULT_ANIMATION_STYLE = "Straight";
local DEFAULT_STICKY_ANIMATION_STYLE = "Pow";
local DEFAULT_SOUND_PATH = "Interface\\AddOns\\MikScrollingBattleText\\Sounds\\";

local FLAG_YOU = 0xF0000000;

-------------------------------------------------------------------------------
-- Private variables.
-------------------------------------------------------------------------------

local popupFrames = {};

-- Backdrop to reuse for the popup frames.
local popupBackdrop = {
  bgFile = "Interface\\Addons\\MSBTOptions\\Artwork\\PlainBackdrop",
  edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
  insets = {left = 6, right = 6, top = 6, bottom = 6},
};

-- Backdrop to reuse for the scroll area mover frame.
local moverBackdrop = {
  bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
};

-- Reusable table for return settings.
local returnSettings = {};

-- Reusable table to configure popup frames.
local tempConfig = {};


-------------------------------------------------------------------------------
-- Utility functions.
-------------------------------------------------------------------------------

-- ****************************************************************************
-- Returns an iterator for the passed table sorted by its keys.
-- ****************************************************************************
local function PairsByKeys(t)
 local temp = {};
 for k in pairs(t) do temp[#temp+1] = k; end
 table.sort(temp);

 local position = 0;
 local iterator = function ()
  position = position + 1
  if temp[position] == nil then
   return nil;
  else
   return temp[position], t[temp[position]]
  end
 end
 return iterator
end


-- ****************************************************************************
-- Called when a popup is hidden.
-- ****************************************************************************
local function OnHidePopup(this)
 if (this.hideHandler) then this.hideHandler(); end
end


-- ****************************************************************************
-- Creates a new generic popup.
-- ****************************************************************************
local function CreatePopup()
 local frame = CreateFrame("Frame", nil, UIParent);
 frame:Hide();
 frame:SetMovable(true);
 frame:EnableMouse(true);
 frame:SetToplevel(true);
 frame:SetClampedToScreen(true);
 frame:SetBackdrop(popupBackdrop);
 frame:SetScript("OnHide", OnHidePopup);

 -- Title region.
 local titleRegion = frame:CreateTitleRegion();
 titleRegion:SetAllPoints(frame);

 -- Register the frame with the main module.
 MSBTOptions.Main.RegisterPopupFrame(frame);
 return frame;
end


-- ****************************************************************************
-- Changes the passed popup frame's parent.
-- ****************************************************************************
local function ChangePopupParent(frame, parent)
 -- Changing the parent can cause the frame to be hidden, so ensure the hide
 -- handler isn't called.
 local oldHandler = frame.hideHandler;
 frame.hideHandler = nil;
 frame:SetParent(parent or UIParent);
 frame.hideHandler = oldHandler;
end


-- ****************************************************************************
-- Disables the controls in the passed table.
-- ****************************************************************************
local function DisableControls(controlsTable)
 for _, frame in pairs(controlsTable) do
  if (frame.Disable) then frame:Disable(); end
 end
end


-- ****************************************************************************
-- Toggles the state of a font dropdown control when an inherit checkbox is
-- changed.
-- ****************************************************************************
local function ToggleDropdownInheritState(dropdown, isInherited, inheritedValue)
 if (isInherited) then
  dropdown:SetSelectedID(inheritedValue);
  dropdown:Disable();
  dropdown:SetAlpha(0.3);
 else
  dropdown:SetAlpha(1);
  dropdown:Enable();
 end
end


-- ****************************************************************************
-- Toggles the state of a font slider control when an inherit checkbox is
-- changed.
-- ****************************************************************************
local function ToggleSliderInheritState(slider, isInherited, inheritedValue)
 if (isInherited) then
  slider:SetValue(inheritedValue);
  slider:Disable();
  slider:SetAlpha(0.3);
 else
  slider:SetAlpha(1);
  slider:Enable();
 end
end




-------------------------------------------------------------------------------
-- Input frame functions.
-------------------------------------------------------------------------------

-- ****************************************************************************
-- Called when the text in the input frame editbox changes to allow validation.
-- ****************************************************************************
local function ValidateInput(this)
 local frame = popupFrames.inputFrame;
 
 -- Clear validation message and enable okay button.
 frame.validateFontString:SetText("");
 frame.okayButton:Enable();

 if (frame.validateHandler) then
  local message = frame.validateHandler(this:GetText());

  -- Disable the save button and display the validation message if validation failed.
  if (message) then
   frame.validateFontString:SetText(message);
   frame.okayButton:Disable();
  end
 end 
end


-- ****************************************************************************
-- Calls the save handler with the entered input.
-- ****************************************************************************
local function SaveInput()
 local frame = popupFrames.inputFrame;
 if (frame.saveHandler and frame.okayButton:IsEnabled() ~= 0) then
  EraseTable(returnSettings);
  returnSettings.inputText = frame.inputEditbox:GetText();
  returnSettings.secondInputText = frame.secondInputEditbox:GetText();
  returnSettings.saveArg1 = frame.saveArg1;
  frame.saveHandler(returnSettings);
  frame:Hide();
 end
end


-- ****************************************************************************
-- Creates the popup input frame.
-- ****************************************************************************
local function CreateInput()
 local frame = CreatePopup();
 frame:SetWidth(350);
 frame:SetHeight(130);

 -- Input editbox.
 local editbox = MSBTControls.CreateEditbox(frame);
 editbox:SetPoint("TOPLEFT", frame, "TOPLEFT", 20, -25);
 editbox:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -20, -25);
 editbox:SetEscapeHandler(
  function (this)
   frame:Hide();
  end
 );
 editbox:SetEnterHandler(SaveInput);
 editbox:SetTextChangedHandler(ValidateInput);
 frame.inputEditbox = editbox;
 
 -- Second input editbox.
 editbox = MSBTControls.CreateEditbox(frame);
 editbox:SetPoint("TOPLEFT", frame.inputEditbox, "BOTTOMLEFT", 0, -10);
 editbox:SetPoint("TOPRIGHT", frame.inputEditbox, "BOTTOMRIGHT", 0, -10);
 editbox:SetEscapeHandler(
  function (this)
   frame:Hide();
  end
 );
 editbox:SetEnterHandler(SaveInput);
 frame.secondInputEditbox = editbox;
 
 
 -- Okay button.
 local button = MSBTControls.CreateOptionButton(frame);
 local objLocale = L.BUTTONS["inputOkay"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -10, 40);
 button:SetClickHandler(SaveInput);
 frame.okayButton = button;

 -- Cancel button.
 button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["inputCancel"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("BOTTOMLEFT", frame, "BOTTOM", 10, 40);
 button:SetClickHandler(
  function (this)
   frame:Hide();
  end
 );
 
 -- Validation text.
 local fontString = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 30, 20);
 fontString:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -30, 20);
 frame.validateFontString = fontString;

 return frame;
end


-- ****************************************************************************
-- Shows the popup input frame using the passed config.
-- ****************************************************************************
local function ShowInput(configTable)
 -- Don't do anything if required parameters weren't passed.
 if (not configTable or not configTable.anchorFrame or not configTable.parentFrame) then return; end
 
 -- Create the frame if it hasn't already been.
 if (not popupFrames.inputFrame) then popupFrames.inputFrame = CreateInput(); end

 -- Set parent.
 local frame = popupFrames.inputFrame;
 ChangePopupParent(frame, configTable.parentFrame)
 
 -- Populate.
 local editbox = frame.inputEditbox
 editbox:SetLabel(configTable.editboxLabel);
 editbox:SetTooltip(configTable.editboxTooltip);
 editbox:SetText("");
 editbox:SetText(configTable.defaultText);

 editbox = frame.secondInputEditbox;
 if (configTable.showSecondEditbox) then
  editbox:Show();
  editbox:SetLabel(configTable.secondEditboxLabel);
  editbox:SetTooltip(configTable.secondEditboxTooltip);
  editbox:SetText(configTable.secondDefaultText);
  frame:SetHeight(170);
 else
  editbox:SetText(nil);
  editbox:Hide();
  frame:SetHeight(130);
 end
 

 -- Configure the frame. 
 frame.showSecondEditbox = configTable.showSecondEditbox;
 frame.validateHandler = configTable.validateHandler;
 frame.saveHandler = configTable.saveHandler;
 frame.saveArg1 = configTable.saveArg1;
 frame.hideHandler = configTable.hideHandler;
 frame:ClearAllPoints();
 frame:SetPoint(configTable.anchorPoint or "TOPLEFT", configTable.anchorFrame, configTable.relativePoint or "BOTTOMLEFT");
 frame:Show();
 frame:Raise();
 frame.inputEditbox:SetFocus();
end


-------------------------------------------------------------------------------
-- Acknowledge frame functions.
-------------------------------------------------------------------------------

-- ****************************************************************************
-- Creates the popup acknowledge frame.
-- ****************************************************************************
local function CreateAcknowledge()
 local frame = CreatePopup();
 frame:SetWidth(350);
 frame:SetHeight(90);
 
 -- Acknowledge text.
 local fontString = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("TOPLEFT", frame, "TOPLEFT", 30, -20);
 fontString:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -30, -20);
 fontString:SetText(L.MSG_ACKNOWLEDGE_TEXT);

 -- Yes button.
 local button = MSBTControls.CreateOptionButton(frame);
 button:Configure(20, YES, nil);
 button:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -10, 15);
 button:SetClickHandler(
  function (this)
   if (frame.acknowledgeHandler) then
    frame.acknowledgeHandler(frame.saveArg1);
    frame:Hide();
   end
  end
 );

 -- No button.
 button = MSBTControls.CreateOptionButton(frame);
 button:Configure(20, NO, nil);
 button:SetPoint("BOTTOMLEFT", frame, "BOTTOM", 10, 15);
 button:SetClickHandler(
  function (this)
   frame:Hide();
  end
 );
 
 return frame;
end


-- ****************************************************************************
-- Shows the popup acknowledge frame using the passed config.
-- ****************************************************************************
local function ShowAcknowledge(configTable)
 -- Don't do anything if required parameters weren't passed.
 if (not configTable or not configTable.anchorFrame or not configTable.parentFrame) then return; end
 
 -- Create the frame if it hasn't already been.
 if (not popupFrames.acknowledgeFrame) then popupFrames.acknowledgeFrame = CreateAcknowledge(); end


 -- Set parent. 
 local frame = popupFrames.acknowledgeFrame;
 ChangePopupParent(frame, configTable.parentFrame)
 
 -- Configure the frame.
 frame.acknowledgeHandler = configTable.acknowledgeHandler;
 frame.saveArg1 = configTable.saveArg1;
 frame.hideHandler = configTable.hideHandler;
 frame:ClearAllPoints();
 frame:SetPoint(configTable.anchorPoint or "TOPLEFT", configTable.anchorFrame, configTable.relativePoint or "BOTTOMLEFT");
 frame:Show();
 frame:Raise();
end


-------------------------------------------------------------------------------
-- Font frame functions.
-------------------------------------------------------------------------------

-- ****************************************************************************
-- Updates the return settings table with the selected font values.
-- ****************************************************************************
local function UpdateFontSettings()
 local frame = popupFrames.fontFrame;

 EraseTable(returnSettings);
 
 if (not frame.hideNormal) then
  returnSettings.normalFontName = not frame.normalFontCheckbox:GetChecked() and frame.normalFontDropdown:GetSelectedID() or nil;
  returnSettings.normalOutlineIndex = not frame.normalOutlineCheckbox:GetChecked() and frame.normalOutlineDropdown:GetSelectedID() or nil;
  returnSettings.normalFontSize = not frame.normalFontSizeCheckbox:GetChecked() and frame.normalFontSizeSlider:GetValue() or nil;
  returnSettings.normalFontAlpha = not frame.normalFontOpacityCheckbox:GetChecked() and frame.normalFontOpacitySlider:GetValue() or nil;
 end
 
 if (not frame.hideCrit) then
  returnSettings.critFontName = not frame.critFontCheckbox:GetChecked() and frame.critFontDropdown:GetSelectedID() or nil;
  returnSettings.critOutlineIndex = not frame.critOutlineCheckbox:GetChecked() and frame.critOutlineDropdown:GetSelectedID() or nil;
  returnSettings.critFontSize = not frame.critFontSizeCheckbox:GetChecked() and frame.critFontSizeSlider:GetValue() or nil;
  returnSettings.critFontAlpha = not frame.critFontOpacityCheckbox:GetChecked() and frame.critFontOpacitySlider:GetValue() or nil;
 end
end


-- ****************************************************************************
-- Updates the normal and crit font previews.
-- ****************************************************************************
local function UpdateFontPreviews()
 local frame = popupFrames.fontFrame;

 local fontPath, fontSize, outline;

 if (not frame.hideNormal) then 
  fontPath = fonts[frame.normalFontDropdown:GetSelectedID()];
  fontSize = frame.normalFontSizeSlider:GetValue();
  outline = OUTLINE_MAP[frame.normalOutlineDropdown:GetSelectedID()];
  frame.normalPreviewFontString:SetFont(fontPath, fontSize, outline);
  frame.normalPreviewFontString:SetAlpha(frame.normalFontOpacitySlider:GetValue() / 100);
 end
 
 if (not frame.hideCrit) then
  fontPath = fonts[frame.critFontDropdown:GetSelectedID()];
  fontSize = frame.critFontSizeSlider:GetValue();
  outline = OUTLINE_MAP[frame.critOutlineDropdown:GetSelectedID()];
  if (fontPath and outline) then frame.critPreviewFontString:SetFont(fontPath, fontSize, outline); end
  frame.critPreviewFontString:SetAlpha(frame.critFontOpacitySlider:GetValue() / 100);
 end
end


-- ****************************************************************************
-- Creates the popup font frame.
-- ****************************************************************************
local function CreateFontPopup()
 local frame = CreatePopup();
 frame:SetWidth(450);
 frame:SetHeight(380);

 -- Title text.
 local fontString = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("TOP", frame, "TOP", 0, -20);
 frame.titleFontString = fontString;

 
 -- Normal container frame.
 local normalFrame = CreateFrame("Frame", nil, frame);
 normalFrame:SetWidth(195);
 normalFrame:SetPoint("TOPLEFT", frame, "TOPLEFT", 20, -60);
 normalFrame:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 20, 40);
 frame.normalFrame = normalFrame;


 -- Normal controls container frame.
 local normalControlsFrame = CreateFrame("Frame", nil, normalFrame);
 normalControlsFrame:SetWidth(155);
 normalControlsFrame:SetPoint("TOPLEFT");
 normalControlsFrame:SetPoint("BOTTOMLEFT");
 frame.normalControlsFrame = normalControlsFrame;

 -- Normal font dropdown.
 local dropdown =  MSBTControls.CreateDropdown(normalControlsFrame);
 objLocale = L.DROPDOWNS["normalFont"];
 dropdown:Configure(150, objLocale.label, objLocale.tooltip);
 dropdown:SetListboxHeight(200);
 dropdown:SetPoint("TOPLEFT");
 dropdown:SetChangeHandler(
  function (this, id)
     UpdateFontPreviews();
  end
 );
 frame.normalFontDropdown = dropdown;

 -- Normal outline dropdown.
 dropdown =  MSBTControls.CreateDropdown(normalControlsFrame);
 objLocale = L.DROPDOWNS["normalOutline"];
 dropdown:Configure(150, objLocale.label, objLocale.tooltip);
 dropdown:SetPoint("TOPLEFT", frame.normalFontDropdown, "BOTTOMLEFT", 0, -20);
 dropdown:SetChangeHandler(
  function (this, id)
     UpdateFontPreviews();
  end
 );
 for outlineIndex, outlineName in ipairs(L.OUTLINES) do
  dropdown:AddItem(outlineName, outlineIndex);
 end
 frame.normalOutlineDropdown = dropdown;

 -- Normal font size slider.
 local slider = MSBTControls.CreateSlider(normalControlsFrame);
 objLocale = L.SLIDERS["normalFontSize"]; 
 slider:Configure(150, objLocale.label, objLocale.tooltip);
 slider:SetPoint("TOPLEFT", frame.normalOutlineDropdown, "BOTTOMLEFT", 0, -30);
 slider:SetMinMaxValues(4, 38);
 slider:SetValueStep(1);
 slider:SetValueChangedHandler(
  function(this, value)
    UpdateFontPreviews();
  end
 );
 frame.normalFontSizeSlider = slider;

 -- Normal font opacity slider.
 slider = MSBTControls.CreateSlider(normalControlsFrame);
 objLocale = L.SLIDERS["normalFontOpacity"]; 
 slider:Configure(150, objLocale.label, objLocale.tooltip);
 slider:SetPoint("TOPLEFT", frame.normalFontSizeSlider, "BOTTOMLEFT", 0, -10);
 slider:SetMinMaxValues(1, 100);
 slider:SetValueStep(1);
 slider:SetValueChangedHandler(
  function(this, value)
    UpdateFontPreviews();
  end
 );
 frame.normalFontOpacitySlider = slider;

 -- Normal preview.
 fontString = normalControlsFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("BOTTOM", normalControlsFrame, "BOTTOM", 0, 10);
 fontString:SetText(L.MSG_NORMAL_PREVIEW_TEXT);
 frame.normalPreviewFontString = fontString;



 -- Normal inherit container frame. 
 local normalInheritFrame = CreateFrame("Frame", nil, normalFrame);
 normalInheritFrame:SetWidth(40);
 normalInheritFrame:SetPoint("TOPLEFT", normalControlsFrame, "TOPRIGHT");
 normalInheritFrame:SetPoint("BOTTOMLEFT", normalControlsFrame, "BOTTOMRIGHT");
 frame.normalInheritFrame = normalInheritFrame;
 
 -- Inherit normal font name checkbox.
 local checkbox = MSBTControls.CreateCheckbox(normalInheritFrame);
 objLocale = L.CHECKBOXES["inheritField"]; 
 checkbox:Configure(20, nil, objLocale.tooltip);
 checkbox:SetPoint("BOTTOMLEFT", frame.normalFontDropdown, "BOTTOMRIGHT", 10, 0);
 checkbox:SetClickHandler(
  function (this, isChecked)
   ToggleDropdownInheritState(frame.normalFontDropdown, isChecked, frame.inheritedNormalFontName);
   UpdateFontPreviews(); 
  end
 );
 frame.normalFontCheckbox = checkbox; 

 -- Inherit normal outline index checkbox.
 checkbox = MSBTControls.CreateCheckbox(normalInheritFrame);
 checkbox:Configure(20, nil, objLocale.tooltip);
 checkbox:SetPoint("BOTTOMLEFT", frame.normalOutlineDropdown, "BOTTOMRIGHT", 10, 0);
 checkbox:SetClickHandler(
  function (this, isChecked)
   ToggleDropdownInheritState(frame.normalOutlineDropdown, isChecked, frame.inheritedNormalOutlineIndex);
   UpdateFontPreviews(); 
  end
 );
 frame.normalOutlineCheckbox = checkbox; 

 -- Inherit normal font size checkbox.
 checkbox = MSBTControls.CreateCheckbox(normalInheritFrame);
 checkbox:Configure(20, nil, objLocale.tooltip);
 checkbox:SetPoint("BOTTOMLEFT", frame.normalFontSizeSlider, "BOTTOMRIGHT", 10, 5);
 checkbox:SetClickHandler(
  function (this, isChecked)
   ToggleSliderInheritState(frame.normalFontSizeSlider, isChecked, frame.inheritedNormalFontSize);
   UpdateFontPreviews(); 
  end
 );
 frame.normalFontSizeCheckbox = checkbox; 

 -- Inherit normal font opacity checkbox.
 checkbox = MSBTControls.CreateCheckbox(normalInheritFrame);
 checkbox:Configure(20, nil, objLocale.tooltip);
 checkbox:SetPoint("BOTTOMLEFT", frame.normalFontOpacitySlider, "BOTTOMRIGHT", 10, 5);
 checkbox:SetClickHandler(
  function (this, isChecked)
   ToggleSliderInheritState(frame.normalFontOpacitySlider, isChecked, frame.inheritedNormalFontAlpha);
   UpdateFontPreviews(); 
  end
 );
 frame.normalFontOpacityCheckbox = checkbox; 

 -- Inherit normal column label.
 fontString = normalInheritFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("BOTTOM", frame.normalFontCheckbox, "TOP", 0, 7);
 fontString:SetText(L.CHECKBOXES["inheritField"].label);

 
 

 -- Crit container frame.
 local critFrame = CreateFrame("Frame", nil, frame);
 critFrame:SetWidth(195);
 critFrame:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -20, -60);
 critFrame:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -20, 40);
 frame.critFrame = critFrame;

 
 -- Crit controls container frame.
 local critControlsFrame = CreateFrame("Frame", nil, critFrame);
 critControlsFrame:SetWidth(155);
 critControlsFrame:SetPoint("TOPLEFT");
 critControlsFrame:SetPoint("BOTTOMLEFT");
 frame.critControlsFrame = critControlsFrame;

 -- Crit font dropdown.
 dropdown =  MSBTControls.CreateDropdown(critControlsFrame);
 objLocale = L.DROPDOWNS["critFont"];
 dropdown:Configure(150, objLocale.label, objLocale.tooltip);
 dropdown:SetListboxHeight(200);
 dropdown:SetPoint("TOPLEFT");
 dropdown:SetChangeHandler(
  function (this, id)
     UpdateFontPreviews();
  end
 );
 frame.critFontDropdown = dropdown;
 
 -- Crit outline dropdown.
 dropdown =  MSBTControls.CreateDropdown(critControlsFrame);
 objLocale = L.DROPDOWNS["critOutline"];
 dropdown:Configure(150, objLocale.label, objLocale.tooltip);
 dropdown:SetPoint("TOPLEFT", frame.critFontDropdown, "BOTTOMLEFT", 0, -20);
 dropdown:SetChangeHandler(
  function (this, id)
     UpdateFontPreviews();
  end
 );
 for outlineIndex, outlineName in ipairs(L.OUTLINES) do
  dropdown:AddItem(outlineName, outlineIndex);
 end
 frame.critOutlineDropdown = dropdown;

 -- Crit font size slider.
 slider = MSBTControls.CreateSlider(critControlsFrame);
 objLocale = L.SLIDERS["critFontSize"]; 
 slider:Configure(150, objLocale.label, objLocale.tooltip);
 slider:SetPoint("TOPLEFT", frame.critOutlineDropdown, "BOTTOMLEFT", 0, -30);
 slider:SetMinMaxValues(4, 38);
 slider:SetValueStep(1);
 slider:SetValueChangedHandler(
  function(this, value)
    UpdateFontPreviews();
  end
 );
 frame.critFontSizeSlider = slider;

 -- Crit font opacity slider.
 slider = MSBTControls.CreateSlider(critControlsFrame);
 objLocale = L.SLIDERS["critFontOpacity"]; 
 slider:Configure(150, objLocale.label, objLocale.tooltip);
 slider:SetPoint("TOPLEFT", frame.critFontSizeSlider, "BOTTOMLEFT", 0, -10);
 slider:SetMinMaxValues(1, 100);
 slider:SetValueStep(1);
 slider:SetValueChangedHandler(
  function(this, value)
   UpdateFontPreviews();
  end
 );
 frame.critFontOpacitySlider = slider;

 -- Crit Preview. 
 fontString = critControlsFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("BOTTOM", critControlsFrame, "BOTTOM", 0, 10);
 fontString:SetText(L.MSG_CRIT);
 frame.critPreviewFontString = fontString;



 -- Crit inherit container frame. 
 local critInheritFrame = CreateFrame("Frame", nil, critFrame);
 critInheritFrame:SetWidth(40);
 critInheritFrame:SetPoint("TOPLEFT", critControlsFrame, "TOPRIGHT");
 critInheritFrame:SetPoint("BOTTOMLEFT", critControlsFrame, "BOTTOMRIGHT");
 frame.critInheritFrame = critInheritFrame;


 -- Inherit crit font name checkbox.
 local checkbox = MSBTControls.CreateCheckbox(critInheritFrame);
 objLocale = L.CHECKBOXES["inheritField"]; 
 checkbox:Configure(20, nil, objLocale.tooltip);
 checkbox:SetPoint("BOTTOMLEFT", frame.critFontDropdown, "BOTTOMRIGHT", 10, 0);
 checkbox:SetClickHandler(
  function (this, isChecked)
   ToggleDropdownInheritState(frame.critFontDropdown, isChecked, frame.inheritedCritFontName);
   UpdateFontPreviews(); 
  end
 );
 frame.critFontCheckbox = checkbox; 

 -- Inherit crit outline index checkbox.
 checkbox = MSBTControls.CreateCheckbox(critInheritFrame);
 checkbox:Configure(20, nil, objLocale.tooltip);
 checkbox:SetPoint("BOTTOMLEFT", frame.critOutlineDropdown, "BOTTOMRIGHT", 10, 0);
 checkbox:SetClickHandler(
  function (this, isChecked)
   ToggleDropdownInheritState(frame.critOutlineDropdown, isChecked, frame.inheritedCritOutlineIndex);
   UpdateFontPreviews(); 
  end
 );
 frame.critOutlineCheckbox = checkbox; 

 -- Inherit crit font size checkbox.
 checkbox = MSBTControls.CreateCheckbox(critInheritFrame);
 checkbox:Configure(20, nil, objLocale.tooltip);
 checkbox:SetPoint("BOTTOMLEFT", frame.critFontSizeSlider, "BOTTOMRIGHT", 10, 5);
 checkbox:SetClickHandler(
  function (this, isChecked)
   ToggleSliderInheritState(frame.critFontSizeSlider, isChecked, frame.inheritedCritFontSize);
   UpdateFontPreviews(); 
  end
 );
 frame.critFontSizeCheckbox = checkbox; 

 -- Inherit crit font opacity checkbox.
 checkbox = MSBTControls.CreateCheckbox(critInheritFrame);
 checkbox:Configure(20, nil, objLocale.tooltip);
 checkbox:SetPoint("BOTTOMLEFT", frame.critFontOpacitySlider, "BOTTOMRIGHT", 10, 5);
 checkbox:SetClickHandler(
  function (this, isChecked)
   ToggleSliderInheritState(frame.critFontOpacitySlider, isChecked, frame.inheritedCritFontAlpha);
   UpdateFontPreviews(); 
  end
 );
 frame.critFontOpacityCheckbox = checkbox; 

 -- Inherit normal column label.
 fontString = critInheritFrame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("BOTTOM", frame.critFontCheckbox, "TOP", 0, 7);
 fontString:SetText(L.CHECKBOXES["inheritField"].label);

 -- Save button.
 local button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["genericSave"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -10, 20);
 button:SetClickHandler(
  function (this)
   UpdateFontSettings();
   if (frame.saveHandler) then frame.saveHandler(returnSettings, frame.saveArg1); end
   frame:Hide();
  end
 );

 -- Cancel button.
 button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["genericCancel"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("BOTTOMLEFT", frame, "BOTTOM", 10, 20);
 button:SetClickHandler(
  function (this)
   frame:Hide();
  end
 );
 

 -- Register the frame with the main module.
 MSBTOptions.Main.RegisterPopupFrame(frame);
 return frame;
end


-- ****************************************************************************
-- Shows the popup font frame using the passed config.
-- ****************************************************************************
local function ShowFont(configTable)
 -- Don't do anything if required parameters weren't passed.
 if (not configTable or not configTable.anchorFrame or not configTable.parentFrame) then return; end
 
 -- Create the frame if it hasn't already been.
 if (not popupFrames.fontFrame) then popupFrames.fontFrame = CreateFontPopup(); end

 -- Set parent.
 local frame = popupFrames.fontFrame; 
 ChangePopupParent(frame, configTable.parentFrame)
 
 -- Show / Hide appropriate controls.
 if (configTable.hideNormal) then frame.normalFrame:Hide(); else frame.normalFrame:Show(); end
 if (configTable.hideCrit) then frame.critFrame:Hide(); else frame.critFrame:Show(); end
 if (configTable.hideInherit) then frame.normalInheritFrame:Hide(); else frame.normalInheritFrame:Show(); end
 if (configTable.hideInherit) then frame.critInheritFrame:Hide(); else frame.critInheritFrame:Show(); end
 frame.hideNormal = configTable.hideNormal;
 frame.hideCrit = configTable.hideCrit;


 -- Populate data.
 local dropdown, checkbox, slider;
 frame.titleFontString:SetText(configTable.title); 

 if (not configTable.hideNormal) then 
  -- Normal font name.
  dropdown = frame.normalFontDropdown;
  dropdown:Clear();
  for fontName in pairs(fonts) do
   dropdown:AddItem(fontName, fontName);
  end
  dropdown:Sort();
  checkbox = frame.normalFontCheckbox;
  checkbox:SetChecked(not configTable.normalFontName or false);
  if (configTable.normalFontName) then dropdown:SetSelectedID(configTable.normalFontName); end
  ToggleDropdownInheritState(dropdown, checkbox:GetChecked(), configTable.inheritedNormalFontName);

  -- Normal outline index.
  dropdown = frame.normalOutlineDropdown;
  checkbox = frame.normalOutlineCheckbox;
  checkbox:SetChecked(not configTable.normalOutlineIndex or false);
  if (configTable.normalOutlineIndex) then dropdown:SetSelectedID(configTable.normalOutlineIndex); end
  ToggleDropdownInheritState(dropdown, checkbox:GetChecked(), configTable.inheritedNormalOutlineIndex);

  -- Normal font size. 
  slider = frame.normalFontSizeSlider;
  checkbox = frame.normalFontSizeCheckbox;
  checkbox:SetChecked(not configTable.normalFontSize or false);
  if (configTable.normalFontSize) then slider:SetValue(configTable.normalFontSize); end
  ToggleSliderInheritState(slider, checkbox:GetChecked(), configTable.inheritedNormalFontSize);

  -- Normal font opacity. 
  slider = frame.normalFontOpacitySlider;
  checkbox = frame.normalFontOpacityCheckbox;
  checkbox:SetChecked(not configTable.normalFontAlpha or false);
  if (configTable.normalFontAlpha) then slider:SetValue(configTable.normalFontAlpha); end
  ToggleSliderInheritState(slider, checkbox:GetChecked(), configTable.inheritedNormalFontAlpha);
 end


 if (not configTable.hideCrit) then
  -- Crit font name.
  dropdown = frame.critFontDropdown;
  dropdown:Clear();
  for fontName in pairs(fonts) do
   dropdown:AddItem(fontName, fontName);
  end
  dropdown:Sort();
  checkbox = frame.critFontCheckbox;
  checkbox:SetChecked(not configTable.critFontName or false);
  if (configTable.critFontName) then dropdown:SetSelectedID(configTable.critFontName); end
  ToggleDropdownInheritState(dropdown, checkbox:GetChecked(), configTable.inheritedCritFontName);

  -- Crit outline index.
  dropdown = frame.critOutlineDropdown;
  checkbox = frame.critOutlineCheckbox;
  checkbox:SetChecked(not configTable.critOutlineIndex or false);
  if (configTable.critOutlineIndex) then dropdown:SetSelectedID(configTable.critOutlineIndex); end
  ToggleDropdownInheritState(dropdown, checkbox:GetChecked(), configTable.inheritedCritOutlineIndex);

  -- Crit font size. 
  slider = frame.critFontSizeSlider;
  checkbox = frame.critFontSizeCheckbox;
  checkbox:SetChecked(not configTable.critFontSize or false);
  if (configTable.critFontSize) then slider:SetValue(configTable.critFontSize); end
  ToggleSliderInheritState(slider, checkbox:GetChecked(), configTable.inheritedCritFontSize);

   -- Crit font opacity. 
  slider = frame.critFontOpacitySlider;
  checkbox = frame.critFontOpacityCheckbox;
  checkbox:SetChecked(not configTable.critFontAlpha or false);
  if (configTable.critFontAlpha) then slider:SetValue(configTable.critFontAlpha); end
  ToggleSliderInheritState(slider, checkbox:GetChecked(), configTable.inheritedCritFontAlpha);
 end 


 -- Store inherited settings. 
 frame.inheritedNormalFontName = configTable.inheritedNormalFontName;
 frame.inheritedNormalOutlineIndex = configTable.inheritedNormalOutlineIndex;
 frame.inheritedNormalFontSize = configTable.inheritedNormalFontSize;
 frame.inheritedNormalFontAlpha = configTable.inheritedNormalFontAlpha;
 frame.inheritedCritFontName = configTable.inheritedCritFontName;
 frame.inheritedCritOutlineIndex = configTable.inheritedCritOutlineIndex;
 frame.inheritedCritFontSize = configTable.inheritedCritFontSize;
 frame.inheritedCritFontAlpha = configTable.inheritedCritFontAlpha;
 
 
 -- Configure the frame. 
 frame.saveHandler = configTable.saveHandler;
 frame.saveArg1 = configTable.saveArg1;
 frame.hideHandler = configTable.hideHandler;
 frame:ClearAllPoints();
 frame:SetPoint(configTable.anchorPoint or "TOPLEFT", configTable.anchorFrame, configTable.relativePoint or "BOTTOMLEFT");
 frame:Show();
 frame:Raise(); 

 UpdateFontPreviews(); 
end


-------------------------------------------------------------------------------
-- Partial effects frame functions.
-------------------------------------------------------------------------------

-- ****************************************************************************
-- Creates the popup partial effects frame.
-- ****************************************************************************
local function CreatePartialEffects()
 local frame = CreatePopup();
 frame:SetWidth(270);
 frame:SetHeight(240);

 -- Close button.
 local button = CreateFrame("Button", nil, frame, "UIPanelCloseButton");
 button:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -2, -2);

 -- Color partial effects.
 local checkbox = MSBTControls.CreateCheckbox(frame);
 local objLocale = L.CHECKBOXES["colorPartialEffects"];
 checkbox:Configure(24, objLocale.label, objLocale.tooltip);
 checkbox:SetPoint("TOPLEFT", frame, "TOPLEFT", 20, -20);
 checkbox:SetClickHandler(
  function (this, isChecked)
   MSBTProfiles.SetOption(nil, "partialColoringDisabled", not isChecked);
  end
 );
 frame.colorCheckbox = checkbox;


 -- Partial effects.
 local anchor = checkbox;
 local colorswatch;
 for effectType in string.gmatch("crushing glancing absorb block resist overheal", "[^%s]+") do
  colorswatch = MSBTControls.CreateColorswatch(frame);
  colorswatch:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", anchor == checkbox and 20 or 0, -10);
  colorswatch:SetColorChangedHandler(
   function (this)
    MSBTProfiles.SetOption(effectType, "colorR", this.r);
    MSBTProfiles.SetOption(effectType, "colorG", this.g);
    MSBTProfiles.SetOption(effectType, "colorB", this.b);
   end
  );
  checkbox = MSBTControls.CreateCheckbox(frame);
  objLocale = L.CHECKBOXES[effectType];
  checkbox:Configure(24, objLocale.label, objLocale.tooltip);
  checkbox:SetPoint("LEFT", colorswatch, "RIGHT", 5, 0);
  checkbox:SetClickHandler(
   function (this, isChecked)
    MSBTProfiles.SetOption(effectType, "disabled", not isChecked);
   end
  );
  frame[effectType .. "Colorswatch"] = colorswatch;
  frame[effectType .. "Checkbox"] = checkbox; 
  
  anchor = colorswatch;
 end

  return frame;
end


-- ****************************************************************************
-- Shows the popup damage partial effects frame using the passed config.
-- ****************************************************************************
local function ShowPartialEffects(configTable)
 -- Don't do anything if required parameters weren't passed.
 if (not configTable or not configTable.anchorFrame or not configTable.parentFrame) then return; end
 
 -- Create the frame if it hasn't already been.
 if (not popupFrames.partialEffectsFrame) then popupFrames.partialEffectsFrame = CreatePartialEffects(); end

 -- Set parent.
 local frame = popupFrames.partialEffectsFrame;
 ChangePopupParent(frame, configTable.parentFrame)
 
 -- Populate data.
 frame.colorCheckbox:SetChecked(not MSBTProfiles.currentProfile.partialColoringDisabled);
 
 local profileEntry;
 for effectType in string.gmatch("crushing glancing absorb block resist overheal", "[^%s]+") do
  profileEntry = MSBTProfiles.currentProfile[effectType];
  frame[effectType .. "Colorswatch"]:SetColor(profileEntry.colorR, profileEntry.colorG, profileEntry.colorB);
  frame[effectType .. "Checkbox"]:SetChecked(not profileEntry.disabled);
 end
 
 -- Configure the frame.
 frame.hideHandler = configTable.hideHandler;
 frame:ClearAllPoints();
 frame:SetPoint(configTable.anchorPoint or "TOPLEFT", configTable.anchorFrame, configTable.relativePoint or "BOTTOMLEFT");
 frame:Show();
 frame:Raise();
end


-------------------------------------------------------------------------------
-- Damage color frame functions.
-------------------------------------------------------------------------------

-- ****************************************************************************
-- Creates the popup damage colors frame.
-- ****************************************************************************
local function CreateDamageColors()
 local frame = CreatePopup();
 frame:SetWidth(260);
 frame:SetHeight(220);

 -- Close button.
 local button = CreateFrame("Button", nil, frame, "UIPanelCloseButton");
 button:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -2, -2);

 -- Color damage amounts.
 local checkbox = MSBTControls.CreateCheckbox(frame);
 local objLocale = L.CHECKBOXES["colorDamageAmounts"];
 checkbox:Configure(24, objLocale.label, objLocale.tooltip);
 checkbox:SetPoint("TOPLEFT", frame, "TOPLEFT", 20, -20);
 checkbox:SetClickHandler(
  function (this, isChecked)
   MSBTProfiles.SetOption(nil, "damageColoringDisabled", not isChecked);
  end
 );
 frame.colorCheckbox = checkbox;


 -- Damage types.
 local anchor = checkbox;
 local globalStringSchoolIndex = 0;
 local colorswatch, fontString;
 for damageType in string.gmatch("physical holy fire nature frost shadow arcane", "[^%s]+") do
  colorswatch = MSBTControls.CreateColorswatch(frame);
  colorswatch:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", anchor == checkbox and 20 or 0, anchor == checkbox and -10 or -5);
  colorswatch:SetColorChangedHandler(
   function (this)
    MSBTProfiles.SetOption(damageType, "colorR", this.r);
    MSBTProfiles.SetOption(damageType, "colorG", this.g);
    MSBTProfiles.SetOption(damageType, "colorB", this.b);
   end
  );
  checkbox = MSBTControls.CreateCheckbox(frame);
  objLocale = L.CHECKBOXES["colorDamageEntry"];
  checkbox:Configure(24, _G["SPELL_SCHOOL" .. globalStringSchoolIndex .. "_CAP"], objLocale.tooltip);
  checkbox:SetPoint("LEFT", colorswatch, "RIGHT", 5, 0);
  checkbox:SetClickHandler(
   function (this, isChecked)
    MSBTProfiles.SetOption(damageType, "disabled", not isChecked);
   end
  );
  frame[damageType .. "Colorswatch"] = colorswatch;
  frame[damageType .. "Checkbox"] = checkbox; 
  
  anchor = colorswatch; 
  globalStringSchoolIndex = globalStringSchoolIndex + 1;
 end

 return frame;
end


-- ****************************************************************************
-- Shows the popup damage type colors frame using the passed config.
-- ****************************************************************************
local function ShowDamageColors(configTable)
 -- Don't do anything if required parameters weren't passed.
 if (not configTable or not configTable.anchorFrame or not configTable.parentFrame) then return; end

 -- Create the frame if it hasn't already been.
 if (not popupFrames.damageColorsFrame) then popupFrames.damageColorsFrame = CreateDamageColors(); end

 -- Set parent.
 local frame = popupFrames.damageColorsFrame;
 ChangePopupParent(frame, configTable.parentFrame)

 -- Populate data.
 frame.colorCheckbox:SetChecked(not MSBTProfiles.currentProfile.damageColoringDisabled);

 local profileEntry;
 for damageType in string.gmatch("physical holy fire nature frost shadow arcane", "[^%s]+") do
  profileEntry = MSBTProfiles.currentProfile[damageType];
  frame[damageType .. "Colorswatch"]:SetColor(profileEntry.colorR, profileEntry.colorG, profileEntry.colorB);
  frame[damageType .. "Checkbox"]:SetChecked(not profileEntry.disabled);
 end
 
 -- Configure the frame.
 frame.hideHandler = configTable.hideHandler;
 frame:ClearAllPoints();
 frame:SetPoint(configTable.anchorPoint or "TOPLEFT", configTable.anchorFrame, configTable.relativePoint or "BOTTOMLEFT");
 frame:Show();
 frame:Raise();
end


-------------------------------------------------------------------------------
-- Scroll area config frame functions.
-------------------------------------------------------------------------------

-- **********************************************************************************
-- This function copies the current scroll area settings into the passed table key.
-- **********************************************************************************
local function CopyTempScrollAreaSettings(settingsTable)
 local frame = popupFrames.scrollAreaConfigFrame;
 EraseTable(settingsTable);

 -- Get the original settings.
 local tempSettings;
 for saKey, saSettings in pairs(MSBTAnimations.scrollAreas) do
  settingsTable[saKey] = {};
  tempSettings = settingsTable[saKey];

  -- Normal.
  tempSettings.animationStyle = saSettings.animationStyle or DEFAULT_ANIMATION_STYLE;
  tempSettings.direction = saSettings.direction;
  tempSettings.behavior = saSettings.behavior;
  tempSettings.textAlignIndex = saSettings.textAlignIndex or DEFAULT_TEXT_ALIGN_INDEX;

  -- Sticky.
  tempSettings.stickyAnimationStyle = saSettings.stickyAnimationStyle or DEFAULT_STICKY_ANIMATION_STYLE;
  tempSettings.stickyDirection = saSettings.stickyDirection;
  tempSettings.stickyBehavior = saSettings.stickyBehavior;
  tempSettings.stickyTextAlignIndex = saSettings.stickyTextAlignIndex or DEFAULT_TEXT_ALIGN_INDEX;

  -- Positioning.
  tempSettings.scrollHeight = saSettings.scrollHeight or DEFAULT_SCROLL_HEIGHT;
  tempSettings.scrollWidth = saSettings.scrollWidth or DEFAULT_SCROLL_WIDTH;
  tempSettings.offsetX = saSettings.offsetX or 0;
  tempSettings.offsetY = saSettings.offsetY or 0;

  -- Speed.
  tempSettings.inheritedAnimationSpeed = MSBTProfiles.currentProfile.animationSpeed;
  tempSettings.animationSpeed = saSettings.animationSpeed;
 end
end


-- ****************************************************************************
-- Changes the normal animation style to the passed value.
-- ****************************************************************************
local function ChangeAnimationStyle(styleKey)
 local frame = popupFrames.scrollAreaConfigFrame;
 local styleSettings = MSBTAnimations.animationStyles[styleKey];
 local firstEntry, name, objLocale;

 -- Normal direction.
 frame.directionDropdown:Clear();
 if (styleSettings.availableDirections) then
  for direction in string.gmatch(styleSettings.availableDirections, "[^;]+") do
   if (not firstEntry) then firstEntry = direction; end
   objLocale = styleSettings.localizationTable;
   name = objLocale and objLocale[direction] or L.ANIMATION_STYLE_DATA[direction] or direction;
   frame.directionDropdown:AddItem(name, direction);
  end
  frame.directionDropdown:SetSelectedID(firstEntry);  
 else
  -- No available directions, so just add a normal entry.
  frame.directionDropdown:AddItem(L.ANIMATION_STYLE_DATA["Normal"], "MSBT_NORMAL");
  frame.directionDropdown:SetSelectedID("MSBT_NORMAL");
 end
 
 -- Normal behavior.
 firstEntry = nil;
 frame.behaviorDropdown:Clear();
 if (styleSettings.availableBehaviors) then
  for behavior in string.gmatch(styleSettings.availableBehaviors, "[^;]+") do
   if (not firstEntry) then firstEntry = behavior; end
   objLocale = styleSettings.localizationTable;
   name = objLocale and objLocale[behavior] or L.ANIMATION_STYLE_DATA[behavior] or behavior;
   frame.behaviorDropdown:AddItem(name, behavior);
  end
  frame.behaviorDropdown:SetSelectedID(firstEntry);  
 else
  -- No available behaviors, so just add a normal entry.
  frame.behaviorDropdown:AddItem(L.ANIMATION_STYLE_DATA["Normal"], "MSBT_NORMAL");
  frame.behaviorDropdown:SetSelectedID("MSBT_NORMAL");
 end
end


-- ****************************************************************************
-- Changes the sticky animation style to the passed value.
-- ****************************************************************************
local function ChangeStickyAnimationStyle(styleKey)
 local frame = popupFrames.scrollAreaConfigFrame;
 local styleSettings = MSBTAnimations.stickyAnimationStyles[styleKey];
 local firstEntry, name, objLocale;

 -- Sticky direction.
 frame.stickyDirectionDropdown:Clear();
 if (styleSettings.availableDirections) then
  for direction in string.gmatch(styleSettings.availableDirections, "[^;]+") do
   if (not firstEntry) then firstEntry = direction; end
   objLocale = styleSettings.localizationTable;
   name = objLocale and objLocale[direction] or L.ANIMATION_STYLE_DATA[direction] or direction;
   frame.stickyDirectionDropdown:AddItem(name, direction);
  end
  frame.stickyDirectionDropdown:SetSelectedID(firstEntry);  
 else
  -- No available directions, so just add a normal entry.
  frame.stickyDirectionDropdown:AddItem(L.ANIMATION_STYLE_DATA["Normal"], "MSBT_NORMAL");
  frame.stickyDirectionDropdown:SetSelectedID("MSBT_NORMAL");
 end
 
 -- Sticky behavior.
 firstEntry = nil;
 frame.stickyBehaviorDropdown:Clear();
 if (styleSettings.availableBehaviors) then
  for behavior in string.gmatch(styleSettings.availableBehaviors, "[^;]+") do
   if (not firstEntry) then firstEntry = behavior; end
   objLocale = styleSettings.localizationTable;
   name = objLocale and objLocale[behavior] or L.ANIMATION_STYLE_DATA[behavior] or behavior;
   frame.stickyBehaviorDropdown:AddItem(name, behavior);
  end
  frame.stickyBehaviorDropdown:SetSelectedID(firstEntry);  
 else
  -- No available behaviors, so just add a normal entry.
  frame.stickyBehaviorDropdown:AddItem(L.ANIMATION_STYLE_DATA["Normal"], "MSBT_NORMAL");
  frame.stickyBehaviorDropdown:SetSelectedID("MSBT_NORMAL");
 end
end


-- ****************************************************************************
-- Changes the scroll area to configure to the passed value.
-- ****************************************************************************
local function ChangeConfigScrollArea(scrollArea)
 local frame = popupFrames.scrollAreaConfigFrame;
 frame.currentScrollArea = scrollArea;
 local saSettings = frame.previewSettings[scrollArea];
 local name, objLocale;

 -- Normal animation style.
 frame.animationStyleDropdown:Clear();
 for styleKey, settings in pairs(MSBTAnimations.animationStyles) do 
  objLocale = settings.localizationTable;
  name = objLocale and objLocale[styleKey] or L.ANIMATION_STYLE_DATA[styleKey] or styleKey;
  frame.animationStyleDropdown:AddItem(name, styleKey);
 end
 frame.animationStyleDropdown:SetSelectedID(saSettings.animationStyle);
 ChangeAnimationStyle(saSettings.animationStyle);

 -- Normal direction, behavior, and text align.
 if (saSettings.direction) then frame.directionDropdown:SetSelectedID(saSettings.direction); end
 if (saSettings.behavior) then frame.behaviorDropdown:SetSelectedID(saSettings.behavior); end
 frame.textAlignDropdown:SetSelectedID(saSettings.textAlignIndex);


 -- Sticky animation style.
 frame.stickyAnimationStyleDropdown:Clear();
 for styleKey, settings in pairs(MSBTAnimations.stickyAnimationStyles) do 
  objLocale = settings.localizationTable;
  name = objLocale and objLocale[styleKey] or L.ANIMATION_STYLE_DATA[styleKey] or styleKey;
  frame.stickyAnimationStyleDropdown:AddItem(name, styleKey);
 end
 frame.stickyAnimationStyleDropdown:SetSelectedID(saSettings.stickyAnimationStyle);
 ChangeStickyAnimationStyle(saSettings.stickyAnimationStyle);

 -- Sticky direction, behavior, and text align.
 if (saSettings.stickyDirection) then frame.stickyDirectionDropdown:SetSelectedID(saSettings.stickyDirection); end
 if (saSettings.stickyBehavior) then frame.stickyBehaviorDropdown:SetSelectedID(saSettings.stickyBehavior); end
 frame.stickyTextAlignDropdown:SetSelectedID(saSettings.stickyTextAlignIndex);

 -- Scroll height and width.
 frame.scrollHeightSlider:SetValue(saSettings.scrollHeight);
 frame.scrollWidthSlider:SetValue(saSettings.scrollWidth);

 -- Animation speed
 local isSpeedInherited = not saSettings.animationSpeed or saSettings.animationSpeed == saSettings.inheritedAnimationSpeed;
 frame.animationSpeedCheckbox:SetChecked(isSpeedInherited);
 if (saSettings.animationSpeed) then frame.animationSpeedSlider:SetValue(saSettings.animationSpeed); end
 ToggleSliderInheritState(frame.animationSpeedSlider, isSpeedInherited , saSettings.inheritedAnimationSpeed);
 
 -- X and Y offset.
 frame.xOffsetEditbox:SetText(saSettings.offsetX);
 frame.yOffsetEditbox:SetText(saSettings.offsetY);
 
 -- Reset the backdrop color of all the scroll area mover frames to grey.
 for _, moverFrame in pairs(frame.moverFrames) do
  moverFrame:SetBackdropColor(0.8, 0.8, 0.8, 1.0);
 end
 
 -- Set the selected scroll area mover frame to red and raise it.
 frame.moverFrames[scrollArea]:SetBackdropColor(0.5, 0.05, 0.05, 1.0);
 frame.moverFrames[scrollArea]:Raise();
end


-- **********************************************************************************
-- This function repositions the mover frame for the passed scroll area.
-- **********************************************************************************
local function RepositionScrollAreaMoverFrame(scrollArea)
 local configFrame = popupFrames.scrollAreaConfigFrame;
 local frame = configFrame.moverFrames[scrollArea];
 local saSettings = configFrame.previewSettings[scrollArea];

 frame:ClearAllPoints();
 frame:SetPoint("BOTTOMLEFT", UIParent, "CENTER", saSettings.offsetX, saSettings.offsetY);
 frame:SetHeight(saSettings.scrollHeight);
 frame:SetWidth(saSettings.scrollWidth);
 frame.fontString:SetText(MSBTAnimations.scrollAreas[scrollArea].name .. " (" .. saSettings.offsetX .. ", " .. saSettings.offsetY .. ")");
 frame:Show();
end


-- **********************************************************************************
-- Save the coordinates of a scroll area mover.
-- **********************************************************************************
local function SaveScrollAreaMoverCoordinates(frame)
 -- Get the UIParent center x and y coords.
 local uiParentX, uiParentY = UIParent:GetCenter();
 local xOffset = math.ceil(this:GetLeft() - uiParentX);
 local yOffset = math.ceil(this:GetBottom() - uiParentY);
 
 -- Save the x and y offsets.
 local configFrame = popupFrames.scrollAreaConfigFrame;
 configFrame.previewSettings[frame.scrollArea].offsetX = xOffset;
 configFrame.previewSettings[frame.scrollArea].offsetY = yOffset;

 -- Populate the x and y offset editboxes if the moved frame is the selected one.
 if (frame.scrollArea == configFrame.scrollAreaDropdown:GetSelectedID()) then
  configFrame.xOffsetEditbox:SetText(xOffset);
  configFrame.yOffsetEditbox:SetText(yOffset);
 end

 -- Reposition the scroll area mover frames to update the coordinates.
 RepositionScrollAreaMoverFrame(frame.scrollArea);
end


-- **********************************************************************************
-- Called when a mouse button is pressed on a mover frame.
-- **********************************************************************************
local function MoverFrameOnMouseDown(this, button)
 if (button == "LeftButton") then this:StartMoving(); end 
end


-- **********************************************************************************
-- Called when a mouse button is released on a mover frame.
-- **********************************************************************************
local function MoverFrameOnMouseUp(this)
 this:StopMovingOrSizing();
 SaveScrollAreaMoverCoordinates(this);

 local configFrame = popupFrames.scrollAreaConfigFrame;
 if (this.scrollArea ~= configFrame.scrollAreaDropdown:GetSelectedID()) then
  configFrame.scrollAreaDropdown:SetSelectedID(this.scrollArea);
  ChangeConfigScrollArea(this.scrollArea);
 end
end


-- **********************************************************************************
-- This function creates a scroll area mover frame for the passed scroll area if
-- it hasn't already been
-- **********************************************************************************
local function CreateScrollAreaMoverFrame(scrollArea)
 local moverFrames = popupFrames.scrollAreaConfigFrame.moverFrames;
 
 if (not moverFrames[scrollArea]) then
  local frame = CreateFrame("FRAME", nil, UIParent);
  frame:Hide();
  frame:SetMovable(true);
  frame:EnableMouse(true);
  frame:SetToplevel(true);
  frame:SetClampedToScreen(true);
  frame:SetBackdrop(moverBackdrop);
  frame:SetScript("OnMouseDown", MoverFrameOnMouseDown);
  frame:SetScript("OnMouseUp", MoverFrameOnMouseUp);
  
  local fontString = frame:CreateFontString(nil, "OVERLAY");
  fontString:SetFont("Fonts\\ARIALN.TTF", 12);
  fontString:SetPoint("CENTER");
  frame.fontString = fontString;

  frame.scrollArea = scrollArea;
  moverFrames[scrollArea] = frame;
 end
end


-- **********************************************************************************
-- Save the passed table to the scroll area settings.
-- **********************************************************************************
local function SaveScrollAreaSettings(settingsTable)
 local frame = popupFrames.scrollAreaConfigFrame;
 
 -- Save the settings in the passed table to the current profile.
 for saKey, saSettings in pairs(settingsTable) do
  -- Normal.
  MSBTProfiles.SetOption("scrollAreas." .. saKey, "animationStyle", saSettings.animationStyle, DEFAULT_ANIMATION_STYLE);
  MSBTProfiles.SetOption("scrollAreas." .. saKey, "direction", saSettings.direction, "MSBT_NORMAL");
  MSBTProfiles.SetOption("scrollAreas." .. saKey, "behavior", saSettings.behavior, "MSBT_NORMAL");  
  MSBTProfiles.SetOption("scrollAreas." .. saKey, "textAlignIndex", saSettings.textAlignIndex, DEFAULT_TEXT_ALIGN_INDEX);
  
  -- Sticky.
  MSBTProfiles.SetOption("scrollAreas." .. saKey, "stickyAnimationStyle", saSettings.stickyAnimationStyle, DEFAULT_STICKY_ANIMATION_STYLE);
  MSBTProfiles.SetOption("scrollAreas." .. saKey, "stickyDirection", saSettings.stickyDirection, "MSBT_NORMAL");
  MSBTProfiles.SetOption("scrollAreas." .. saKey, "stickyBehavior", saSettings.stickyBehavior, "MSBT_NORMAL");
  MSBTProfiles.SetOption("scrollAreas." .. saKey, "stickyTextAlignIndex", saSettings.stickyTextAlignIndex, DEFAULT_TEXT_ALIGN_INDEX);
  
  -- Position.  
  MSBTProfiles.SetOption("scrollAreas." .. saKey, "scrollHeight", saSettings.scrollHeight, DEFAULT_SCROLL_HEIGHT);
  MSBTProfiles.SetOption("scrollAreas." .. saKey, "scrollWidth", saSettings.scrollWidth, DEFAULT_SCROLL_WIDTH);
  MSBTProfiles.SetOption("scrollAreas." .. saKey, "offsetX", saSettings.offsetX);
  MSBTProfiles.SetOption("scrollAreas." .. saKey, "offsetY", saSettings.offsetY);
  
  -- Animation speed.
  local animationSpeed = saSettings.animationSpeed;
  MSBTProfiles.SetOption("scrollAreas." .. saKey, "animationSpeed", animationSpeed, saSettings.inheritedAnimationSpeed);
 end
 MSBTAnimations.UpdateScrollAreas();
end


-- ****************************************************************************
-- Creates the popup scroll areas config frames.
-- ****************************************************************************
local function CreateScrollAreaConfig()
 local frame = CreatePopup();
 frame:SetWidth(320);
 frame:SetHeight(535);
 frame:SetPoint("RIGHT");
 frame:SetScript("OnHide",
  function (this)
   for _, moverFrame in pairs(this.moverFrames) do
    moverFrame:Hide();
   end
   MSBTOptions.Main.ShowMainFrame();
  end
 );
 
 -- Scroll area dropdown.
 local dropdown =  MSBTControls.CreateDropdown(frame);
 objLocale = L.DROPDOWNS["scrollArea"];
 dropdown:Configure(200, objLocale.label, objLocale.tooltip);
 dropdown:SetPoint("TOP", frame, "TOP", 0, -20);
 dropdown:SetChangeHandler(
  function (this, id)
   ChangeConfigScrollArea(id);
  end
 );
 frame.scrollAreaDropdown = dropdown;

 
 -- Top horizontal bar.
 local texture = frame:CreateTexture(nil, "ARTWORK");
 texture:SetTexture("Interface\\PaperDollInfoFrame\\SkillFrame-BotLeft");
 texture:SetHeight(4);
 texture:SetPoint("TOPLEFT", frame, "TOPLEFT", 10, -70);
 texture:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -10, -70);
 texture:SetTexCoord(0.078125, 1, 0.59765625, 0.61328125);

 
 -- Normal animation style dropdown.
 dropdown =  MSBTControls.CreateDropdown(frame);
 objLocale = L.DROPDOWNS["animationStyle"];
 dropdown:Configure(135, objLocale.label, objLocale.tooltip);
 dropdown:SetPoint("TOPLEFT", texture, "BOTTOMLEFT", 5, -15);
 dropdown:SetChangeHandler(
  function (this, id)
   ChangeAnimationStyle(id);
   frame.previewSettings[frame.currentScrollArea].animationStyle = id;
   frame.previewSettings[frame.currentScrollArea].direction = frame.directionDropdown:GetSelectedID();
   frame.previewSettings[frame.currentScrollArea].behavior = frame.behaviorDropdown:GetSelectedID();
  end
 );
 frame.animationStyleDropdown = dropdown;

 -- Sticky animation style dropdown.
 dropdown =  MSBTControls.CreateDropdown(frame);
 objLocale = L.DROPDOWNS["stickyAnimationStyle"];
 dropdown:Configure(135, objLocale.label, objLocale.tooltip);
 dropdown:SetPoint("LEFT", frame.animationStyleDropdown, "RIGHT", 15, 0);
 dropdown:SetChangeHandler(
  function (this, id)
   ChangeStickyAnimationStyle(id);
   frame.previewSettings[frame.currentScrollArea].stickyAnimationStyle = id;
   frame.previewSettings[frame.currentScrollArea].stickyDirection = frame.stickyDirectionDropdown:GetSelectedID();
   frame.previewSettings[frame.currentScrollArea].stickyBehavior = frame.stickyBehaviorDropdown:GetSelectedID();
  end
 );
 frame.stickyAnimationStyleDropdown = dropdown;

 -- Normal direction dropdown.
 dropdown =  MSBTControls.CreateDropdown(frame);
 objLocale = L.DROPDOWNS["direction"];
 dropdown:Configure(135,objLocale.label, objLocale.tooltip);
 dropdown:SetPoint("TOPLEFT", frame.animationStyleDropdown, "BOTTOMLEFT", 0, -10);
 dropdown:SetChangeHandler(
  function (this, id)
   frame.previewSettings[frame.currentScrollArea].direction = id;
  end
 );
 frame.directionDropdown = dropdown;

 -- Sticky direction dropdown.
 dropdown =  MSBTControls.CreateDropdown(frame);
 objLocale = L.DROPDOWNS["direction"];
 dropdown:Configure(135, objLocale.label, objLocale.tooltip);
 dropdown:SetPoint("TOPLEFT", frame.stickyAnimationStyleDropdown, "BOTTOMLEFT", 0, -10);
 dropdown:SetChangeHandler(
  function (this, id)
   frame.previewSettings[frame.scrollAreaDropdown:GetSelectedID()].stickyDirection = id;
  end
 );
 frame.stickyDirectionDropdown = dropdown;

 -- Normal behavior dropdown.
 dropdown =  MSBTControls.CreateDropdown(frame);
 objLocale = L.DROPDOWNS["behavior"];
 dropdown:Configure(135, objLocale.label, objLocale.tooltip);
 dropdown:SetPoint("TOPLEFT", frame.directionDropdown, "BOTTOMLEFT", 0, -10);
  dropdown:SetChangeHandler(
  function (this, id)
   frame.previewSettings[frame.currentScrollArea].behavior = id;
  end
 );
 frame.behaviorDropdown = dropdown;

 -- Sticky behavior dropdown.
 dropdown =  MSBTControls.CreateDropdown(frame);
 objLocale = L.DROPDOWNS["behavior"];
 dropdown:Configure(135, objLocale.label, objLocale.tooltip);
 dropdown:SetPoint("TOPLEFT", frame.stickyDirectionDropdown, "BOTTOMLEFT", 0, -10);
 dropdown:SetChangeHandler(
  function (this, id)
   frame.previewSettings[frame.currentScrollArea].stickyBehavior = id;
  end
 );
 frame.stickyBehaviorDropdown = dropdown;

 -- Normal text align dropdown.
 dropdown =  MSBTControls.CreateDropdown(frame);
 objLocale = L.DROPDOWNS["textAlign"];
 dropdown:Configure(135, objLocale.label, objLocale.tooltip);
 dropdown:SetPoint("TOPLEFT", frame.behaviorDropdown, "BOTTOMLEFT", 0, -10);
 dropdown:SetChangeHandler(
  function (this, id)
   frame.previewSettings[frame.currentScrollArea].textAlignIndex = id;
  end
 );
 for index, anchorPoint in ipairs(L.TEXT_ALIGNS) do
  dropdown:AddItem(anchorPoint, index);
 end
 frame.textAlignDropdown = dropdown;

 -- Sticky text align dropdown.
 dropdown =  MSBTControls.CreateDropdown(frame);
 objLocale = L.DROPDOWNS["textAlign"];
 dropdown:Configure(135, objLocale.label, objLocale.tooltip);
 dropdown:SetPoint("TOPLEFT", frame.stickyBehaviorDropdown, "BOTTOMLEFT", 0, -10);
 dropdown:SetChangeHandler(
  function (this, id)
   frame.previewSettings[frame.currentScrollArea].stickyTextAlignIndex = id;
  end
 );
 for index, anchorPoint in ipairs(L.TEXT_ALIGNS) do
  dropdown:AddItem(anchorPoint, index);
 end
 frame.stickyTextAlignDropdown = dropdown;

 
 -- Middle horizontal bar.
 texture = frame:CreateTexture(nil, "ARTWORK");
 texture:SetTexture("Interface\\PaperDollInfoFrame\\SkillFrame-BotLeft");
 texture:SetHeight(4);
 texture:SetPoint("TOPLEFT", frame, "TOPLEFT", 10, -295);
 texture:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -10, -295);
 texture:SetTexCoord(0.078125, 1, 0.59765625, 0.61328125);

 
 -- Scroll height slider.
 local slider = MSBTControls.CreateSlider(frame);
 objLocale = L.SLIDERS["scrollHeight"]; 
 slider:Configure(135, objLocale.label, objLocale.tooltip);
 slider:SetPoint("TOPLEFT", texture, "BOTTOMLEFT", 5, -15);
 slider:SetMinMaxValues(50, 600);
 slider:SetValueStep(5);
 slider:SetValueChangedHandler(
  function(this, value)
   frame.previewSettings[frame.currentScrollArea].scrollHeight = value;
   RepositionScrollAreaMoverFrame(frame.currentScrollArea);
  end
 );
 frame.scrollHeightSlider = slider;

 -- Scroll width slider.
 slider = MSBTControls.CreateSlider(frame);
 objLocale = L.SLIDERS["scrollWidth"]; 
 slider:Configure(135, objLocale.label, objLocale.tooltip);
 slider:SetPoint("LEFT", frame.scrollHeightSlider, "RIGHT", 15, 0);
 slider:SetMinMaxValues(10, 800);
 slider:SetValueStep(10);
 slider:SetValueChangedHandler(
  function(this, value)
   frame.previewSettings[frame.currentScrollArea].scrollWidth = value;
   RepositionScrollAreaMoverFrame(frame.currentScrollArea);
  end
 );
 frame.scrollWidthSlider = slider;

 -- Animation speed slider.
 slider = MSBTControls.CreateSlider(frame);
 objLocale = L.SLIDERS["scrollAnimationSpeed"]; 
 slider:Configure(135, objLocale.label, objLocale.tooltip);
 slider:SetPoint("TOPLEFT", frame.scrollHeightSlider, "BOTTOMLEFT", 0, -10);
 slider:SetMinMaxValues(20, 250);
 slider:SetValueStep(10);
 slider:SetValueChangedHandler(
  function(this, value)
   frame.previewSettings[frame.currentScrollArea].animationSpeed = value;
  end
 );
 frame.animationSpeedSlider = slider;
 
 -- Inherit animation speed checkbox.
 checkbox = MSBTControls.CreateCheckbox(frame);
 objLocale = L.CHECKBOXES["inheritField"]; 
 checkbox:Configure(20, objLocale.label, objLocale.tooltip);
 checkbox:SetPoint("BOTTOMLEFT", frame.animationSpeedSlider, "BOTTOMRIGHT", 10, 5);
 checkbox:SetClickHandler(
  function (this, isChecked)
   ToggleSliderInheritState(frame.animationSpeedSlider, isChecked, frame.previewSettings[frame.currentScrollArea].inheritedAnimationSpeed);
  end
 );
 frame.animationSpeedCheckbox = checkbox; 


 -- X offset editbox.
 local editbox = MSBTControls.CreateEditbox(frame);
 objLocale = L.EDITBOXES["xOffset"]; 
 editbox:Configure(135, objLocale.label, objLocale.tooltip);
 editbox:SetPoint("TOPLEFT", frame.animationSpeedSlider, "BOTTOMLEFT", 0, -10);
 editbox:SetTextChangedHandler(
  function (this)
   local newOffset = tonumber(this:GetText());
   if (newOffset) then
    frame.previewSettings[frame.currentScrollArea].offsetX = newOffset;
    RepositionScrollAreaMoverFrame(frame.currentScrollArea);
   end
  end
 );
 frame.xOffsetEditbox = editbox;

 -- Y offset editbox.
 editbox = MSBTControls.CreateEditbox(frame);
 objLocale = L.EDITBOXES["yOffset"]; 
 editbox:Configure(135, objLocale.label, objLocale.tooltip);
 editbox:SetPoint("LEFT", frame.xOffsetEditbox, "RIGHT", 15, 0);
 editbox:SetTextChangedHandler(
  function (this)
   local newOffset = tonumber(this:GetText());
   if (newOffset) then
    frame.previewSettings[frame.currentScrollArea].offsetY = newOffset;
    RepositionScrollAreaMoverFrame(frame.currentScrollArea);
   end
  end
 );
 frame.yOffsetEditbox = editbox;

 
 -- Bottom horizontal bar.
 texture = frame:CreateTexture(nil, "ARTWORK");
 texture:SetTexture("Interface\\PaperDollInfoFrame\\SkillFrame-BotLeft");
 texture:SetHeight(4);
 texture:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 10, 80);
 texture:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -10, 80);
 texture:SetTexCoord(0.078125, 1, 0.59765625, 0.61328125);


 -- Preview button.
 button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["scrollAreasPreview"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("BOTTOM", frame, "BOTTOM", 0, 50);
 button:SetClickHandler(
  function (this)
   SaveScrollAreaSettings(frame.previewSettings);
   local name;
   for saKey in pairs(frame.previewSettings) do
    name = MSBTAnimations.scrollAreas[saKey].name;
    MikSBT.DisplayMessage(name, saKey, nil, 255, 0, 0);
    MikSBT.DisplayMessage(name, saKey, nil, 255, 255, 255);
    MikSBT.DisplayMessage(name, saKey, true, 0, 0, 255);
   end
  end
 );
 
 -- Save button.
 local button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["genericSave"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -10, 20);
 button:SetClickHandler(
  function (this)
   SaveScrollAreaSettings(frame.previewSettings);
   frame:Hide();
  end
 );

 -- Cancel button.
 button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["genericCancel"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("BOTTOMLEFT", frame, "BOTTOM", 10, 20);
 button:SetClickHandler(
  function (this)
   SaveScrollAreaSettings(frame.originalSettings);
   frame:Hide();
  end
 );
 
 -- Track internal values.
 frame.moverFrames = {};
 frame.originalSettings = {};
 frame.previewSettings = {};
 
 -- Give the frame a global name.
 _G["MSBTScrollAreasConfigFrame"] = frame;
 return frame;
end


-- ****************************************************************************
-- Shows the popup scroll area config screen.
-- ****************************************************************************
local function ShowScrollAreaConfig()
 -- Create the frame if it hasn't already been.
 if (not popupFrames.scrollAreaConfigFrame) then popupFrames.scrollAreaConfigFrame = CreateScrollAreaConfig(); end
 
 local frame = popupFrames.scrollAreaConfigFrame;

 -- Backup the original settings for previewing and cancelling.
 CopyTempScrollAreaSettings(frame.originalSettings);
 CopyTempScrollAreaSettings(frame.previewSettings);
 
 -- Populate the scroll areas and setup the mover frames. 
 frame.scrollAreaDropdown:Clear();
 for saKey, saSettings in pairs(MSBTAnimations.scrollAreas) do
  frame.scrollAreaDropdown:AddItem(saSettings.name, saKey);
  
  -- Create and reposition the scroll area mover frames.
  CreateScrollAreaMoverFrame(saKey);
  RepositionScrollAreaMoverFrame(saKey);
 end
 frame.scrollAreaDropdown:Sort();
 frame.currentScrollArea = "Incoming";
 frame.scrollAreaDropdown:SetSelectedID(frame.currentScrollArea);
 ChangeConfigScrollArea(frame.currentScrollArea);
 
 frame:Show();
end


-------------------------------------------------------------------------------
-- Scroll area selection frame functions.
-------------------------------------------------------------------------------

-- ****************************************************************************
-- Creates the popup scroll area selection frame.
-- ****************************************************************************
local function CreateScrollAreaSelection()
 local frame = CreatePopup();
 frame:SetWidth(350);
 frame:SetHeight(150);
 
 -- Title text.
 local fontString = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("TOP", frame, "TOP", 0, -20);
 frame.titleFontString = fontString;

 
 -- Scroll area dropdown.
 local dropdown =  MSBTControls.CreateDropdown(frame);
 objLocale = L.DROPDOWNS["outputScrollArea"];
 dropdown:Configure(150, objLocale.label, objLocale.tooltip);
 dropdown:SetPoint("TOPLEFT", frame, "TOPLEFT", 20, -45);
 frame.scrollAreaDropdown = dropdown;
 
 
 -- Okay button.
 local button = MSBTControls.CreateOptionButton(frame);
 local objLocale = L.BUTTONS["inputOkay"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -10, 20);
 button:SetClickHandler(
  function (this)
   if (frame.saveHandler) then frame.saveHandler(frame.scrollAreaDropdown:GetSelectedID(), frame.saveArg1); end
   frame:Hide();   
  end
 );
 frame.okayButton = button;

 -- Cancel button.
 button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["inputCancel"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("BOTTOMLEFT", frame, "BOTTOM", 10, 20);
 button:SetClickHandler(
  function (this)
   frame:Hide();
  end
 );
 
 return frame;
end


-- ****************************************************************************
-- Shows the popup scroll area selection frame using the passed config.
-- ****************************************************************************
local function ShowScrollAreaSelection(configTable)
 -- Don't do anything if required parameters weren't passed.
 if (not configTable or not configTable.anchorFrame or not configTable.parentFrame) then return; end

 -- Create the frame if it hasn't already been.
 if (not popupFrames.scrollAreaSelectionFrame) then popupFrames.scrollAreaSelectionFrame = CreateScrollAreaSelection(); end
 
 -- Set parent.
 local frame = popupFrames.scrollAreaSelectionFrame;
 ChangePopupParent(frame, configTable.parentFrame)

 -- Populate data.
 frame.titleFontString:SetText(configTable.title);
 
 -- Scroll areas. 
 frame.scrollAreaDropdown:Clear();
 for saKey, saSettings in pairs(MSBTAnimations.scrollAreas) do
  frame.scrollAreaDropdown:AddItem(saSettings.name, saKey);
 end
 frame.scrollAreaDropdown:Sort();
 frame.scrollAreaDropdown:SetSelectedID("Incoming");


 -- Configure the frame.
 frame.saveHandler = configTable.saveHandler;
 frame.saveArg1 = configTable.saveArg1;
 frame.hideHandler = configTable.hideHandler;
 frame:ClearAllPoints();
 frame:SetPoint(configTable.anchorPoint or "TOPLEFT", configTable.anchorFrame, configTable.relativePoint or "BOTTOMLEFT");
 frame:Show();
 frame:Raise();
end


-------------------------------------------------------------------------------
-- Event frame functions.
-------------------------------------------------------------------------------

-- ****************************************************************************
-- Populates the available sounds for the event along with the passed custom
-- sound file.
-- ****************************************************************************
local function PopulateEventSounds(selectedSound)
 local controls = popupFrames.eventFrame.controls;
 
 local isCustomSound = selectedSound and true;
 controls.soundDropdown:Clear();
 for soundName in pairs(sounds) do
  if (soundName ~= NONE) then controls.soundDropdown:AddItem(L.SOUNDS[soundName] or soundName, soundName); end
  if (soundName == selectedSound) then isCustomSound = nil; end
 end
 controls.soundDropdown:AddItem(NONE, "");
 controls.soundDropdown:Sort();
 if (isCustomSound) then controls.soundDropdown:AddItem(selectedSound, selectedSound); end
 controls.soundDropdown:SetSelectedID(selectedSound or "");
end


-- ****************************************************************************
-- Enables the controls on the event popup.
-- ****************************************************************************
local function EnableEventControls()
 for name, frame in pairs(popupFrames.eventFrame.controls) do
  if (frame.Enable) then frame:Enable(); end
 end
end


-- ****************************************************************************
-- Validates if the passed skill name does not already exist and is valid.
-- ****************************************************************************
local function ValidateSoundFileName(fileName)
 if (not string.find(fileName, ".mp3") and not string.find(fileName, ".wav")) then
  return L.MSG_INVALID_SOUND_FILE;
 end
end


-- ****************************************************************************
-- Adds a custom sound file to for the event.
-- ****************************************************************************
local function AddCustomSoundFile(settings)
 PopulateEventSounds(settings.inputText);
end


-- ****************************************************************************
-- Creates the popup event settings frame.
-- ****************************************************************************
local function CreateEvent()
 local frame = CreatePopup();
 frame:SetWidth(320);
 frame:SetHeight(370);
 frame.controls = {};
 local controls = frame.controls;

 -- Title text.
 local fontString = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("TOP", frame, "TOP", 0, -20);
 frame.titleFontString = fontString;

 -- Scroll area dropdown.
 local dropdown =  MSBTControls.CreateDropdown(frame);
 objLocale = L.DROPDOWNS["outputScrollArea"];
 dropdown:Configure(150, objLocale.label, objLocale.tooltip);
 dropdown:SetPoint("TOPLEFT", frame, "TOPLEFT", 20, -60);
 controls.scrollAreaDropdown = dropdown;

 -- Output message editbox.
 local editbox = MSBTControls.CreateEditbox(frame);
 local objLocale = L.EDITBOXES["eventMessage"];
 editbox:Configure(250, objLocale.label, nil);
 editbox:SetPoint("TOPLEFT", controls.scrollAreaDropdown, "BOTTOMLEFT", 0, -20);
 controls.messageEditbox = editbox;

 -- Sound dropdown. 
 local dropdown =  MSBTControls.CreateDropdown(frame);
 objLocale = L.DROPDOWNS["sound"];
 dropdown:Configure(150, objLocale.label, objLocale.tooltip);
 dropdown:SetPoint("TOPLEFT", controls.messageEditbox, "BOTTOMLEFT", 0, -20);
 controls.soundDropdown = dropdown;

 -- Custom sound file button.
 local button = MSBTControls.CreateIconButton(frame, "Configure");
 local objLocale = L.BUTTONS["customSound"];
 button:SetTooltip(objLocale.tooltip);
 button:SetPoint("LEFT", controls.soundDropdown, "RIGHT", 10, -5);
 button:SetClickHandler(
  function (this)
   local objLocale = L.EDITBOXES["soundFile"];
   EraseTable(tempConfig);
   tempConfig.editboxLabel = objLocale.label;
   tempConfig.editboxTooltip = objLocale.tooltip;
   tempConfig.parentFrame = frame;
   tempConfig.anchorFrame = this;
   tempConfig.anchorPoint = "BOTTOMRIGHT";
   tempConfig.relativePoint = "TOPRIGHT";
   tempConfig.validateHandler = ValidateSoundFileName;
   tempConfig.saveHandler = AddCustomSoundFile;
   tempConfig.hideHandler = EnableEventControls;
   DisableControls(controls);
   ShowInput(tempConfig);
  end
 );
 controls[#controls+1] = button;

 -- Play sound button.
 local button = MSBTControls.CreateOptionButton(frame);
 local objLocale = L.BUTTONS["playSound"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("LEFT", controls[#controls], "RIGHT", 10, 0);
 button:SetClickHandler(
  function (this)
   local soundFile = controls.soundDropdown:GetSelectedID();
   for soundName, soundPath in MikSBT.IterateSounds() do
    if (soundName == soundFile) then soundFile = soundPath; end
   end
   soundFile = string.find(soundFile, "\\", nil, 1) and soundFile or DEFAULT_SOUND_PATH .. soundFile;
   PlaySoundFile(soundFile);
  end
 );
 controls[#controls+1] = button;

 -- Always sticky checkbox.
 local checkbox = MSBTControls.CreateCheckbox(frame);
 objLocale = L.CHECKBOXES["stickyEvent"]; 
 checkbox:Configure(28, objLocale.label, objLocale.tooltip);
 checkbox:SetPoint("TOPLEFT", controls.soundDropdown, "BOTTOMLEFT", 0, -20);
 controls.stickyCheckbox = checkbox; 

 
 -- Icon skill editbox.
 editbox = MSBTControls.CreateEditbox(frame);
 local objLocale = L.EDITBOXES["iconSkill"];
 editbox:Configure(250, objLocale.label, objLocale.tooltip);
 editbox:SetPoint("TOPLEFT", controls.stickyCheckbox, "BOTTOMLEFT", 0, -20);
 controls.iconSkillEditbox = editbox;



 -- Save button.
 button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["genericSave"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -10, 20);
 button:SetClickHandler(
  function (this)
   EraseTable(returnSettings);
   returnSettings.scrollArea = controls.scrollAreaDropdown:GetSelectedID();
   returnSettings.message = controls.messageEditbox:GetText();
   returnSettings.soundFile = controls.soundDropdown:GetSelectedID();
   returnSettings.alwaysSticky = controls.stickyCheckbox:GetChecked();
   returnSettings.iconSkill = controls.iconSkillEditbox:GetText();
   if (frame.saveHandler) then frame.saveHandler(returnSettings, frame.saveArg1); end
   frame:Hide();
  end
 );
 controls[#controls+1] = button;

 -- Cancel button.
 button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["genericCancel"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("BOTTOMLEFT", frame, "BOTTOM", 10, 20);
 button:SetClickHandler(
  function (this)
   frame:Hide();
  end
 );
 controls[#controls+1] = button;

 return frame;
end


-- ****************************************************************************
-- Shows the popup event settings frame using the passed config.
-- ****************************************************************************
local function ShowEvent(configTable)
 -- Don't do anything if required parameters weren't passed.
 if (not configTable or not configTable.anchorFrame or not configTable.parentFrame) then return; end

 -- Create the frame if it hasn't already been.
 if (not popupFrames.eventFrame) then popupFrames.eventFrame = CreateEvent(); end
 
 -- Set parent.
 local frame = popupFrames.eventFrame;
 ChangePopupParent(frame, configTable.parentFrame)

 -- Populate data.
 frame.titleFontString:SetText(configTable.title);

 local controls = frame.controls;
 controls.scrollAreaDropdown:Clear();
 for saKey, saSettings in pairs(MSBTAnimations.scrollAreas) do
  controls.scrollAreaDropdown:AddItem(saSettings.name, saKey);
 end
 controls.scrollAreaDropdown:Sort();
 controls.scrollAreaDropdown:SetSelectedID(configTable.scrollArea);

 local objLocale = L.EDITBOXES["eventMessage"];
 controls.messageEditbox:SetText(configTable.message);
 controls.messageEditbox:SetTooltip(objLocale.tooltip .. "\n\n" .. (configTable.codes or ""));
 PopulateEventSounds(configTable.soundFile);
 controls.stickyCheckbox:SetChecked(configTable.alwaysSticky);
 controls.iconSkillEditbox:SetText(configTable.iconSkill);


 -- Show / hide always sticky checkbox depending on if the event is a crit or not.
 if (configTable.isCrit) then controls.stickyCheckbox:Hide(); else controls.stickyCheckbox:Show(); end

 -- Show / hide icon skill editbox.
 if (configTable.showIconSkillEditbox) then
  frame:SetHeight(370);
  controls.iconSkillEditbox:Show();
 else
  controls.iconSkillEditbox:Hide();
  frame:SetHeight(310);
 end
 
 -- Configure the frame.
 frame.saveHandler = configTable.saveHandler;
 frame.saveArg1 = configTable.saveArg1;
 frame.hideHandler = configTable.hideHandler;
 frame:ClearAllPoints();
 frame:SetPoint(configTable.anchorPoint or "TOPLEFT", configTable.anchorFrame, configTable.relativePoint or "BOTTOMLEFT");
 frame:Show();
 frame:Raise();
end


-------------------------------------------------------------------------------
-- Trigger classes frame functions.
-------------------------------------------------------------------------------

-- ****************************************************************************
-- Creates the popup classes frame.
-- ****************************************************************************
local function CreateClasses()
 local frame = CreatePopup();
 frame:SetWidth(270);
 frame:SetHeight(340);
 frame.classCheckboxes = {};
 local classCheckboxes = frame.classCheckboxes;

 -- Close button.
 local button = CreateFrame("Button", nil, frame, "UIPanelCloseButton");
 button:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -2, -2);
 
 -- All classes checkbox.
 local checkbox = MSBTControls.CreateCheckbox(frame);
 objLocale = L.CHECKBOXES["allClasses"]; 
 checkbox:Configure(24, objLocale.label, objLocale.tooltip);
 checkbox:SetPoint("TOPLEFT", frame, "TOPLEFT", 20, -40);
 checkbox:SetClickHandler(
  function (this, isChecked)
   frame.classes["ALL"] = isChecked and true or nil;
   if (isChecked) then
    for name, checkFrame in pairs(frame.classCheckboxes) do
     checkFrame:SetChecked(true);
     checkFrame:Disable();
    end
   else
    for name, checkFrame in pairs(classCheckboxes) do
     checkFrame:Enable();
     checkFrame:SetChecked(frame.classes[checkFrame.associatedClass]);
    end
   end
   if (frame.updateHandler) then frame.updateHandler(); end
  end
 );
 frame.allClassesCheckbox = checkbox; 

 local anchor = checkbox;
 for class in string.gmatch("DEATHKNIGHT DRUID HUNTER MAGE PALADIN PRIEST ROGUE SHAMAN WARLOCK WARRIOR", "[^ ]+") do
  checkbox = MSBTControls.CreateCheckbox(frame);
  checkbox:Configure(24, L.CLASS_NAMES[class], nil);
  checkbox:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", anchor == frame.allClassesCheckbox and 20 or 0, anchor == frame.allClassesCheckbox and -10 or 0);
  checkbox:SetClickHandler(
   function (this, isChecked)
    frame.classes[this.associatedClass] = isChecked and true or nil;
    if (frame.updateHandler) then frame.updateHandler(); end
   end
  );
  checkbox.associatedClass = class;
  anchor = checkbox;
  classCheckboxes[class .. "Checkbox"] = checkbox;
 end 

 return frame;
end


-- ****************************************************************************
-- Shows the popup classes frame.
-- ****************************************************************************
local function ShowClasses(configTable)
 -- Don't do anything if required parameters weren't passed.
 if (not configTable or not configTable.anchorFrame or not configTable.parentFrame or not configTable.classes) then return; end

 -- Create the frame if it hasn't already been.
 if (not popupFrames.classesFrame) then popupFrames.classesFrame = CreateClasses(); end
 
 -- Set parent.
 local frame = popupFrames.classesFrame;
 ChangePopupParent(frame, configTable.parentFrame)

 -- Populate data.
 if (configTable.classes["ALL"]) then
  frame.allClassesCheckbox:SetChecked(true);
  for name, checkFrame in pairs(frame.classCheckboxes) do
   checkFrame:SetChecked(true);
   checkFrame:Disable();
  end  
 else
  frame.allClassesCheckbox:SetChecked(false);
  for name, checkFrame in pairs(frame.classCheckboxes) do
   checkFrame:Enable();
   checkFrame:SetChecked(configTable.classes[checkFrame.associatedClass]);
  end
 end
 

 -- Configure the frame.
 frame.classes = configTable.classes;
 frame.updateHandler = configTable.updateHandler;
 frame.hideHandler = configTable.hideHandler;
 frame:ClearAllPoints();
 frame:SetPoint(configTable.anchorPoint or "TOPLEFT", configTable.anchorFrame, configTable.relativePoint or "BOTTOMLEFT");
 frame:Show();
 frame:Raise();
end


-------------------------------------------------------------------------------
-- Trigger condition frame functions.
-------------------------------------------------------------------------------

-- ****************************************************************************
-- Called when one of the condition dropdowns are changed.
-- ****************************************************************************
local function ConditionDropdownOnChange(this, id)
 local frame = popupFrames.triggerConditionFrame;
 local conditionData = popupFrames.triggerFrame.conditionData[id];
 
 frame.parameterEditbox:Hide();
 frame.parameterSlider:Hide();
 frame.parameterDropdown:Hide();
 
 frame.relationDropdown:Clear();
 if (conditionData) then
  if (conditionData.relations) then
   for relationType, relationName in pairs(conditionData.relations) do
    frame.relationDropdown:AddItem(relationName, relationType);
   end
   frame.relationDropdown:SetSelectedID(conditionData.defaultRelation or "eq");
  end

  local control;
  if (conditionData.controlType == "editbox") then
   control = frame.parameterEditbox;
   control:Show();
   control:SetText(conditionData.default or "");
  elseif (conditionData.controlType == "slider") then
   control = frame.parameterSlider;
   control:Show();
   control:SetMinMaxValues(conditionData.minValue, conditionData.maxValue);
   control:SetValueStep(conditionData.step);
   control:SetValue(conditionData.default or conditionData.minValue);
  elseif (conditionData.controlType == "dropdown") then
   control = frame.parameterDropdown;
   control:Show();
   control:Clear();
   for itemValue, itemName in pairs(conditionData.items) do
    control:AddItem(itemName, itemValue);
   end
   control:Sort();
   control:SetSelectedID(conditionData.default);
   
  end
 end
end


-- ****************************************************************************
-- Creates the trigger condition frame.
-- ****************************************************************************
local function CreateTriggerCondition()
 local frame = CreatePopup();
 frame:SetWidth(350);
 frame:SetHeight(240);

 -- Condition dropdown.
 local dropdown = MSBTControls.CreateDropdown(frame);
 local objLocale = L.DROPDOWNS["triggerCondition"];
 dropdown:Configure(200, objLocale.label, objLocale.tooltip);
 dropdown:SetListboxHeight(200);
 dropdown:SetListboxWidth(200);
 dropdown:SetPoint("TOPLEFT", frame, "TOPLEFT", 20, -20);
 dropdown:SetChangeHandler(ConditionDropdownOnChange);
 frame.conditionDropdown = dropdown;

 -- Relation dropdown.
 local dropdown = MSBTControls.CreateDropdown(frame);
 local objLocale = L.DROPDOWNS["triggerRelation"];
 dropdown:Configure(120, objLocale.label, objLocale.tooltip);
 dropdown:SetListboxHeight(200);
 dropdown:SetPoint("TOPLEFT", frame.conditionDropdown, "BOTTOMLEFT", 0, -20);
 frame.relationDropdown = dropdown;
  
 -- Parameter editbox.
 local editbox = MSBTControls.CreateEditbox(frame);
 local objLocale = L.DROPDOWNS["triggerParameter"];
 editbox:Configure(0, objLocale.label, objLocale.tooltip);
 editbox:SetPoint("TOPLEFT", dropdown, "BOTTOMLEFT", 0, -20);
 editbox:SetPoint("RIGHT", frame, "RIGHT", -35, 0);
 frame.parameterEditbox = editbox;
 
 -- Parameter slider.
 local slider = MSBTControls.CreateSlider(frame);
 local objLocale = L.DROPDOWNS["triggerParameter"];
 slider:Configure(180, objLocale.label, objLocale.tooltip);
 slider:SetPoint("TOPLEFT", dropdown, "BOTTOMLEFT", 0, -30);
 frame.parameterSlider = slider;

 -- Parameter dropdown.
 local dropdown = MSBTControls.CreateDropdown(frame);
 local objLocale = L.DROPDOWNS["triggerParameter"];
 dropdown:Configure(150, objLocale.label, objLocale.tooltip);
 dropdown:SetListboxHeight(120);
 dropdown:SetPoint("TOPLEFT", frame.relationDropdown, "BOTTOMLEFT", 0, -20);
 frame.parameterDropdown = dropdown;

 -- Save button.
 local button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["genericSave"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -10, 20);
 button:SetClickHandler(
  function (this)
   EraseTable(returnSettings);
   returnSettings.conditionType = frame.conditionDropdown:GetSelectedID();
   returnSettings.conditionRelation = frame.relationDropdown:GetSelectedID();
   if (frame.parameterEditbox:IsShown()) then
    returnSettings.conditionValue = frame.parameterEditbox:GetText();
   elseif (frame.parameterSlider:IsShown()) then
    returnSettings.conditionValue = frame.parameterSlider:GetValue();
   elseif (frame.parameterDropdown:IsShown()) then
    returnSettings.conditionValue = frame.parameterDropdown:GetSelectedID();
   end
   if (frame.saveHandler) then frame.saveHandler(returnSettings, frame.saveArg1); end
   frame:Hide();
  end
 );

 -- Cancel button.
 button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["genericCancel"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("BOTTOMLEFT", frame, "BOTTOM", 10, 20);
 button:SetClickHandler(
  function (this)
   frame:Hide();
  end
 );

 return frame;
end


-- ****************************************************************************
-- Shows the popup trigger condition frame.
-- ****************************************************************************
local function ShowTriggerCondition(configTable)
 -- Don't do anything if required parameters weren't passed.
 if (not configTable or not configTable.anchorFrame or not configTable.parentFrame) then return; end

 -- Create the frame if it hasn't already been.
 if (not popupFrames.triggerConditionFrame) then popupFrames.triggerConditionFrame = CreateTriggerCondition(); end

 -- Set parent.
 local frame = popupFrames.triggerConditionFrame;
 ChangePopupParent(frame, configTable.parentFrame)

 -- Populate condition type.
 frame.conditionDropdown:Clear();
 for conditionType in string.gmatch(configTable.availableConditions, "[^%s]+") do
  frame.conditionDropdown:AddItem(L.TRIGGER_DATA[conditionType] or conditionType, conditionType);
 end 
 frame.conditionDropdown:Sort();
 frame.conditionDropdown:SetSelectedID(configTable.conditionType);
 ConditionDropdownOnChange(frame.conditionDropdown, configTable.conditionType);
 
 -- Populate the condition relation.
 frame.relationDropdown:SetSelectedID(configTable.conditionRelation);

 -- Populate the condition value.
 local conditionData = popupFrames.triggerFrame.conditionData[configTable.conditionType];
 local conditionValue = configTable.conditionValue;
 if (type(conditionValue) == "boolean") then conditionValue = tostring(conditionValue); end
 if (conditionData.controlType == "editbox") then
  frame.parameterEditbox:SetText(conditionValue);
 elseif (conditionData.controlType == "slider") then
  frame.parameterSlider:SetValue(conditionValue);
 elseif (conditionData.controlType == "dropdown") then
  frame.parameterDropdown:SetSelectedID(conditionValue);
 end


 -- Configure the frame.
 frame.saveHandler = configTable.saveHandler;
 frame.saveArg1 = configTable.saveArg1;
 frame.hideHandler = configTable.hideHandler;
 frame:ClearAllPoints();
 frame:SetPoint(configTable.anchorPoint or "TOPLEFT", configTable.anchorFrame, configTable.relativePoint or "BOTTOMLEFT");
 frame:Show();
 frame:Raise();
end


-------------------------------------------------------------------------------
-- Trigger main event frame functions.
-------------------------------------------------------------------------------

-- ****************************************************************************
-- Enables the controls on the trigger popup.
-- ****************************************************************************
local function EnableMainEventControls()
 for name, frame in pairs(popupFrames.mainEventFrame.controls) do
  if (frame.Enable) then frame:Enable(); end
 end
end


-- ****************************************************************************
-- Updates the main event conditions listbox.
-- ****************************************************************************
local function UpdateMainEventConditions()
 local frame = popupFrames.mainEventFrame;
 frame.conditionsListbox:Clear();
 for x = 1, #frame.eventConditions, 3 do
  frame.conditionsListbox:AddItem(x)
 end
end


-- ****************************************************************************
-- Saves the condition the user entered.
-- ****************************************************************************
local function SaveMainEventCondition(settings, conditionNum)
 local frame = popupFrames.mainEventFrame;
 frame.eventConditions[conditionNum] = settings.conditionType;
 frame.eventConditions[conditionNum+1] = settings.conditionRelation;
 frame.eventConditions[conditionNum+2] = settings.conditionValue;
 UpdateMainEventConditions();
end


-- ****************************************************************************
-- Called when one of the exception delete buttons is clicked.
-- ****************************************************************************
local function DeleteConditionButtonOnClick(this)
 local frame = popupFrames.mainEventFrame;
 local line = this:GetParent();
 table.remove(frame.eventConditions, line.conditionNum);
 table.remove(frame.eventConditions, line.conditionNum);
 table.remove(frame.eventConditions, line.conditionNum);
 UpdateMainEventConditions();
end


-- ****************************************************************************
-- Called when one of the main event edit buttons is clicked.
-- ****************************************************************************
local function EditConditionButtonOnClick(this)
 local frame = popupFrames.mainEventFrame;
 local line = this:GetParent();
 local eventType = frame.mainEventDropdown:GetSelectedID();
 local conditionData = popupFrames.triggerFrame.eventConditionData[eventType];

 EraseTable(tempConfig);
 tempConfig.conditionType = frame.eventConditions[line.conditionNum];
 tempConfig.conditionRelation = frame.eventConditions[line.conditionNum+1];
 tempConfig.conditionValue = frame.eventConditions[line.conditionNum+2];
 tempConfig.availableConditions = conditionData and conditionData.availableConditions;
 tempConfig.saveHandler = SaveMainEventCondition;
 tempConfig.saveArg1 = line.conditionNum;
 tempConfig.parentFrame = frame;
 tempConfig.anchorFrame = this;
 tempConfig.anchorPoint = "BOTTOMLEFT";
 tempConfig.relativePoint = "TOPLEFT";
 tempConfig.hideHandler = EnableMainEventControls;
 DisableControls(frame.controls);
 ShowTriggerCondition(tempConfig);
end


-- ****************************************************************************
-- Called by listbox to create a line for main event conditions.
-- ****************************************************************************
local function CreateMainEventConditionsLine(this)
 local controls = popupFrames.mainEventFrame.controls;
 local frame = CreateFrame("Button", nil, this);
 frame:EnableMouse(false);

 -- Edit condition button.
 local button = MSBTControls.CreateIconButton(frame, "Configure");
 local objLocale = L.BUTTONS["editCondition"];
 button:SetTooltip(objLocale.tooltip);
 button:SetPoint("LEFT", frame, "LEFT", 0, 0);
 button:SetClickHandler(EditConditionButtonOnClick);
 frame.editConditionButton = button;
 controls[#controls+1] = button; 
 
 -- Delete condition button.
 button = MSBTControls.CreateIconButton(frame, "Delete");
 objLocale = L.BUTTONS["deleteCondition"];
 button:SetTooltip(objLocale.tooltip);
 button:SetPoint("RIGHT", frame, "RIGHT", -10, -5);
 button:SetClickHandler(DeleteConditionButtonOnClick);
 controls[#controls+1] = button;
 
 -- Condition text.
 local fontString = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("LEFT", frame.editConditionButton, "RIGHT", 5, 0);
 fontString:SetPoint("RIGHT", controls[#controls], "LEFT", -10, 0);
 fontString:SetJustifyH("LEFT");
 fontString:SetTextColor(1, 1, 1);
 frame.conditionFontString = fontString;
 
 return frame;
end


-- ****************************************************************************
-- Called by listbox to display an exception line.
-- ****************************************************************************
local function DisplayMainEventConditionsLine(this, line, key, isSelected)
 line.conditionNum = key;

 local frame = popupFrames.mainEventFrame;
 local conditionType = frame.eventConditions[key];
 local conditionData = popupFrames.triggerFrame.conditionData[conditionType];
 local relation = conditionData and conditionData.relations[frame.eventConditions[key+1]];

 -- Get the localized parameter.
 local parameter = frame.eventConditions[key+2];
 if (type(parameter) == "boolean") then parameter = tostring(parameter); end
 if (conditionData and conditionData.controlType == "dropdown") then parameter = conditionData.items[parameter]; end

 local conditionText = L.TRIGGER_DATA[conditionType] or conditionType;
 if (relation) then conditionText = conditionText .. " - " .. relation; end
 if (parameter) then conditionText = conditionText .. " - " .. parameter; end
 
 line.conditionFontString:SetText(conditionText);
end



-- ****************************************************************************
-- Creates the popup main event frame.
-- ****************************************************************************
local function CreateMainEvent()
 local frame = CreatePopup();
 frame:SetWidth(450);
 frame:SetHeight(325);
 frame.controls = {};
 local controls = frame.controls;


 -- Main event dropdown.
 local dropdown =  MSBTControls.CreateDropdown(frame);
 local objLocale = L.DROPDOWNS["mainEvent"];
 dropdown:Configure(200, objLocale.label, nil);
 dropdown:SetListboxHeight(200);
 dropdown:SetPoint("TOPLEFT", frame, "TOPLEFT", 20, -40);
 dropdown:SetChangeHandler(
  function (this, id)
   EraseTable(frame.eventConditions);
   local conditionData = popupFrames.triggerFrame.eventConditionData[id];
   if (conditionData and conditionData.defaultConditions and conditionData.defaultConditions ~= "") then
    for conditionEntry in string.gmatch(conditionData.defaultConditions .. ";;", "(.-);;") do
     frame.eventConditions[#frame.eventConditions+1] = ConvertType(conditionEntry);
    end
   end
   UpdateMainEventConditions();
  end
 );
 for eventType in pairs(popupFrames.triggerFrame.eventConditionData) do
  dropdown:AddItem(L.TRIGGER_DATA[eventType] or eventType, eventType);
 end
 dropdown:Sort();
 frame.mainEventDropdown = dropdown;
 controls[#controls+1] = dropdown;


 -- Trigger conditions label.
 fontString = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("TOPLEFT", frame.mainEventDropdown, "BOTTOMLEFT", 0, -30);
 fontString:SetText(L.MSG_EVENT_CONDITIONS .. ":");
 frame.triggerConditionsLabel = fontString;
 
 -- Add event condition button.
 button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["addEventCondition"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("LEFT", frame.triggerConditionsLabel, "RIGHT", 10, 0);
 button:SetClickHandler(
  function (this)
   local eventType = frame.mainEventDropdown:GetSelectedID();
   local conditionData = popupFrames.triggerFrame.eventConditionData[eventType];
   local conditionType, conditionRelation, conditionValue;
   if (conditionData and conditionData.defaultConditions ~= "") then
    _, _, conditionType, conditionRelation, conditionValue = string.find(conditionData.defaultConditions, "(.-);;(.-);;(.-)");
	conditionValue = conditionValue and ConvertType(conditionValue);
	if (type(conditionValue == "boolean")) then conditionValue = tostring(conditionValue); end
   end
   EraseTable(tempConfig);
   tempConfig.conditionType = conditionType or "skillName";
   tempConfig.conditionRelation = conditionRelation or "eq";
   tempConfig.conditionValue = conditionValue or "";
   tempConfig.availableConditions = conditionData and conditionData.availableConditions;
   tempConfig.saveHandler = SaveMainEventCondition;
   tempConfig.saveArg1 = #frame.eventConditions+1;
   tempConfig.parentFrame = frame;
   tempConfig.anchorFrame = this;
   tempConfig.anchorPoint = "BOTTOMLEFT";
   tempConfig.relativePoint = "TOPLEFT";
   tempConfig.hideHandler = EnableMainEventControls;
   DisableControls(frame.controls);
   ShowTriggerCondition(tempConfig);
  end
 );
 controls[#controls+1] = button;

 -- Main event conditions listbox.
 listbox = MSBTControls.CreateListbox(frame);
 listbox:Configure(400, 100, 25);
 listbox:SetPoint("TOPLEFT", frame.triggerConditionsLabel, "BOTTOMLEFT", 10, -10);
 listbox:SetCreateLineHandler(CreateMainEventConditionsLine);
 listbox:SetDisplayHandler(DisplayMainEventConditionsLine);
 frame.conditionsListbox = listbox;
 controls[#controls+1] = listbox;

 

 -- Save button.
 local button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["genericSave"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -10, 20);
 button:SetClickHandler(
  function (this)
   EraseTable(returnSettings);
   returnSettings.eventType = frame.mainEventDropdown:GetSelectedID();
   returnSettings.eventConditions = {};
   for _, conditionEntry in ipairs(frame.eventConditions) do
    returnSettings.eventConditions[#returnSettings.eventConditions+1] = conditionEntry;
   end
   if (frame.saveHandler) then frame.saveHandler(returnSettings, frame.saveArg1); end
   frame:Hide();
  end
 );
 controls[#controls+1] = button;

 -- Cancel button.
 button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["genericCancel"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("BOTTOMLEFT", frame, "BOTTOM", 10, 20);
 button:SetClickHandler(
  function (this)
   frame:Hide();
  end
 );
 controls[#controls+1] = button;

 frame.eventConditions = {};
 
 return frame;
end


-- ****************************************************************************
-- Shows the popup main event frame.
-- ****************************************************************************
local function ShowMainEvent(configTable)
 -- Don't do anything if required parameters weren't passed.
 if (not configTable or not configTable.anchorFrame or not configTable.parentFrame) then return; end

 -- Create the frame if it hasn't already been.
 if (not popupFrames.mainEventFrame) then popupFrames.mainEventFrame = CreateMainEvent(); end

 -- Set parent.
 local frame = popupFrames.mainEventFrame;
 ChangePopupParent(frame, configTable.parentFrame)

 -- Populate data.
 frame.mainEventDropdown:SetSelectedID(configTable.eventType);
 
 EraseTable(frame.eventConditions);
 for _, conditionEntry in ipairs(configTable.eventConditions) do
  frame.eventConditions[#frame.eventConditions+1] = conditionEntry;
 end
 UpdateMainEventConditions();

 -- Configure the frame.
 frame.saveHandler = configTable.saveHandler;
 frame.saveArg1 = configTable.saveArg1;
 frame.hideHandler = configTable.hideHandler;
 frame:ClearAllPoints();
 frame:SetPoint(configTable.anchorPoint or "TOPLEFT", configTable.anchorFrame, configTable.relativePoint or "BOTTOMLEFT");
 frame:Show();
 frame:Raise();
end


-------------------------------------------------------------------------------
-- Trigger frame functions.
-------------------------------------------------------------------------------

-- ****************************************************************************
-- Updates the classes font string based on what classes are selected.
-- ****************************************************************************
local function UpdateClassesText()
 local frame = popupFrames.triggerFrame;
 
 -- Get localized list of seleced classes.
 local selectedClasses = "";
 if (frame.classes["ALL"]) then
  selectedClasses = L.CHECKBOXES["allClasses"].label;
 else
  for className in pairs(frame.classes) do
   selectedClasses = selectedClasses .. L.CLASS_NAMES[className] .. ", ";
  end

  -- Strip off the extra comma and space.
  selectedClasses = string.sub(selectedClasses, 1, -3);
 end

 frame.classesFontString:SetText(selectedClasses);
end


-- ****************************************************************************
-- Updates the main events listbox.
-- ****************************************************************************
local function UpdateMainEvents()
 local frame = popupFrames.triggerFrame;
 frame.mainEventsListbox:Clear();
 for index, mainEvent in pairs(frame.mainEvents) do
  frame.mainEventsListbox:AddItem(index)
 end
end


-- ****************************************************************************
-- Updates the exceptions listbox.
-- ****************************************************************************
local function UpdateExceptions()
 local frame = popupFrames.triggerFrame;
 frame.exceptionsListbox:Clear();
 for x = 1, #frame.exceptions, 3 do
  frame.exceptionsListbox:AddItem(x)
 end
end


-- ****************************************************************************
-- Enables the controls on the trigger popup.
-- ****************************************************************************
local function EnableTriggerControls()
 for name, frame in pairs(popupFrames.triggerFrame.controls) do
  if (frame.Enable) then frame:Enable(); end
 end
end


-- ****************************************************************************
-- Saves the main event the user entered to the trigger main event frame.
-- ****************************************************************************
local function SaveMainEvent(settings, eventNum)
 local frame = popupFrames.triggerFrame;
 frame.mainEvents[eventNum] = settings.eventType;
 frame.eventConditions[eventNum] = settings.eventConditions;
 UpdateMainEvents();
end


-- ****************************************************************************
-- Saves the exception the user entered to the trigger exception frame.
-- ****************************************************************************
local function SaveException(settings, exceptionNum)
 local frame = popupFrames.triggerFrame;
 frame.exceptions[exceptionNum] = settings.conditionType;
 frame.exceptions[exceptionNum+1] = settings.conditionRelation;
 frame.exceptions[exceptionNum+2] = settings.conditionValue;
 UpdateExceptions();
end


-- ****************************************************************************
-- Called when one of the main event edit buttons is clicked.
-- ****************************************************************************
local function EditMainEventButtonOnClick(this)
 local frame = popupFrames.triggerFrame;
 local line = this:GetParent();

 EraseTable(tempConfig);
 tempConfig.eventType = frame.mainEvents[line.eventNum];
 tempConfig.eventConditions = frame.eventConditions[line.eventNum];
 tempConfig.saveHandler = SaveMainEvent;
 tempConfig.saveArg1 = line.eventNum;
 tempConfig.parentFrame = frame;
 tempConfig.anchorFrame = this;
 tempConfig.hideHandler = EnableTriggerControls;
 DisableControls(frame.controls);
 ShowMainEvent(tempConfig);
end


-- ****************************************************************************
-- Called when one of the main event edit buttons is clicked.
-- ****************************************************************************
local function EditExceptionButtonOnClick(this)
 local frame = popupFrames.triggerFrame;
 local line = this:GetParent();

 EraseTable(tempConfig);
 tempConfig.conditionType = frame.exceptions[line.exceptionNum];
 tempConfig.conditionRelation = frame.exceptions[line.exceptionNum+1];
 tempConfig.conditionValue = frame.exceptions[line.exceptionNum+2];
 tempConfig.availableConditions = frame.availableExceptions;
 tempConfig.saveHandler = SaveException;
 tempConfig.saveArg1 = line.exceptionNum;
 tempConfig.parentFrame = frame;
 tempConfig.anchorFrame = this;
 tempConfig.anchorPoint = "BOTTOMLEFT";
 tempConfig.relativePoint = "TOPLEFT";
 tempConfig.hideHandler = EnableTriggerControls;
 DisableControls(frame.controls);
 ShowTriggerCondition(tempConfig);
end


-- ****************************************************************************
-- Called when one of the main event delete buttons is clicked.
-- ****************************************************************************
local function DeleteMainEventButtonOnClick(this)
 local frame = popupFrames.triggerFrame;
 local line = this:GetParent();
 table.remove(frame.mainEvents, line.eventNum);
 table.remove(frame.eventConditions, line.eventNum);
 UpdateMainEvents();
end


-- ****************************************************************************
-- Called when one of the exception delete buttons is clicked.
-- ****************************************************************************
local function DeleteExceptionButtonOnClick(this)
 local frame = popupFrames.triggerFrame;
 local line = this:GetParent();
 table.remove(frame.exceptions, line.exceptionNum);
 table.remove(frame.exceptions, line.exceptionNum);
 table.remove(frame.exceptions, line.exceptionNum);
 UpdateExceptions();
end


-- ****************************************************************************
-- Called by listbox to create a line for main events.
-- ****************************************************************************
local function CreateMainEventsLine(this)
 local controls = popupFrames.triggerFrame.controls;
 local frame = CreateFrame("Button", nil, this);
 frame:EnableMouse(false);

 -- Edit event button.
 local button = MSBTControls.CreateIconButton(frame, "Configure");
 local objLocale = L.BUTTONS["editEventConditions"];
 button:SetTooltip(objLocale.tooltip);
 button:SetPoint("LEFT", frame, "LEFT", 0, 0);
 button:SetClickHandler(EditMainEventButtonOnClick);
 frame.editEventButton = button;
 controls[#controls+1] = button;
 
 
 -- Delete event button.
 button = MSBTControls.CreateIconButton(frame, "Delete");
 objLocale = L.BUTTONS["deleteMainEvent"];
 button:SetTooltip(objLocale.tooltip);
 button:SetPoint("RIGHT", frame, "RIGHT", -10, 0);
 button:SetClickHandler(DeleteMainEventButtonOnClick);
 controls[#controls+1] = button;
 
 -- Event text.
 local fontString = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("LEFT", frame.editEventButton, "RIGHT", 5, 0);
 fontString:SetPoint("RIGHT", controls[#controls], "LEFT", -10, 0);
 fontString:SetJustifyH("LEFT");
 fontString:SetTextColor(1, 1, 1);
 frame.eventFontString = fontString;

 return frame;
end


-- ****************************************************************************
-- Called by listbox to create a line for main events.
-- ****************************************************************************
local function CreateExceptionsLine(this)
 local controls = popupFrames.triggerFrame.controls;
 local frame = CreateFrame("Button", nil, this);
 frame:EnableMouse(false);

 -- Edit exception button.
 local button = MSBTControls.CreateIconButton(frame, "Configure");
 local objLocale = L.BUTTONS["editCondition"];
 button:SetTooltip(objLocale.tooltip);
 button:SetPoint("LEFT", frame, "LEFT", 0, 0);
 button:SetClickHandler(EditExceptionButtonOnClick);
 frame.editExceptionButton = button;
 controls[#controls+1] = button; 
 
 -- Delete exception button.
 button = MSBTControls.CreateIconButton(frame, "Delete");
 objLocale = L.BUTTONS["deleteCondition"];
 button:SetTooltip(objLocale.tooltip);
 button:SetPoint("RIGHT", frame, "RIGHT", -10, -5);
 button:SetClickHandler(DeleteExceptionButtonOnClick);
 controls[#controls+1] = button;
 
 -- Exception text.
 local fontString = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("LEFT", frame.editExceptionButton, "RIGHT", 5, 0);
 fontString:SetPoint("RIGHT", controls[#controls], "LEFT", -10, 0);
 fontString:SetJustifyH("LEFT");
 fontString:SetTextColor(1, 1, 1);
 frame.exceptionFontString = fontString;
 
 return frame;
end


-- ****************************************************************************
-- Called by listbox to display a main event line.
-- ****************************************************************************
local function DisplayMainEventsLine(this, line, key, isSelected)
 line.eventNum = key;

 local frame = popupFrames.triggerFrame;
 local eventType = frame.mainEvents[key];
 local eventText = L.TRIGGER_DATA[eventType] or UNKNOWN;
 local eventConditions = frame.eventConditions[key];
 
 local numConditions = #eventConditions / 3;
 eventText = eventText .. " - " .. numConditions .. " " .. (numConditions == 1 and L.MSG_CONDITION or L.MSG_CONDITIONS);

 line.eventFontString:SetText(eventText);
end


-- ****************************************************************************
-- Called by listbox to display an exception line.
-- ****************************************************************************
local function DisplayExceptionsLine(this, line, key, isSelected)
 line.exceptionNum = key;

 local frame = popupFrames.triggerFrame;
 local exceptionType = frame.exceptions[key];
 local conditionData = frame.conditionData[exceptionType];
 local relation = conditionData.relations[frame.exceptions[key+1]];

 -- Get the localized parameter.
 local parameter = frame.exceptions[key+2];
 if (type(parameter) == "boolean") then parameter = tostring(parameter); end
 if (conditionData.controlType == "dropdown") then parameter = conditionData.items[parameter]; end

 local exceptionText = L.TRIGGER_DATA[exceptionType] or exceptionType;
 if (relation) then exceptionText = exceptionText .. " - " .. relation; end
 if (parameter) then exceptionText = exceptionText .. " - " .. parameter; end
 
 line.exceptionFontString:SetText(exceptionText);
end


-- ****************************************************************************
-- Creates the popup trigger settings frame.
-- ****************************************************************************
local function CreateTriggerPopup()
 local frame = CreatePopup();
 frame:SetWidth(500);
 frame:SetHeight(460);
 frame.controls = {};
 local controls = frame.controls;

 -- Title text.
 local fontString = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("TOP", frame, "TOP", 0, -20);
 frame.titleFontString = fontString;

 -- Trigger classes label.
 fontString = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("TOPLEFT", frame, "TOPLEFT", 20, -50);
 fontString:SetText(L.MSG_TRIGGER_CLASSES .. ":");
 frame.classesLabel = fontString;

 -- Edit trigger classes button.
 local button = MSBTControls.CreateIconButton(frame, "Configure");
 local objLocale = L.BUTTONS["editTriggerClasses"];
 button:SetTooltip(objLocale.tooltip);
 button:SetPoint("TOPLEFT", frame.classesLabel, "BOTTOMLEFT", 10, -5);
 button:SetClickHandler(
  function (this)
   EraseTable(tempConfig);
   tempConfig.parentFrame = frame;
   tempConfig.anchorFrame = this;
   tempConfig.classes = frame.classes;
   tempConfig.updateHandler = UpdateClassesText;
   tempConfig.hideHandler = EnableTriggerControls;
   DisableControls(controls);
   ShowClasses(tempConfig);
  end
 );
 controls[#controls+1] = button;

 -- Classes text.
 fontString = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("LEFT", controls[#controls], "RIGHT", 10, -5);
 fontString:SetPoint("RIGHT", frame, "RIGHT", -20, 0);
 fontString:SetHeight(30);
 fontString:SetJustifyH("LEFT");
 fontString:SetJustifyV("TOP");
 fontString:SetTextColor(1, 1, 1);
 frame.classesFontString = fontString;

 -- Main events label.
 fontString = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("TOPLEFT", controls[#controls], "BOTTOMLEFT", -10, -15);
 fontString:SetText(L.MSG_MAIN_EVENTS .. ":");
 frame.mainEventsLabel = fontString;
 
 -- Add main events button.
 button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["addMainEvent"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("LEFT", frame.mainEventsLabel, "RIGHT", 10, 0);
 button:SetClickHandler(
  function (this)
   EraseTable(tempConfig);
   tempConfig.eventType = "SPELL_AURA_APPLIED";
   tempConfig.eventConditions = {"recipientAffiliation", "eq", FLAG_YOU, "skillName", "eq", UNKNOWN };
   tempConfig.saveHandler = SaveMainEvent;
   tempConfig.saveArg1 = #frame.mainEvents + 1;
   tempConfig.parentFrame = frame;
   tempConfig.anchorFrame = this;
   tempConfig.hideHandler = EnableTriggerControls;
   DisableControls(frame.controls);
   ShowMainEvent(tempConfig);
  end
 );
 controls[#controls+1] = button;

 -- Main events listbox.
 local listbox = MSBTControls.CreateListbox(frame);
 listbox:Configure(450, 100, 25);
 listbox:SetPoint("TOPLEFT", frame.mainEventsLabel, "BOTTOMLEFT", 10, -10);
 listbox:SetCreateLineHandler(CreateMainEventsLine);
 listbox:SetDisplayHandler(DisplayMainEventsLine);
 frame.mainEventsListbox = listbox;
 controls[#controls+1] = listbox;


 -- Trigger exceptions label.
 fontString = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("TOPLEFT", frame.mainEventsListbox, "BOTTOMLEFT", -10, -15);
 fontString:SetText(L.MSG_TRIGGER_EXCEPTIONS .. ":");
 frame.triggerExceptionsLabel = fontString;
 
 -- Add trigger exceptions button.
 button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["addTriggerException"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("LEFT", frame.triggerExceptionsLabel, "RIGHT", 10, 0);
 button:SetClickHandler(
  function (this)
   EraseTable(tempConfig);
   tempConfig.conditionType = "recentlyFired";
   tempConfig.conditionRelation = "lt";
   tempConfig.conditionValue = 5;
   tempConfig.availableConditions = frame.availableExceptions;
   tempConfig.saveHandler = SaveException;
   tempConfig.saveArg1 = #frame.exceptions+1;
   tempConfig.parentFrame = frame;
   tempConfig.anchorFrame = this;
   tempConfig.anchorPoint = "BOTTOMLEFT";
   tempConfig.relativePoint = "TOPLEFT";
   tempConfig.hideHandler = EnableTriggerControls;
   DisableControls(frame.controls);
   ShowTriggerCondition(tempConfig);
  end
 );
 controls[#controls+1] = button;

 -- Trigger exceptions listbox.
 listbox = MSBTControls.CreateListbox(frame);
 listbox:Configure(450, 100, 25);
 listbox:SetPoint("TOPLEFT", frame.triggerExceptionsLabel, "BOTTOMLEFT", 10, -10);
 listbox:SetCreateLineHandler(CreateExceptionsLine);
 listbox:SetDisplayHandler(DisplayExceptionsLine);
 frame.exceptionsListbox = listbox;
 controls[#controls+1] = listbox;

 -- Save button.
 button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["genericSave"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -10, 20);
 button:SetClickHandler(
  function (this)
   EraseTable(returnSettings);
   -- Make the classes string.
   if (not frame.classes["ALL"]) then
    local sortedClasses = frame.sortedClasses;
    EraseTable(sortedClasses);
    for class in pairs(frame.classes) do
     sortedClasses[#sortedClasses+1] = class;
    end
    table.sort(sortedClasses);
    returnSettings.classes = table.concat(sortedClasses, ",");
   end

   -- Make the main events string.
   if (next(frame.mainEvents)) then
    local events = "";
    for eventNum, eventType in ipairs(frame.mainEvents) do
     events = events .. eventType .. "{"
     if (next(frame.eventConditions[eventNum])) then
      for _, conditionEntry in ipairs(frame.eventConditions[eventNum]) do
       events = events .. tostring(conditionEntry) .. ";;";
      end
      events = string.sub(events, 1, -3);
     end
     events = events .. "}&&";
    end
    returnSettings.mainEvents = string.sub(events, 1, -3);
   end

   -- Make the exceptions string.
   if (next(frame.exceptions)) then
    local exceptions = "";
    for x = 1, #frame.exceptions, 3 do
     exceptions = exceptions .. string.format("%s;;%s;;%s;;", tostring(frame.exceptions[x]), tostring(frame.exceptions[x+1]), tostring(frame.exceptions[x+2]));
    end
    returnSettings.exceptions = string.sub(exceptions, 1, -3);
   end

   if (frame.saveHandler) then frame.saveHandler(returnSettings, frame.saveArg1); end
   frame:Hide();
  end
 );
 controls[#controls+1] = button;

 -- Cancel button.
 button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["genericCancel"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("BOTTOMLEFT", frame, "BOTTOM", 10, 20);
 button:SetClickHandler(
  function (this)
   frame:Hide();
  end
 );
 controls[#controls+1] = button;

 frame.classes = {};
 frame.sortedClasses = {};
 frame.mainEvents = {};
 frame.eventConditions = {};
 frame.exceptions = {};
 frame.sortedKeys = {};

 -- Relations.
 local objLocale = L.TRIGGER_DATA;
 local equalityRelations = {eq = objLocale["eq"]};
 local stringRelations = {eq = objLocale["eq"], ne = objLocale["ne"], like = objLocale["like"], unlike = objLocale["unlike"]};
 local numberRelations = {eq = objLocale["eq"], ne = objLocale["ne"], lt = objLocale["lt"], gt = objLocale["gt"]};
 local booleanRelations = {eq = objLocale["eq"], ne = objLocale["ne"]};
 local lessThanRelations = {lt = objLocale["lt"]};
 
 -- Localized warrior stances.
 local warriorStances = {
  [1] = GetSpellInfo(2457),
  [2] = GetSpellInfo(71),
  [3] = GetSpellInfo(2458),
 };

 -- Localized affiliations.
 local affiliationTypes = {
  [MSBTParser.AFFILIATION_MINE] = objLocale["affiliationMine"],
  [MSBTParser.AFFILIATION_PARTY] = objLocale["affiliationParty"],
  [MSBTParser.AFFILIATION_RAID] = objLocale["affiliationRaid"],
  [MSBTParser.AFFILIATION_OUTSIDER] = objLocale["affiliationOutsider"],
  [MSBTParser.TARGET_TARGET] = objLocale["affiliationTarget"],
  [MSBTParser.TARGET_FOCUS] = objLocale["affiliationFocus"],
  [FLAG_YOU] = objLocale["affiliationYou"],
 };

 -- Localized reactions.
 local reactionTypes = {
  [MSBTParser.REACTION_FRIENDLY] = objLocale["reactionFriendly"],
  [MSBTParser.REACTION_NEUTRAL] = objLocale["reactionNeutral"],
  [MSBTParser.REACTION_HOSTILE] = objLocale["reactionHostile"],
 };

 -- Localized control types.
 local controlTypes = {
  [MSBTParser.CONTROL_HUMAN] = objLocale["controlHuman"],
  [MSBTParser.CONTROL_SERVER] = objLocale["controlServer"],
 };

 -- Localized unit types.
 local unitTypes = {
  [MSBTParser.UNITTYPE_PLAYER] = objLocale["unitTypePlayer"],
  [MSBTParser.UNITTYPE_NPC] = objLocale["unitTypeNPC"],
  [MSBTParser.UNITTYPE_PET] = objLocale["unitTypePet"],
  [MSBTParser.UNITTYPE_GUARDIAN] = objLocale["unitTypeGuardian"],
  [MSBTParser.UNITTYPE_OBJECT] = objLocale["unitTypeObject"],
 };
 
 -- Miss types.
 local missTypes = {
  ["MISS"] = MISS,
  ["DODGE"] = DODGE,
  ["PARRY"] = PARRY,
  ["BLOCK"] = BLOCK,
  ["RESIST"] = RESIST,
  ["ABSORB"] = ABSORB,
  ["IMMUNE"] = IMMUNE,
  ["EVADE"] = EVADE,
  ["REFLECT"] = REFLECT,
 };
 
 -- Hazard type.
 local hazardTypes = {
  ["DROWNING"] = STRING_ENVIRONMENTAL_DAMAGE_DROWNING,
  ["FALLING"] = STRING_ENVIRONMENTAL_DAMAGE_FALLING,
  ["FATIGUE "] = STRING_ENVIRONMENTAL_DAMAGE_FATIGUE,
  ["FIRE"] = STRING_ENVIRONMENTAL_DAMAGE_FIRE,
  ["LAVA"] = STRING_ENVIRONMENTAL_DAMAGE_LAVA,
  ["SLIME"] = STRING_ENVIRONMENTAL_DAMAGE_SLIME,
 };
 
 local auraTypes = {
  BUFF = objLocale["auraTypeBuff"],
  DEBUFF = objLocale["auraTypeDebuff"],
 };
 
 local unitIDs = {
  player = YOU,
  target = TARGET,
  focus = FOCUS,
  pet = PET,
  party = objLocale["affiliationParty"],
  party1 = objLocale["affiliationParty"] .. " 1",
  party2 = objLocale["affiliationParty"] .. " 2",
  party3 = objLocale["affiliationParty"] .. " 3",
  party4 = objLocale["affiliationParty"] .. " 4",
  party5 = objLocale["affiliationParty"] .. " 5",
  raid = objLocale["affiliationRaid"],
 }
 
 -- Localized booleans.
 local booleanItems = {["true"] = objLocale["booleanTrue"], ["false"] = objLocale["booleanFalse"]};

 -- Localized zone types.
 local zoneTypes = {arena = objLocale["zoneTypeArena"], pvp = objLocale["zoneTypePvP"], party = objLocale["zoneTypeParty"], raid = objLocale["zoneTypeRaid"]};
 
 

 -- Condition data.
 frame.conditionData = {
  -- Main event conditions.
  -- Source unit.
  sourceName = {controlType = "editbox", relations = stringRelations},
  sourceAffiliation = {controlType = "dropdown", items = affiliationTypes, default=FLAG_YOU, relations = booleanRelations},
  sourceReaction = {controlType = "dropdown", items = reactionTypes, default=MSBTParser.REACTION_HOSTILE, relations = booleanRelations},
  sourceControl = {controlType = "dropdown", items = controlTypes, default=MSBTParser.CONTROL_HUMAN, relations = booleanRelations},
  sourceUnitType = {controlType = "dropdown", items = unitTypes, default=MSBTParser.UNITTYPE_PLAYER, relations = booleanRelations},

  -- Recipient unit.
  recipientName = {controlType = "editbox", relations = stringRelations},
  recipientAffiliation = {controlType = "dropdown", items = affiliationTypes, default=FLAG_YOU, relations = booleanRelations},
  recipientReaction = {controlType = "dropdown", items = reactionTypes, default=MSBTParser.REACTION_HOSTILE, relations = booleanRelations},
  recipientControl = {controlType = "dropdown", items = controlTypes, default=MSBTParser.CONTROL_HUMAN, relations = booleanRelations},
  recipientUnitType = {controlType = "dropdown", items = unitTypes, default=MSBTParser.UNITTYPE_PLAYER, relations = booleanRelations},

  -- Skill.
  skillID = {controlType = "editbox", relations = booleanRelations},
  skillName = {controlType = "editbox", relations = stringRelations},
  skillSchool = {controlType = "dropdown", items = MSBTMain.damageTypeMap, default=0x1, relations = booleanRelations},
  
  -- Extra skill.
  extraSkillID = {controlType = "editbox", relations = booleanRelations},
  extraSkillName = {controlType = "editbox", relations = stringRelations},
  extraSkillSchool = {controlType = "dropdown", items = MSBTMain.damageTypeMap, default=0x1, relations = booleanRelations},

  -- Damage/heal.
  amount = {controlType = "editbox", relations = numberRelations},
  damageType = {controlType = "dropdown", items = MSBTMain.damageTypeMap, default=0x1, relations = booleanRelations},
  resistAmount = {controlType = "editbox", relations = numberRelations},
  blockAmount = {controlType = "editbox", relations = numberRelations},
  absorbAmount = {controlType = "editbox", relations = numberRelations},
  isCrit = {controlType = "dropdown", items = booleanItems, default = "true", relations = booleanRelations},
  isGlancing = {controlType = "dropdown", items = booleanItems, default = "true", relations = booleanRelations},
  isCrushing = {controlType = "dropdown", items = booleanItems, default = "true", relations = booleanRelations},

  -- Miss/environmental/power.
  missType = {controlType = "dropdown", items = missTypes, default="MISS", relations = booleanRelations},
  hazardType = {controlType = "dropdown", items = hazardTypes, default="FALLING", relations = booleanRelations},
  powerType = {controlType = "dropdown", items = MSBTMain.powerTypes, default=0, relations = booleanRelations},
  extraAmount = {controlType = "editbox", relations = numberRelations},
  
  -- Aura.
  auraType = {controlType = "dropdown", items = auraTypes, default="BUFF", relations = booleanRelations},

  -- Health/power changes.
  threshold = {controlType = "slider", minValue=1, maxValue=100, step=1, default=40, relations=numberRelations, defaultRelation = "lt"},
  unitID = {controlType = "dropdown", items = unitIDs, default="player", relations = booleanRelations},
  unitReaction = {controlType = "dropdown", items = reactionTypes, default=MSBTParser.REACTION_HOSTILE, relations = booleanRelations},

  -- Exception conditions.
  buffActive = {controlType = "editbox", relations = equalityRelations},
  currentCP = {controlType = "slider", minValue = 1, maxValue = 5, step = 1, default = 5, relations = numberRelations, defaultRelation = "lt"},
  currentPower = {controlType = "slider", minValue = 1, maxValue = 100, step = 1, default = 20, relations = numberRelations, defaultRelation = "lt"},
  recentlyFired = {controlType = "slider", minValue = 1, maxValue = 30, step = 1, default = 5, relations = lessThanRelations, defaultRelation = "lt"},
  trivialTarget = {controlType = "dropdown", items = booleanItems, default = "false", relations = booleanRelations},
  unavailableSkill = {controlType = "editbox", relations = equalityRelations},
  warriorStance = {controlType = "dropdown", items = warriorStances, default = 1, relations = booleanRelations},
  zoneName = {controlType = "editbox", relations = stringRelations},
  zoneType = {controlType = "dropdown", items = zoneTypes, default="arena", relations = booleanRelations},
 };

 -- Event condition data.
 local commonSourceFields = "sourceName sourceAffiliation sourceReaction sourceControl sourceUnitType ";
 local commonRecipientFields = "recipientName recipientAffiliation recipientReaction recipientControl recipientUnitType ";
 local commonLogFields = commonSourceFields .. commonRecipientFields;
 local commonSkillFields = "skillID skillName skillSchool ";
 local commonDamageFields = "amount damageType resistAmount blockAmount absorbAmount isCrit isGlancing isCrushing";
 local commonExtraSkillFields = "extraSkillID extraSkillName extraSkillSchool ";
 local commonHealFields = "amount isCrit";
 local commonPowerFields = "amount powerType";
 local commonHealthPowerFields = "unitID unitReaction amount threshold";
 local eventConditionData = {
  -- Damage events.
  SWING_DAMAGE = {availableConditions = commonLogFields .. commonDamageFields, defaultConditions="sourceAffiliation;;eq;;" .. FLAG_YOU .. ";;isCrit;;eq;;true"},
  SPELL_DAMAGE = {availableConditions = commonLogFields .. commonSkillFields .. commonDamageFields, defaultConditions="sourceAffiliation;;eq;;" .. FLAG_YOU .. ";;isCrit;;eq;;true;;skillName;;eq;;" .. UNKNOWN},
  
  -- Miss events.
  SWING_MISSED = {availableConditions = commonLogFields .. "missType", defaultConditions="recipientAffiliation;;eq;;" .. FLAG_YOU .. ";;missType;;eq;;BLOCK"},
  SPELL_MISSED = {availableConditions = commonLogFields .. commonSkillFields .. "missType", defaultConditions="recipientAffiliation;;eq;;" .. FLAG_YOU .. ";;missType;;eq;;RESIST;;skillName;;eq;;" .. UNKNOWN},
  SPELL_DISPEL_FAILED = {availableConditions = commonLogFields .. commonSkillFields .. commonExtraSkillFields .. "missType", defaultConditions="sourceAffiliation;;eq;;" .. FLAG_YOU .. ";;skillName;;eq;;" .. UNKNOWN},

  -- Heal events.
  SPELL_HEAL = {availableConditions = commonLogFields .. commonSkillFields .. commonHealFields, defaultConditions="recipientReaction;;eq;;" .. MSBTParser.REACTION_HOSTILE .. ";;isCrit;;eq;;true"},

  -- Environmental events.
  ENVIRONMENTAL_DAMAGE = {availableConditions = commonLogFields .. commonDamageFields .. " hazardType", defaultConditions="recipientAffiliation;;eq;;" .. FLAG_YOU .. ";;hazardType;;eq;;DROWNING"},

  -- Power events.
  SPELL_ENERGIZE = {availableConditions = commonLogFields .. commonSkillFields .. commonPowerFields, defaultConditions="recipientAffiliation;;eq;;" .. FLAG_YOU .. ";;powerType;;eq;;0"},
  SPELL_DRAIN = {availableConditions = commonLogFields .. commonSkillFields .. commonPowerFields .. " extraAmount", defaultConditions="recipientAffiliation;;eq;;" .. FLAG_YOU .. ";;powerType;;eq;;0"},

  -- Interrupt events.
  SPELL_INTERRUPT = {availableConditions = commonLogFields .. commonSkillFields .. commonExtraSkillFields, defaultConditions="recipientAffiliation;;eq;;" .. FLAG_YOU},

  -- Aura events.
  SPELL_AURA_APPLIED = {availableConditions = commonLogFields .. commonSkillFields .. "auraType amount", defaultConditions="recipientAffiliation;;eq;;" .. FLAG_YOU .. ";;skillName;;eq;;" .. UNKNOWN},

  -- Enchant events.
  ENCHANT_APPLIED = {availableConditions = commonLogFields .. "skillName itemID itemName", defaultConditions="skillName;;eq;;" .. UNKNOWN},
  
  -- Dispel events.
  SPELL_DISPEL = {availableConditions = commonLogFields .. commonSkillFields .. commonExtraSkillFields .. " auraType", defaultConditions="recipientAffiliation;;eq;;" .. FLAG_YOU .. ";;skillName;;eq;;" .. UNKNOWN},

  -- Cast events.
  SPELL_CAST_START = {availableConditions = commonSourceFields .. commonSkillFields, defaultConditions="sourceReaction;;eq;;" .. MSBTParser.REACTION_HOSTILE .. ";;skillName;;eq;;" .. UNKNOWN},

  -- Kill events.
  PARTY_KILL = {availableConditions = commonLogFields, defaultConditions="recipientName;;eq;;" .. UNKNOWN},

  -- Extra Attack events.
  SPELL_EXTRA_ATTACKS = {availableConditions = commonLogFields .. commonSkillFields .. "amount", defaultConditions="sourceAffiliation;;eq;;" .. FLAG_YOU .. ";;skillName;;eq;;" .. UNKNOWN},
  
  -- Threshold events.
  UNIT_HEALTH = {availableConditions = commonHealthPowerFields, defaultConditions="unitID;;eq;;player;;threshold;;lt;;20"},

  -- Skill cooldowns.
  SKILL_COOLDOWN = {availableConditions = "skillName", defaultConditions="skillName;;eq;;" .. UNKNOWN},
 };
 eventConditionData["RANGE_DAMAGE"] = eventConditionData["SPELL_DAMAGE"];
 eventConditionData["GENERIC_DAMAGE"] = eventConditionData["SPELL_DAMAGE"];
 eventConditionData["SPELL_PERIODIC_DAMAGE"] = eventConditionData["SPELL_DAMAGE"];
 eventConditionData["DAMAGE_SHIELD"] = eventConditionData["SPELL_DAMAGE"];
 eventConditionData["DAMAGE_SPLIT"] = eventConditionData["SPELL_DAMAGE"];
 eventConditionData["RANGE_MISSED"] = eventConditionData["SPELL_MISSED"];
 eventConditionData["GENERIC_MISSED"] = eventConditionData["SPELL_MISSED"];
 eventConditionData["SPELL_PERIODIC_MISSED"] = eventConditionData["SPELL_MISSED"];
 eventConditionData["DAMAGE_SHIELD_MISSED"] = eventConditionData["SPELL_MISSED"];
 eventConditionData["SPELL_PERIODIC_HEAL"] = eventConditionData["SPELL_HEAL"];
 eventConditionData["SPELL_PERIODIC_ENERGIZE"] = eventConditionData["SPELL_ENERGIZE"];
 eventConditionData["SPELL_PERIODIC_DRAIN"] = eventConditionData["SPELL_DRAIN"];
 eventConditionData["SPELL_LEECH"] = eventConditionData["SPELL_DRAIN"];
 eventConditionData["SPELL_PERIODIC_LEECH"] = eventConditionData["SPELL_DRAIN"];
 eventConditionData["SPELL_AURA_REMOVED"] = eventConditionData["SPELL_AURA_APPLIED"];
 eventConditionData["SPELL_STOLEN"] = eventConditionData["SPELL_DISPEL"];
 eventConditionData["ENCHANT_REMOVED"] = eventConditionData["ENCHANT_APPLIED"];
 eventConditionData["SPELL_CAST_SUCCESS"] = eventConditionData["SPELL_CAST_START"];
 eventConditionData["SPELL_CAST_FAILED"] = eventConditionData["SPELL_CAST_START"]; -- Ignore failure reason.
 eventConditionData["SPELL_SUMMON"] = eventConditionData["SPELL_CAST_START"];
 eventConditionData["SPELL_CREATE"] = eventConditionData["SPELL_CAST_START"];
 eventConditionData["UNIT_DIED"] = eventConditionData["PARTY_KILL"];
 eventConditionData["UNIT_DESTROYED"] = eventConditionData["PARTY_KILL"];
 eventConditionData["UNIT_MANA"] = eventConditionData["UNIT_HEALTH"];
 eventConditionData["UNIT_ENERGY"] = eventConditionData["UNIT_HEALTH"];
 eventConditionData["UNIT_RAGE"] = eventConditionData["UNIT_HEALTH"];
 eventConditionData["UNIT_RUNIC_POWER"] = eventConditionData["UNIT_HEALTH"];

 frame.eventConditionData = eventConditionData;

 -- Available exceptions.
 frame.availableExceptions = "buffActive currentCP currentPower recentlyFired trivialTarget unavailableSkill warriorStance zoneName zoneType";

 return frame;
end


-- ****************************************************************************
-- Shows the popup trigger settings frame using the passed config.
-- ****************************************************************************
local function ShowTrigger(configTable)
 -- Don't do anything if required parameters weren't passed.
 if (not configTable or not configTable.anchorFrame or not configTable.parentFrame) then return; end

 -- Create the frame if it hasn't already been.
 if (not popupFrames.triggerFrame) then popupFrames.triggerFrame = CreateTriggerPopup(); end
 
 -- Set parent.
 local frame = popupFrames.triggerFrame;
 ChangePopupParent(frame, configTable.parentFrame)

 
 -- Populate data.
 local triggerKey = configTable.triggerKey;
 local settings = MSBTProfiles.currentProfile.triggers[triggerKey];
 frame.titleFontString:SetText(configTable.title); 

 -- Classes.
 EraseTable(frame.classes);
 if (settings.classes) then
  for className in string.gmatch(settings.classes, "[^,]+") do
   frame.classes[className] = true;
  end
 else
  frame.classes["ALL"] = true;
 end
 UpdateClassesText();

 -- Main events.
 local conditions;
 EraseTable(frame.mainEvents);
 EraseTable(frame.eventConditions);
 if (settings.mainEvents) then
  for eventType, eventConditions in string.gmatch(settings.mainEvents .. "&&", "(.-)%{(.-)%}&&") do
   frame.mainEvents[#frame.mainEvents+1] = eventType;
   conditions = {};
   if (eventConditions ~= "") then
    for conditionEntry in string.gmatch(eventConditions .. ";;", "(.-);;") do
     conditions[#conditions+1] = ConvertType(conditionEntry);
    end
   end
   frame.eventConditions[#frame.eventConditions+1] = conditions;
  end
 end
 UpdateMainEvents();
 
 -- Exceptions.
 EraseTable(frame.exceptions);
 if (settings.exceptions and settings.exceptions ~= "") then
  for exceptionCondition in string.gmatch(settings.exceptions .. ";;", "(.-);;") do
   frame.exceptions[#frame.exceptions+1] = ConvertType(exceptionCondition);
  end
 end
 UpdateExceptions();

 -- Configure the frame.
 frame.saveHandler = configTable.saveHandler;
 frame.saveArg1 = configTable.saveArg1;
 frame.hideHandler = configTable.hideHandler;
 frame:ClearAllPoints();
 frame:SetPoint(configTable.anchorPoint or "TOPLEFT", configTable.anchorFrame, configTable.relativePoint or "BOTTOMLEFT");
 frame:Show();
 frame:Raise();
end


-------------------------------------------------------------------------------
-- Skill list frame functions.
-------------------------------------------------------------------------------

-- ****************************************************************************
-- Enables the controls on the skill list popup.
-- ****************************************************************************
local function EnableSkillListControls()
 for name, frame in pairs(popupFrames.skillListFrame.controls) do
  if (frame.Enable) then frame:Enable(); end
 end
end


-- ****************************************************************************
-- Validates if the passed skill name does not already exist and is valid.
-- ****************************************************************************
local function ValidateSkillListName(skillName)
 if (not skillName or skillName == "") then
  return L.MSG_INVALID_SKILL_NAME;
 end

 if (popupFrames.skillListFrame.skills[skillName]) then
  return L.MSG_SKILL_ALREADY_EXISTS;
 end
end


-- ****************************************************************************
-- Adds the passed skill name to the list of skills.
-- ****************************************************************************
local function SaveSkillListName(settings)
 local skillName = settings.inputText;
 local frame = popupFrames.skillListFrame;
 if (frame.listType == "throttle") then
  frame.skills[skillName] = 3;
 elseif (frame.listType == "substitution") then
  frame.skills[skillName] = settings.secondInputText;
 else
  frame.skills[skillName] = true;
 end

 frame.skillsListbox:AddItem(skillName, true);
end


-- ****************************************************************************
-- Called when one of the delete skill buttons is pressed.
-- ****************************************************************************
local function DeleteSkillButtonOnClick(this)
 local line = this:GetParent();
 popupFrames.skillListFrame.skills[line.skillName] = false;
 popupFrames.skillListFrame.skillsListbox:RemoveItem(line.itemNumber);
end


-- ****************************************************************************
-- Called when one of the time slider changes.
-- ****************************************************************************
local function TimeSliderOnValueChanged(this, value)
 local line = this:GetParent();
 popupFrames.skillListFrame.skills[line.skillName] = value;
end


-- ****************************************************************************
-- Called by listbox to create a line for skill list popup.
-- ****************************************************************************
local function CreateSkillListLine(this)
 local controls = popupFrames.skillListFrame.controls;
 local frame = CreateFrame("Button", nil, this);
 frame:EnableMouse(false);

 -- Delete skill button.
 local button = MSBTControls.CreateIconButton(frame, "Delete");
 objLocale = L.BUTTONS["deleteSkill"];
 button:SetTooltip(objLocale.tooltip);
 button:SetPoint("RIGHT", frame, "RIGHT", -10, 0);
 button:SetClickHandler(DeleteSkillButtonOnClick);
 frame.deleteButton = button;
 controls[#controls+1] = button;

 -- Time slider.
 local slider = MSBTControls.CreateSlider(frame);
 objLocale = L.SLIDERS["skillThrottleTime"]; 
 slider:Configure(120, objLocale.label, objLocale.tooltip);
 slider:SetPoint("RIGHT", frame.deleteButton, "LEFT", -10, -5);
 slider:SetMinMaxValues(1, 5);
 slider:SetValueStep(1);
 slider:SetValueChangedHandler(TimeSliderOnValueChanged);
 frame.timeSlider = slider;
 controls[#controls+1] = slider;

 -- Skill name text.
 local fontString = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("LEFT", frame, "LEFT", 5, 0);
 fontString:SetPoint("RIGHT", frame.timeSlider, "LEFT", -10, 0);
 fontString:SetJustifyH("LEFT");
 fontString:SetTextColor(1, 1, 1);
 frame.skillFontString = fontString;

 return frame;
end


-- ****************************************************************************
-- Called by listbox to display a line.
-- ****************************************************************************
local function DisplaySkillListLine(this, line, key, isSelected)
 local frame = popupFrames.skillListFrame;
 line.skillName = key;
 if (frame.listType == "throttle") then
  line.skillFontString:SetText(key);
  line.skillFontString:SetPoint("RIGHT", line.timeSlider, "LEFT", -10, 0);
  line.timeSlider:Show();
  line.timeSlider:SetValue(frame.skills[key] or 3);
 elseif (frame.listType == "substitution") then
  line.skillFontString:SetText(key .. " - " .. tostring(frame.skills[key]));
  line.skillFontString:SetPoint("RIGHT", line.deleteButton, "LEFT", -10, 0);
  line.timeSlider:Hide();
 else
  line.skillFontString:SetText(key);
  line.skillFontString:SetPoint("RIGHT", line.deleteButton, "LEFT", -10, 0);
  line.timeSlider:Hide();
 end
end


-- ****************************************************************************
-- Creates the popup skill list frame.
-- ****************************************************************************
local function CreateSkillList()
 local frame = CreatePopup();
 frame:SetWidth(400);
 frame:SetHeight(300);
 frame.controls = {};
 local controls = frame.controls;

 -- Title text.
 local fontString = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("TOP", frame, "TOP", 0, -20);
 frame.titleFontString = fontString;
 
 fontString = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall");
 fontString:SetPoint("TOPLEFT", frame, "TOPLEFT", 20, -50);
 fontString:SetText(L.MSG_SKILLS .. ":");
 frame.skillsFontString = fontString;
 
 -- Add skill button.
 local button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["addSkill"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("LEFT", frame.skillsFontString, "RIGHT", 10, 0);
 button:SetClickHandler(
  function (this)
   local objLocale = L.EDITBOXES["skillName"];
   EraseTable(tempConfig);
   tempConfig.editboxLabel = objLocale.label;
   tempConfig.editboxTooltip = objLocale.tooltip;
   tempConfig.parentFrame = frame;
   tempConfig.anchorFrame = this;
   tempConfig.validateHandler = ValidateSkillListName;
   tempConfig.saveHandler = SaveSkillListName;
   tempConfig.hideHandler = EnableSkillListControls;
   if (frame.listType == "substitution") then
    objLocale = L.EDITBOXES["substitutionText"];
    tempConfig.showSecondEditbox = true;
    tempConfig.secondEditboxLabel = objLocale.label;
    tempConfig.secondEditboxTooltip = objLocale.tooltip;
   end
   DisableControls(controls);
   ShowInput(tempConfig);
  end
 );
 frame.addSkillButton = button;
 controls[#controls+1] = button;
 
 -- Skills listbox.
 local listbox = MSBTControls.CreateListbox(frame);
 listbox:Configure(355, 180, 30);
 listbox:SetPoint("TOPLEFT", frame.skillsFontString, "BOTTOMLEFT", 10, -10);
 listbox:SetCreateLineHandler(CreateSkillListLine);
 listbox:SetDisplayHandler(DisplaySkillListLine);
 frame.skillsListbox = listbox;
 controls[#controls+1] = listbox;
 
 -- Save button.
 button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["genericSave"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -10, 20);
 button:SetClickHandler(
  function (this)
   if (frame.saveHandler) then frame.saveHandler(frame.saveArg1); end
   frame:Hide();
  end
 );
 controls[#controls+1] = button;

 -- Cancel button.
 button = MSBTControls.CreateOptionButton(frame);
 objLocale = L.BUTTONS["genericCancel"];
 button:Configure(20, objLocale.label, objLocale.tooltip);
 button:SetPoint("BOTTOMLEFT", frame, "BOTTOM", 10, 20);
 button:SetClickHandler(
  function (this)
   frame:Hide();
  end
 );
 controls[#controls+1] = button;
 
 return frame; 
end


-- ****************************************************************************
-- Shows the popup skill list frame using the passed config.
-- ****************************************************************************
local function ShowSkillList(configTable)
 -- Don't do anything if required parameters weren't passed.
 if (not configTable or not configTable.anchorFrame or not configTable.parentFrame or not configTable.skills) then return; end

 -- Create the frame if it hasn't already been.
 if (not popupFrames.skillListFrame) then popupFrames.skillListFrame = CreateSkillList(); end
 
 -- Set parent.
 local frame = popupFrames.skillListFrame;
 ChangePopupParent(frame, configTable.parentFrame)

 
 -- Populate data.
 frame.titleFontString:SetText(configTable.title); 
 
 -- Skills.
 frame.listType = configTable.listType;
 frame.skills = configTable.skills;
 frame.skillsListbox:Clear();
 for skillName, value in pairs(configTable.skills) do
  if (value) then frame.skillsListbox:AddItem(skillName); end
 end

 -- Configure the frame.
 frame.saveHandler = configTable.saveHandler;
 frame.saveArg1 = configTable.saveArg1;
 frame.hideHandler = configTable.hideHandler;
 frame:ClearAllPoints();
 frame:SetPoint(configTable.anchorPoint or "TOPLEFT", configTable.anchorFrame, configTable.relativePoint or "BOTTOMLEFT");
 frame:Show();
 frame:Raise();
end




-------------------------------------------------------------------------------
-- Module interface.
-------------------------------------------------------------------------------

-- Protected Functions.
module.DisableControls				= DisableControls;
module.ShowInput					= ShowInput;
module.ShowAcknowledge				= ShowAcknowledge;
module.ShowFont						= ShowFont;
module.ShowPartialEffects			= ShowPartialEffects;
module.ShowDamageColors				= ShowDamageColors;
module.ShowScrollAreaConfig			= ShowScrollAreaConfig;
module.ShowScrollAreaSelection		= ShowScrollAreaSelection;
module.ShowEvent					= ShowEvent;
module.ShowTrigger					= ShowTrigger;
module.ShowSkillList				= ShowSkillList;
module.ShowSubstitutionList			= ShowSubstitutionList;