You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2886 lines
100 KiB

--[[
Current system for configuration:
BtWTodoConfigPanel handles config for the UI, including todo list, characters, item size and auto adding current player
refresh function clones the data for the ui profiles as well as category data and stores them within the frame
Todos and Characters are stored as data providers for their scrollboxes
Categories are stored as a table, id => { id = string|number, name = string, color = ColorMixin }, name/color affects all profiles
okay function stores the data back into the saved variables
default function SHOULD revert either one or more profiles back to the default layout?
cancel function just closes the frame, no need to actually do anything since refresh is called when the panel is shown again
should the changes show in the normal ui?
BtWTodoConfigTodoPanel handles config for the todo items, including states and functions
BtWTodoConfigTodoPanel works by cloning todo items, but only as they are edited
refresh function clears the cache of cloned todo items
okay function saves the cache of cloned items back into the BtWTodoData saved variable
default function SHOULD revert the currently selected todo back to default if its a preregisted todo?
cancel function just closes the frame
should the changes show in the normal ui?
]]
local ADDON_NAME, Internal = ...
local L = Internal.L
local External = _G[ADDON_NAME]
BTWTODO = "BtWTodo"
BTWTODO_SUBTEXT = " "
BTWTODO_WINDOWS = L["Windows"]
BTWTODO_WINDOWS_SUBTEXT = L["These options allow you to customize the different frames, changing which todos and characters are displayed."]
BTWTODO_ADD_TODO = L["Add Todo"]
BTWTODO_ADD_TODO_SUBTEXT = L["Search for a todo to add below"]
BTWTODO_ADD_CATEGORY = L["Add Category"]
BTWTODO_ADD_CATEGORY_SUBTEXT = L["Enter the name of the category below"]
BTWTODO_ADD_CHARACTER = L["Add Character"]
BTWTODO_ADD_CHARACTER_SUBTEXT = L["Search for a character to add"]
BTWTODO_AUTO_ADD_PLAYER = L["Auto Add Player"]
BTWTODO_CHANGE_COLOR = L["Change Color"]
BTWTODO_CHANGE_NAME_COLOR = L["Change Name/Color"]
BTWTODO_LIST = L["List"]
BTWTODO_BUTTON_SIZE = L["Button Size"]
BTWTODO_LISTS = L["Lists"]
BTWTODO_LISTS_SUBTEXT = L["Create and edit lists for choosing which todos to display."]
BTWTODO_NEW_LIST = L["New List"]
BTWTODO_TODOS = L["Todos"]
BTWTODO_TODOS_SUBTEXT = L["These options allow you to customize your todos, select a todo from the drop down menu below to begin."]
BTWTODO_ADD_STATE = L["Add State"]
BTWTODO_COMPLETED = GOAL_COMPLETED
BTWTODO_TEXT = LOCALE_TEXT_LABEL
BTWTODO_CLICK = L["Click"]
BTWTODO_TOOLTIP = L["Tooltip"]
BTWTODO_BASIC = L["Basic"]
BTWTODO_ADVANCED = EFFECTS_LABEL
BTWTODO_NEW_TODO = L["New Todo"]
BTWTODO_ADD_ITEM = L["Add %s"]
BTWTODO_TOGGLE_VISIBILITY = L["Toggle Visibility"]
BTWTODO_REVERT = L["Revert"]
BTWTODO_REVERT_TODO = L["Revert To-do"]
BTWTODO_UPDATED_MESSAGE = L["The selected to-do has been updated since you edited it. Click here if you wish to change to the updated version, |cFFFF0000the changes you made will be lost|r."]
BTWTODO_REVERT_MESSAGE = L["Revert this to-do back to it's default version, |cFFFF0000any changes you made will be lost|r"]
BTWTODO_CHANGE_LOG = L["Change Log:"]
BTWTODO_CHARACTERS = L["Characters"]
BTWTODO_CHARACTERS_SUBTEXT = L["These options allow you to customize your characters, changing which are displayed, and removed old characters."]
BTWTODO_CLONE = L["Clone"]
BTWTODO_EXPORT = L["Export"]
BTWTODO_IMPORT = L["Import"]
BTWTODO_UPDATE = L["Update"]
BTWTODO_IMPORT_TITLE = L["Import Todo"]
BTWTODO_MINIMAP_ICON = L["Show Minimap Icon"]
BTWTODO_MINIMAP_TOOLTIP = L["Show Minimap Tooltip"]
BTWTODO_MINIMAP_LEFT_CLICK = L["Left Click Action"]
BTWTODO_MINIMAP_SHIFT_LEFT_CLICK = L["Shift Left Click Action"]
BTWTODO_MINIMAP_RIGHT_CLICK = L["Right Click Action"]
--[==[@debug@
local debug = print
--@end-debug@]==]
--@non-debug@
local debug = function () end
--@end-non-debug@
local function IterateLists()
local tbl = {}
for id,todo in pairs(BtWTodoConfigListsPanel.lists) do
tbl[id] = todo
end
for id,todo in Internal.IterateLists() do
if not tbl[id] then
tbl[id] = todo
end
end
return next, tbl, nil
end
local function GetList(id)
local list = BtWTodoConfigListsPanel.lists[id]
if not list then
list = Internal.GetList(id)
end
return list
end
-- [[ Add Item Overlay ]]
BtWTodoAutoCompleteButtonMixin = {}
function BtWTodoAutoCompleteButtonMixin:Set(name, isSelected)
self:SetText(name)
if isSelected then
self:LockHighlight();
else
self:UnlockHighlight();
end
end
function BtWTodoAutoCompleteButtonMixin:OnClick()
self:GetParent():OnButtonClick(self:GetID())
end
BtWTodoAddFrameAutoCompleteListMixin = {}
function BtWTodoAddFrameAutoCompleteListMixin:OnLoad()
BackdropTemplateMixin.OnBackdropLoaded(self)
if self.TooltipBackdropOnLoad then
self:TooltipBackdropOnLoad()
end
self.items = {}
self.offset = 0
self.maxButtons = 6
self.selectedIndex = 1
self.Buttons = {}
end
function BtWTodoAddFrameAutoCompleteListMixin:SetOnButtonClick(callback)
self.onButtonClick = callback
end
function BtWTodoAddFrameAutoCompleteListMixin:OnButtonClick(index)
if self.onButtonClick then
self.onButtonClick(self, self.items[index]:gsub("|[cC][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]", ""):gsub("|[rR]", ""))
end
end
function BtWTodoAddFrameAutoCompleteListMixin:GetSelectedIndex()
return self.selectedIndex
end
function BtWTodoAddFrameAutoCompleteListMixin:SetSelectedIndex(index)
index = tonumber(index) or 1
if index < self.selectedIndex then
self.offset = min(index - 1, self.offset)
else
self.offset = max(self.offset, index - self.maxButtons)
end
self.selectedIndex = index
self:Update()
end
function BtWTodoAddFrameAutoCompleteListMixin:SetItems(items)
self.items = items
self:Update()
end
function BtWTodoAddFrameAutoCompleteListMixin:GetItems()
return self.items
end
function BtWTodoAddFrameAutoCompleteListMixin:GetButton(index)
return self.Buttons[index]
end
function BtWTodoAddFrameAutoCompleteListMixin:SetMaxButtons(count)
self.maxButtons = tonumber(count) or 4
self:Update()
end
function BtWTodoAddFrameAutoCompleteListMixin:Update()
if #self.items == 0 then
return self:Hide()
end
local count = min(self.maxButtons, #self.items)
self.selectedIndex = max(1, min(self.selectedIndex, #self.items))
self.offset = max(0, min(self.offset, #self.items - self.maxButtons))
local width = 120
for i=1,count do
local index = i + self.offset
local button = self.Buttons[i]
if not button then
button = CreateFrame("Button", nil, self, "BtWTodoAutoCompleteButtonTemplate")
if i == 1 then
button:SetPoint("TOPLEFT", 0, -10)
else
button:SetPoint("TOPLEFT", self.Buttons[i-1], "BOTTOMLEFT", 0, 0)
end
self.Buttons[i] = button
end
button:SetID(index)
button:Set(self.items[index], index == self:GetSelectedIndex())
width = max(width, button:GetFontString():GetWidth() + 30)
button:SetWidth(width)
button:Enable();
button:Show()
end
for i=count+1,#self.Buttons do
self.Buttons[i]:Hide()
end
self:SetSize(width, self.Buttons[1]:GetHeight() * count + 20)
self:Show()
end
BtWTodoAddFrameEditBoxMixin = {}
function BtWTodoAddFrameEditBoxMixin:OnLoad()
self.addHighlightedText = true
self.autoCompleteListFrame = nil
self.autoCompleteList = {}
self.selectedIndex = 0
end
function BtWTodoAddFrameEditBoxMixin:SetAutoCompleteListFrame(frame)
self.autoCompleteListFrame = frame
frame:SetItems(self.autoCompleteList)
frame:SetPoint("TOPLEFT", self, "BOTTOMLEFT", 0, -5)
frame:SetOnButtonClick(function (_, value)
self:SetText(value)
self:SetCursorPosition(strlen(value))
frame:Hide()
end)
end
function BtWTodoAddFrameEditBoxMixin:Wipe()
wipe(self.autoCompleteList)
end
function BtWTodoAddFrameEditBoxMixin:UpdateAutoCompleteList()
local text = self:GetText()
local utf8Position = self:GetUTF8CursorPosition()
wipe(self.autoCompleteList)
self.autoCompleteCallback(self, self.autoCompleteList, text, 1, utf8Position)
return #self.autoCompleteList > 0
end
function BtWTodoAddFrameEditBoxMixin:GetNumResults()
return #self.autoCompleteList
end
function BtWTodoAddFrameEditBoxMixin:GetAutoCompleteList()
return self.autoCompleteListFrame
end
function BtWTodoAddFrameEditBoxMixin:HasAutoCompleteList()
return self.autoCompleteListFrame ~= nil
end
function BtWTodoAddFrameEditBoxMixin:GetSelectedIndex()
if self:HasAutoCompleteList() then
return self.autoCompleteListFrame:GetSelectedIndex()
end
return 0
end
function BtWTodoAddFrameEditBoxMixin:SetSelectedIndex(index)
if self:HasAutoCompleteList() then
self.autoCompleteListFrame:SetSelectedIndex(index)
end
end
function BtWTodoAddFrameEditBoxMixin:HideAutoCompleteFrame()
if self:HasAutoCompleteList() and not self.autoCompleteListFrame:IsMouseOver() then
self.autoCompleteListFrame:Hide()
end
end
function BtWTodoAddFrameEditBoxMixin:IncrementSelection(up)
if self:HasAutoCompleteList() then
local selectedIndex = self:GetSelectedIndex()
local numReturns = self:GetNumResults()
if up then
local nextNum = mod(selectedIndex - 1, numReturns)
if nextNum <= 0 then
nextNum = numReturns -- 1 indexed
end
self:SetSelectedIndex(nextNum)
else
local nextNum = mod(selectedIndex + 1, numReturns)
if nextNum == 0 then
nextNum = numReturns
end
self:SetSelectedIndex(nextNum)
end
end
end
function BtWTodoAddFrameEditBoxMixin:OnTabPressed()
if self:HasAutoCompleteList() and not self.autoCompleteListFrame:IsShown() then
self:UpdateAutoCompleteList()
self.autoCompleteListFrame:Update()
return
end
self:IncrementSelection(IsShiftKeyDown())
end
function BtWTodoAddFrameEditBoxMixin:OnArrowPressed(key)
if self:HasAutoCompleteList() and not self.autoCompleteListFrame:IsShown() then
self:UpdateAutoCompleteList()
self.autoCompleteListFrame:Update()
return
end
if key == "UP" then
return self:IncrementSelection(true)
elseif key == "DOWN" then
return self:IncrementSelection(false)
end
end
function BtWTodoAddFrameEditBoxMixin:OnEnterPressed()
if self:HasAutoCompleteList() and self:GetAutoCompleteList():IsShown() then
local selectedIndex = self:GetSelectedIndex()
if selectedIndex ~= 0 then
return self:GetAutoCompleteList():GetButton(selectedIndex):Click()
end
end
if self.enterCallback then
self.enterCallback(self, self:GetText())
end
end
function BtWTodoAddFrameEditBoxMixin:OnTextChanged(userInput)
if userInput then
if self.disallowAutoComplete then
self:HideAutoCompleteFrame()
elseif self:HasAutoCompleteList() then
self:UpdateAutoCompleteList()
self.autoCompleteListFrame:Update()
end
end
end
function BtWTodoAddFrameEditBoxMixin:OnKeyDown(key)
if key == "BACKSPACE" then
self.disallowAutoComplete = true;
end
end
function BtWTodoAddFrameEditBoxMixin:OnKeyUp(key)
if key == "BACKSPACE" then
self.disallowAutoComplete = false;
end
end
function BtWTodoAddFrameEditBoxMixin:SetAddHighlightedText(value)
self.addHighlightedText = not not value
end
function BtWTodoAddFrameEditBoxMixin:AddHighlightedText(text)
if not self.autoCompleteCallback then
return
end
local utf8Position = self:GetUTF8CursorPosition()
if utf8Position ~= strlenutf8(text) then
return
end
if self:UpdateAutoCompleteList() then
local newText = self.autoCompleteList[1]:gsub("|[cC][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]", ""):gsub("|[rR]", "")
self:SetText(newText);
self:HighlightText(strlen(text), strlen(newText))
self:SetCursorPosition(strlen(text))
end
end
function BtWTodoAddFrameEditBoxMixin:OnChar()
if self.disallowAutoComplete then
self:HideAutoCompleteFrame()
elseif self:HasAutoCompleteList() then
self:UpdateAutoCompleteList()
self.autoCompleteListFrame:Update()
end
if self.addHighlightedText and self:GetUTF8CursorPosition() == strlenutf8(self:GetText()) then
self:AddHighlightedText(self:GetText());
end
end
function BtWTodoAddFrameEditBoxMixin:OnEditFocusGained()
if self:HasAutoCompleteList() then
self:UpdateAutoCompleteList()
self.autoCompleteListFrame:Update()
end
end
function BtWTodoAddFrameEditBoxMixin:OnEditFocusLost()
self:HighlightText(0, 0);
self:HideAutoCompleteFrame()
end
function BtWTodoAddFrameEditBoxMixin:OnEscapePressed()
EditBox_ClearFocus(self)
if self.escapeCallback then
self.escapeCallback(self)
end
end
function BtWTodoAddFrameEditBoxMixin:SetAutoCompleteCallback(callback)
self.autoCompleteCallback = callback
end
function BtWTodoAddFrameEditBoxMixin:SetOnEnterCallback(callback)
self.enterCallback = callback
end
function BtWTodoAddFrameEditBoxMixin:SetOnEscapePressed(callback)
self.escapeCallback = callback
end
BtWTodoAddItemOverlayMixin = {}
function BtWTodoAddItemOverlayMixin:OnShow()
self.EditBox:SetAutoCompleteListFrame(self.AutoCompleteList)
self.EditBox:SetFocus()
end
function BtWTodoAddItemOverlayMixin:GetText()
return self.EditBox:GetText()
end
function BtWTodoAddItemOverlayMixin:Clear()
self.EditBox:SetText("")
self.EditBox:Wipe()
end
function BtWTodoAddItemOverlayMixin:OnButtonClicked(button)
if button.button == 1 then
self.EditBox:OnEnterPressed()
elseif button.button == 2 then
self.EditBox:OnEscapePressed()
end
end
function BtWTodoAddItemOverlayMixin:SetTitle(text, subtitle)
if subtitle and subtitle ~= "" then
self.Title:SetText(text)
self.SubTitle:SetText(subtitle)
self.SubTitle:Show()
self.Title:SetPoint("CENTER", 0, 48)
else
self.Title:SetText(text)
self.Title:SetPoint("CENTER", 0, 24)
self.SubTitle:Hide()
end
end
function BtWTodoAddItemOverlayMixin:SetAutoCompleteCallback(callback)
self.EditBox:SetAutoCompleteCallback(callback)
end
function BtWTodoAddItemOverlayMixin:SetOnOkayCallback(callback)
self.EditBox:SetOnEnterCallback(function (_, text)
callback(self, text)
end)
end
function BtWTodoAddItemOverlayMixin:SetOnCancelCallback(callback)
self.EditBox:SetOnEscapePressed(callback)
end
-- [[ Drag Scroll Box ]]
BtWTodoDragScrollBoxItemMixin = {}
function BtWTodoDragScrollBoxItemMixin:OnLoad()
self:RegisterForDrag("LeftButton")
end
function BtWTodoDragScrollBoxItemMixin:OnDragStart()
self:GetParent():GetParent():SetDragTarget(self.data.orderIndex)
end
function BtWTodoDragScrollBoxItemMixin:OnEnter()
self:GetParent():GetParent():DragOver(self.data.orderIndex)
end
BtWTodoDragScrollBoxMixin = {}
function BtWTodoDragScrollBoxMixin:OnLoad()
self.dragIndexes = {}
ScrollBoxListMixin.OnLoad(self)
self:RegisterEvent("GLOBAL_MOUSE_UP")
end
function BtWTodoDragScrollBoxMixin:OnEvent()
self:SetDragRange(nil)
end
function BtWTodoDragScrollBoxMixin:GetDragRange()
return unpack(self.dragIndexes)
end
function BtWTodoDragScrollBoxMixin:SetDragRange(startIndex, endIndex) -- Inclusive
if startIndex == nil then
wipe(self.dragIndexes)
self:ForEachFrame(function (frame)
frame.Drag:Hide()
end)
return
end
if startIndex > endIndex then -- Lowest first always
startIndex, endIndex = endIndex, startIndex
end
local foundFrame = false
for _,frame in ipairs(self:GetFrames()) do
local orderIndex = frame:GetOrderIndex()
if orderIndex >= startIndex and orderIndex <= endIndex then
frame.Drag:Show()
foundFrame = true
elseif foundFrame then
break
end
end
self.dragIndexes = {startIndex, endIndex}
end
local function GetCategoryRange(self, orderIndex)
local dataProvider = self:GetDataProvider()
local index = orderIndex + 1
local item = dataProvider:Find(index)
while item and item.type ~= "category" do
index = index + 1
item = dataProvider:Find(index)
end
return index - 1
end
function BtWTodoDragScrollBoxMixin:SetDragTarget(orderIndex)
local item = self:GetDataProvider():Find(orderIndex)
if item == nil then
error(self:GetName() .. ":SetDragFrame(orderIndex): orderIndex out of range")
end
if item.type == "category" then
self:SetDragRange(orderIndex, GetCategoryRange(self, orderIndex))
else
self:SetDragRange(orderIndex, orderIndex)
end
end
function BtWTodoDragScrollBoxMixin:DragOver(orderIndex)
local startIndex, endIndex = unpack(self.dragIndexes)
if not startIndex then
return
end
if orderIndex >= startIndex and orderIndex <= endIndex then
return
end
-- We modify the orderIndex values, but until we call Sort on the data provider they wont be updated
local dataProvider = self:GetDataProvider()
-- In situations where we are moving an entire category we need to move it before or after another category only
local isCategory = startIndex ~= endIndex
if orderIndex < startIndex then -- Moving items up
local item = dataProvider:Find(orderIndex)
if isCategory and item ~= nil and item.type ~= "category" then -- Drag up onto a category to put it before
return
end
local index = orderIndex
for i=startIndex,endIndex do
local item = dataProvider:Find(i)
item.orderIndex = index
index = index + 1
end
self.dragIndexes = {orderIndex, orderIndex + endIndex - startIndex}
local index = orderIndex
local item = dataProvider:Find(index)
while item and index < startIndex do
item.orderIndex = endIndex - startIndex + 1 + index
index = index + 1
item = dataProvider:Find(index)
end
else
local item = dataProvider:Find(orderIndex+1)
if isCategory and item ~= nil and item.type ~= "category" then -- Drag down onto the last item before the end or a category
return
end
local index = startIndex-- -- Needs to go before category
for i=endIndex+1,orderIndex do
local item = dataProvider:Find(i)
item.orderIndex = index
index = index + 1
end
local index = orderIndex-(endIndex - startIndex)
for i=startIndex,endIndex do
local item = dataProvider:Find(i)
item.orderIndex = index
index = index + 1
end
self.dragIndexes = {orderIndex-(endIndex - startIndex), orderIndex-(endIndex - startIndex) + endIndex - startIndex}
end
dataProvider:Sort()
end
function BtWTodoDragScrollBoxMixin:Remove(orderIndex)
-- We modify the orderIndex values, but until we call Sort on the data provider they wont be updated
local dataProvider = self:GetDataProvider()
dataProvider:RemoveIndex(orderIndex)
for index,elementData in dataProvider:Enumerate() do
elementData.orderIndex = index -- Fix indexes
end
dataProvider:Sort()
end
-- [[ Config Panel ]]
local ldbi = LibStub("LibDBIcon-1.0")
-- TODO: when custom frames are added, construct this list of actions dynamically
local minimapButtonActions = {
"toggleWindow:main",
"toggleWindow:small",
"openConfig",
}
local minimapButtonActionNames = {
toggleWindow = L["Toggle %s Window"],
openConfig = L["Open Options"],
}
local frameNames = {
main = L["Main"],
small = L["Small"],
tooltip = L["Tooltip"],
}
function Internal.SplitAction(actionStr)
local action, subAction = strsplit(":", actionStr, 2)
return action, subAction
end
function Internal.ActionName(action, subAction)
local name = minimapButtonActionNames[action]
if action == "toggleWindow" then
name = string.format(name, frameNames[subAction])
end
return name
end
BtWTodoConfigPanelMixin = {}
function BtWTodoConfigPanelMixin:OnLoad()
InterfaceOptions_AddCategory(self)
UIDropDownMenu_SetWidth(self.MinimapLeftClickDropDown, 175);
UIDropDownMenu_JustifyText(self.MinimapLeftClickDropDown, "LEFT");
UIDropDownMenu_Initialize(self.MinimapLeftClickDropDown, function (_, level, menuList)
local selected = self:GetMinimapLeftClickAction()
local info = UIDropDownMenu_CreateInfo();
info.func = function (_, arg1, arg2, checked)
self:SetMinimapLeftClickAction(arg1)
end
for _,actionStr in pairs(minimapButtonActions) do
info.arg1 = actionStr
info.text = Internal.ActionName(Internal.SplitAction(actionStr))
info.checked = selected == actionStr
UIDropDownMenu_AddButton(info, level)
end
end);
UIDropDownMenu_SetWidth(self.MinimapShiftLeftClickDropDown, 175);
UIDropDownMenu_JustifyText(self.MinimapShiftLeftClickDropDown, "LEFT");
UIDropDownMenu_Initialize(self.MinimapShiftLeftClickDropDown, function (_, level, menuList)
local selected = self:GetMinimapShiftLeftClickAction()
local info = UIDropDownMenu_CreateInfo();
info.func = function (_, arg1, arg2, checked)
self:SetMinimapShiftLeftClickAction(arg1)
end
for _,actionStr in pairs(minimapButtonActions) do
info.arg1 = actionStr
info.text = Internal.ActionName(Internal.SplitAction(actionStr))
info.checked = selected == actionStr
UIDropDownMenu_AddButton(info, level)
end
end);
UIDropDownMenu_SetWidth(self.MinimapRightClickDropDown, 175);
UIDropDownMenu_JustifyText(self.MinimapRightClickDropDown, "LEFT");
UIDropDownMenu_Initialize(self.MinimapRightClickDropDown, function (_, level, menuList)
local selected = self:GetMinimapRightClickAction()
local info = UIDropDownMenu_CreateInfo();
info.func = function (_, arg1, arg2, checked)
self:SetMinimapRightClickAction(arg1)
end
for _,actionStr in pairs(minimapButtonActions) do
info.arg1 = actionStr
info.text = Internal.ActionName(Internal.SplitAction(actionStr))
info.checked = selected == actionStr
UIDropDownMenu_AddButton(info, level)
end
end);
end
function BtWTodoConfigPanelMixin:SetMinimapIcon(checked)
local icon = ldbi:GetMinimapButton(ADDON_NAME)
if checked then
if not icon then
ldbi:Show(ADDON_NAME)
else
icon:Show()
end
else
icon:Hide()
end
end
function BtWTodoConfigPanelMixin:SetMinimapTooltip(checked)
end
function BtWTodoConfigPanelMixin:GetMinimapLeftClickAction()
return self.minimapLeftClickAction;
end
function BtWTodoConfigPanelMixin:SetMinimapLeftClickAction(actionStr)
self.minimapLeftClickAction = actionStr;
UIDropDownMenu_SetText(self.MinimapLeftClickDropDown, Internal.ActionName(Internal.SplitAction(actionStr)))
end
function BtWTodoConfigPanelMixin:GetMinimapShiftLeftClickAction()
return self.minimapShiftLeftClickAction;
end
function BtWTodoConfigPanelMixin:SetMinimapShiftLeftClickAction(actionStr)
self.minimapShiftLeftClickAction = actionStr;
UIDropDownMenu_SetText(self.MinimapShiftLeftClickDropDown, Internal.ActionName(Internal.SplitAction(actionStr)))
end
function BtWTodoConfigPanelMixin:GetMinimapRightClickAction()
return self.minimapRightClickAction;
end
function BtWTodoConfigPanelMixin:SetMinimapRightClickAction(actionStr)
self.minimapRightClickAction = actionStr;
UIDropDownMenu_SetText(self.MinimapRightClickDropDown, Internal.ActionName(Internal.SplitAction(actionStr)))
end
function BtWTodoConfigPanelMixin:okay()
xpcall(function()
local icon = ldbi:GetMinimapButton(ADDON_NAME)
BtWTodoDataBroker.hide = not self.MinimapIconButton:GetChecked()
if BtWTodoDataBroker.hide then
icon:Hide()
else
icon:Show()
end
BtWTodoDataBroker.hideTooltip = not self.MinimapTooltipButton:GetChecked()
BtWTodoDataBroker.leftClickAction = self:GetMinimapLeftClickAction()
BtWTodoDataBroker.shiftLeftClickAction = self:GetMinimapShiftLeftClickAction()
BtWTodoDataBroker.rightClickAction = self:GetMinimapRightClickAction()
end, geterrorhandler())
end
function BtWTodoConfigPanelMixin:cancel()
xpcall(function()
local icon = ldbi:GetMinimapButton(ADDON_NAME)
if BtWTodoDataBroker.hide then
icon:Hide()
else
icon:Show()
end
end, geterrorhandler())
end
function BtWTodoConfigPanelMixin:default()
xpcall(function()
self.MinimapIconButton:SetChecked(true)
ldbi:GetMinimapButton(ADDON_NAME):Show()
end, geterrorhandler())
end
function BtWTodoConfigPanelMixin:refresh()
xpcall(function()
self.MinimapIconButton:SetChecked(not BtWTodoDataBroker.hide)
self.MinimapTooltipButton:SetChecked(not BtWTodoDataBroker.hideTooltip)
self:SetMinimapLeftClickAction(BtWTodoDataBroker.leftClickAction)
self:SetMinimapShiftLeftClickAction(BtWTodoDataBroker.shiftLeftClickAction)
self:SetMinimapRightClickAction(BtWTodoDataBroker.rightClickAction)
end, geterrorhandler())
end
do
local tbl = {}
Internal.RegisterEvent("REGISTER_STATE_PROVIDER", function ()
wipe(tbl)
end)
local function DropDownInit(self, level, menuList)
local function OnClick(_, arg1, arg2, checked)
self:SetSelected(arg1, arg2)
end
if tbl[1] == nil then
for _,provider in Internal.IterateStateProviders() do
tbl[#tbl+1] = provider
end
table.sort(tbl, function (a, b)
if a:GetName() == b:GetName() then
return a:GetID() < b:GetID()
end
return a:GetName() < b:GetName()
end)
end
local info = UIDropDownMenu_CreateInfo()
if (level or 1) == 1 then
info.func = OnClick
info.notCheckable = true
for _,provider in ipairs(tbl) do
info.text = provider:GetName()
info.arg1 = provider:GetID()
UIDropDownMenu_AddButton(info, level)
end
end
end
BtWTodoStateProviderDropDownMixin = {}
function BtWTodoStateProviderDropDownMixin:OnLoad()
end
function BtWTodoStateProviderDropDownMixin:OnShow()
if not self.initialized then
UIDropDownMenu_Initialize(self, DropDownInit, "MENU")
self.initialized = true
end
end
function BtWTodoStateProviderDropDownMixin:SetSelected(type, key)
if self.onchange then
self:onchange(type, key)
end
end
function BtWTodoStateProviderDropDownMixin:SetScript(scriptType, handler)
if scriptType == "OnChange" then
self.onchange = handler
else
getmetatable(self).__index.SetScript(self, scriptType, handler)
end
end
end
-- [[ Todo States Input ]]
BtWTodoConfigStatesInputItemMixin = {}
function BtWTodoConfigStatesInputItemMixin:OnLoad()
self:RegisterForDrag("LeftButton")
end
function BtWTodoConfigStatesInputItemMixin:Init(state)
self.state = state
if state.GetDisplayName then
local result = state:GetDisplayName(true)
if type(result) == "function" then
result(function (result)
self.Text:SetText(result)
self:SetWidth(self.Text:GetWidth() + 25 + 16)
end)
else
self.Text:SetText(result)
end
else
self.Text:SetText(state:GetUniqueKey())
end
self:SetWidth(self.Text:GetWidth() + 25 + 16)
end
function BtWTodoConfigStatesInputItemMixin:OnEnter()
if not self:IsDragging() then
self.RemoveButton.texture:SetDesaturated(false)
end
end
function BtWTodoConfigStatesInputItemMixin:OnLeave()
if not self:IsMouseOver() then
self.RemoveButton.texture:SetDesaturated(true)
end
end
function BtWTodoConfigStatesInputItemMixin:OnDragStart()
self:GetParent():GetParent():OnDragStart()
end
function BtWTodoConfigStatesInputItemMixin:OnDragStop()
self:GetParent():GetParent():OnDragStop()
end
function BtWTodoConfigStatesInputItemMixin:OnRemoveClick()
self:GetParent():GetParent():Remove(self.state)
end
BtWTodoConfigStatesInputMixin = CreateFromMixins(Internal.ScriptHandlerMixin)
function BtWTodoConfigStatesInputMixin:OnLoad()
Internal.ScriptHandlerMixin.OnLoad(self)
self:RegisterSupportedScriptHandlers("OnAdd", "OnRemove")
self:RegisterForDrag("LeftButton")
self:EnableMouseWheel(true)
self.pool = CreateFramePool("Frame", self:GetScrollChild(), "BtWTodoConfigStatesInputItemTemplate")
end
function BtWTodoConfigStatesInputMixin:Init(states)
self.states = states
self:SetHorizontalScroll(0)
self:Update()
self:UpdateScrollChildRect()
self:SetHorizontalScroll(self:GetHorizontalScrollRange())
end
function BtWTodoConfigStatesInputMixin:Update()
self.pool:ReleaseAll()
local previousFrame
for _,state in ipairs(self.states) do
local frame = self.pool:Acquire()
if previousFrame then
frame:SetPoint("LEFT", previousFrame, "RIGHT", 5, 0)
else
frame:SetPoint("LEFT", 0, 0)
end
frame:Init(state)
frame:Show()
previousFrame = frame
end
end
function BtWTodoConfigStatesInputMixin:Add(key)
local frame = self:GetParent():GetParent()
local provider = Internal.GetStateProvider(key)
if provider:RequiresID() then
frame.AddItem:SetTitle(provider:GetAddTitle())
frame.AddItem:SetAutoCompleteCallback(function (_, tbl, text, offset, length)
provider:FillAutoComplete(tbl, text, offset, length)
end)
frame.AddItem:SetOnOkayCallback(function (_, text)
local result = {provider:ParseInput(text)}
local success = table.remove(result, 1)
if not success then
UIErrorsFrame:AddMessage(result[1] or format(L["Invalid %s"], provider:GetName()), 1.0, 0.1, 0.1, 1.0);
return
end
if not result[1] then
result[1] = tonumber(text) or text
end
local target = provider:Acquire(unpack(result))
for _,state in ipairs(self.states) do
if state:GetUniqueKey() == target:GetUniqueKey() then
return
end
end
target.source = {
type = provider:GetID(),
id = table.remove(result, 1),
values = #result > 0 and result or nil
}
self.states[target:GetUniqueKey()] = target
self.states[#self.states+1] = target
self:Update()
self:UpdateScrollChildRect()
self:SetHorizontalScroll(self:GetHorizontalScrollRange())
self:RunScript("OnAdd", target, provider)
frame.AddItem:Clear()
frame.AddItem:Hide()
end)
frame.AddItem:Show()
else
local target = provider:Acquire()
for _,state in ipairs(self.states) do
if state:GetUniqueKey() == target:GetUniqueKey() then
return
end
end
self.states[target:GetUniqueKey()] = target
self.states[#self.states+1] = target
self:Update()
self:UpdateScrollChildRect()
self:SetHorizontalScroll(self:GetHorizontalScrollRange())
self:RunScript("OnAdd", target, provider)
end
end
function BtWTodoConfigStatesInputMixin:GetCount()
return #self.states
end
function BtWTodoConfigStatesInputMixin:OnAddClick()
self:Add()
end
function BtWTodoConfigStatesInputMixin:Remove(value)
self.states[value:GetUniqueKey()] = nil
for index,state in ipairs(self.states) do
if state == value then
table.remove(self.states, index)
end
end
self:Update()
self:UpdateScrollChildRect()
self:SetHorizontalScroll(math.min(math.max(self:GetHorizontalScroll(), 0), self:GetHorizontalScrollRange()))
self:RunScript("OnRemove", value)
end
function BtWTodoConfigStatesInputMixin:OnEnter()
end
function BtWTodoConfigStatesInputMixin:OnLeave()
end
function BtWTodoConfigStatesInputMixin:OnDragStart()
self.scrollX, self.scrollY = self:GetHorizontalScroll(), self:GetVerticalScroll()
self.mouseX, self.mouseY = GetCursorPosition()
local scale = self:GetScrollChild():GetEffectiveScale()
self.mouseX, self.mouseY = self.mouseX / scale, self.mouseY / scale
self:SetScript("OnUpdate", self.OnDrag)
end
function BtWTodoConfigStatesInputMixin:OnDragStop()
self:SetScript("OnUpdate", nil)
end
function BtWTodoConfigStatesInputMixin:OnDrag()
local mouseX, mouseY = GetCursorPosition()
local scale = self:GetEffectiveScale()
mouseX, mouseY = mouseX / scale, mouseY / scale
local maxXScroll, maxYScroll = self:GetHorizontalScrollRange(), self:GetVerticalScrollRange()
mouseX = min(max(self.mouseX + self.scrollX - mouseX, 0), maxXScroll)
mouseY = min(max(mouseY - self.mouseY + self.scrollY, 0), maxYScroll)
self:SetHorizontalScroll(mouseX)
self:SetVerticalScroll(mouseY)
end
function BtWTodoConfigStatesInputMixin:OnMouseUp()
end
function BtWTodoConfigStatesInputMixin:OnMouseDown()
end
function BtWTodoConfigStatesInputMixin:OnMouseWheel(delta)
local scroll = self:GetHorizontalScroll() + (30 * -delta)
self:SetHorizontalScroll(math.min(math.max(scroll, 0), self:GetHorizontalScrollRange()))
end
-- [[ Lua Syntax Editor ]]
local contextIncreasesIndents = {
BLOCK = true,
MULTILINE_COMMENT = true,
}
local contextDecreasesIndents = {
}
local function GetExpectedIndentForContext(context)
local indent = 0
for _,item in ipairs(context) do
if contextIncreasesIndents[item] then
indent = indent + 1
elseif contextDecreasesIndents[item] then
indent = indent - 1
end
end
return indent
end
local TOKEN = {
FORMATTING = "FORMATTING",
NEWLINE = "NEWLINE",
WHITESPACE = "WHITESPACE",
STRING = "STRING", -- Contents of a string
DOUBLE_QUOTE = "DOUBLE_QUOTE", -- Start/End of a double quoted string
SINGLE_QUOTE = "SINGLE_QUOTE", -- Start/End of a single quoted string
MULTILINE_QUOTE = "MULTILINE_QUOTE", -- Start/End of a multiline string [[ ... ]]
MULTILINE_COMMENT_END = "MULTILINE_COMMENT_END", -- Start/End of a multiline comment --[[ ... ]]
COMMENT = "COMMENT",
IDENT = "IDENT",
NUMBER = "NUMBER",
CONCAT = "CONCAT",
}
local KEYWORDS = {
["and"] = true,
["break"] = true,
["do"] = true,
["else"] = true,
["elseif"] = true,
["end"] = true,
["false"] = true,
["for"] = true,
["function"] = true,
["if"] = true,
["in"] = true,
["local"] = true,
["nil"] = true,
["not"] = true,
["or"] = true,
["repeat"] = true,
["return"] = true,
["then"] = true,
["true"] = true,
["until"] = true,
["while"] = true,
}
local tokenColors = {
["local"] = CreateColor(0.275, 0.741, 0.875, 1),
["return"] = CreateColor(0.275, 0.741, 0.875, 1),
["if"] = CreateColor(0.275, 0.741, 0.875, 1),
["then"] = CreateColor(0.275, 0.741, 0.875, 1),
["elseif"] = CreateColor(0.275, 0.741, 0.875, 1),
["else"] = CreateColor(0.275, 0.741, 0.875, 1),
["while"] = CreateColor(0.275, 0.741, 0.875, 1),
["for"] = CreateColor(0.275, 0.741, 0.875, 1),
["do"] = CreateColor(0.275, 0.741, 0.875, 1),
["end"] = CreateColor(0.275, 0.741, 0.875, 1),
["function"] = CreateColor(0.275, 0.741, 0.875, 1),
["break"] = CreateColor(0.275, 0.741, 0.875, 1),
["and"] = CreateColor(0.275, 0.741, 0.875, 1),
["or"] = CreateColor(0.275, 0.741, 0.875, 1),
COMMENT = CreateColor(0.4, 0.4, 0.4, 1),
MULTILINE_COMMENT_END = CreateColor(0.4, 0.4, 0.4, 1),
NUMBER = CreateColor(0.322, 0.824, 0.451, 1),
STRING = CreateColor(0.898, 0.447, 0.333, 1),
DOUBLE_QUOTE = CreateColor(0.898, 0.447, 0.333, 1),
SINGLE_QUOTE = CreateColor(0.898, 0.447, 0.333, 1),
MULTILINE_QUOTE = CreateColor(0.898, 0.447, 0.333, 1),
CONCAT = CreateColor(0.91, 0.310, 0.392, 1),
}
local function GetNextToken(string, offset, lineEnd, context, multilineSize)
local byte = strbyte(string, offset)
-- if byte == 124 then -- Formatting
-- local match = strmatch(string, "^|c[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]", offset)
-- if match then
-- return TOKEN.FORMATTING, #match
-- end
-- match = strmatch(string, "^|r", offset)
-- if match then
-- return TOKEN.FORMATTING, #match
-- end
-- error("Unsupported formatting " .. string)
-- elseif byte == 10 or byte == 13 then -- white space (new lines)
-- local length = 1
-- return TOKEN.NEWLINE, length
-- else
if byte == 32 or byte == 9 then -- white space
local length = 1
repeat
byte = strbyte(string, offset + length)
length = length + 1
until byte ~= 32 and byte ~= 9
return TOKEN.WHITESPACE, length - 1
elseif context[#context] == "DOUBLE_QUOTED_STRING" then
if byte == 34 then -- double quoted string
return TOKEN.DOUBLE_QUOTE, 1
else
local content = strmatch(string, "^(.-[^\\])\"", offset)
return TOKEN.STRING, #content
end
elseif context[#context] == "MULTILINE_COMMENT" then
local commentEnd = strfind(string, "%]" .. strrep("=", multilineSize or 0) .. "%]", offset)
if commentEnd == nil then
return TOKEN.COMMENT, #string - offset
elseif commentEnd == offset then
return TOKEN.MULTILINE_COMMENT_END, (multilineSize or 0) + 2
else
return TOKEN.COMMENT, commentEnd - offset
end
else
if byte == 34 then -- double quoted string
return TOKEN.DOUBLE_QUOTE, 1
elseif byte == 39 then -- single quoted string
return TOKEN.SINGLE_QUOTE, 1
-- elseif byte == 91 then -- maybe multi line string?
-- return TOKEN.MULTILINE_QUOTE, 2+
elseif byte == 46 and strbyte(string, offset + 1) == 46 then -- double dash
return TOKEN.CONCAT, 2
elseif byte == 45 and strbyte(string, offset + 1) == 45 then -- double dash
local m = strmatch(string, "^%[([=]*)%[", offset + 2)
if m ~= nil then -- Multi line comment start
return TOKEN.MULTILINE_COMMENT_END, 4 + #m
else
return TOKEN.COMMENT, lineEnd - offset
end
else
local number = strmatch(string, "^0[xX][A-F0-9]", offset)
if number then
return TOKEN.NUMBER, #number
end
local number = strmatch(string, "^%d+.%d+[eE][+-%d]%d", offset)
if number then
return TOKEN.NUMBER, #number
end
local number = strmatch(string, "^%d+.%d+", offset)
if number then
return TOKEN.NUMBER, #number
end
local number = strmatch(string, "^%d+", offset)
if number then
return TOKEN.NUMBER, #number
end
local word = strmatch(string, "^[%w]+", offset)
if word then
if KEYWORDS[word] then
return word, #word
else
return TOKEN.IDENT, #word
end
else
return strsub(string, offset, offset), 1
end
end
end
end
BtWTodoConfigEditorMixin = CreateFromMixins(Internal.ScriptHandlerMixin)
function BtWTodoConfigEditorMixin:OnLoad()
Internal.ScriptHandlerMixin.OnLoad(self)
self:RegisterSupportedScriptHandlers("OnTextChanged", "OnChar", "OnEnterPressed", "OnTabPressed", "OnSpacePressed", "OnKeyUp")
local scrollBar = self.ScrollBar;
scrollBar:ClearAllPoints();
scrollBar:SetPoint("TOPLEFT", self, "TOPRIGHT", -13, -11);
scrollBar:SetPoint("BOTTOMLEFT", self, "BOTTOMRIGHT", -13, 9);
self.ScrollBar.ScrollDownButton:SetPoint("TOP", scrollBar, "BOTTOM", 0, 4);
self.ScrollBar.ScrollUpButton:SetPoint("BOTTOM", scrollBar, "TOP", 0, -4);
self.scrollBarHideable = 1;
scrollBar:Hide();
self.EditBox:SetWidth(self:GetWidth() - 18);
self.initialContext = ".lua"
self.lineBytes = {} -- Amount of bytes per line, including new line and formatting
self.lineContexts = {} -- Stores the line ending context, these are dot separated strings that describe the state of the code at the end of the line
end
function BtWTodoConfigEditorMixin:SetInitialContext(context)
if self.initialContext ~= context then
self.initialContext = context
self:Update()
end
end
function BtWTodoConfigEditorMixin:SetText(text)
wipe(self.lineBytes)
wipe(self.lineContexts)
self.EditBox:SetText(gsub(text, "\r\n", "\n"))
self:SetCursorPosition(0)
-- self.EditBox:SetFocus()
self:Update()
self.EditBox:SetFocus()
end
function BtWTodoConfigEditorMixin:GetDisplayText()
local text = self.EditBox:GetText()
return text:gsub("|r", ""):gsub("|c[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]","")
-- return self.EditBox:GetDisplayText()
end
function BtWTodoConfigEditorMixin:GetText()
return self.EditBox:GetText()
end
function BtWTodoConfigEditorMixin:GetCursorPosition()
return self.EditBox:GetCursorPosition()
end
function BtWTodoConfigEditorMixin:SetCursorPosition(...)
return self.EditBox:SetCursorPosition(...)
end
local function UpdateContext(context, token)
if token == "if" then
context[#context+1] = "IF"
context[#context+1] = "CONDITION"
elseif token == "while" then
context[#context+1] = "WHILE"
context[#context+1] = "CONDITION"
elseif token == "for" then
context[#context+1] = "FOR"
context[#context+1] = "FOR_CONDITION"
elseif token == "then" then
if context[#context] == "CONDITION" then
context[#context] = "BLOCK"
end
elseif token == "do" then
if context[#context] == "FOR_CONDITION" and context[#context-1] == "FOR" then
context[#context] = "BLOCK"
elseif context[#context] == "CONDITION" and context[#context-1] == "CONDITION" then
context[#context] = "BLOCK"
elseif context[#context] == "BLOCK" then
context[#context+1] = "BLOCK" -- do ... end for generic blocks is a thing
else
-- ERROR
end
elseif token == "elseif" then
if context[#context] == "BLOCK" and context[#context-1] == "IF" then
context[#context] = "CONDITION"
else
-- ERROR
end
elseif token == "else" then
if context[#context] ~= "BLOCK" or context[#context-1] ~= "IF" then
-- ERROR
end
elseif token == "repeat" then
context[#context+1] = "REPEAT"
context[#context+1] = "BLOCK"
elseif token == "until" then
if context[#context] == "BLOCK" and context[#context] == "BLOCK" then
context[#context] = "UNTIL_CONDITION"
else
-- ERROR
end
elseif token == "end" then
if context[#context] == "BLOCK" then
context[#context] = nil
if context[#context] == "IF" or context[#context] == "WHILE" or context[#context] == "FOR" then
context[#context] = nil
else
debug(context[#context])
end
else
-- ERROR
end
elseif token == TOKEN.MULTILINE_COMMENT_END then
if context[#context] == "MULTILINE_COMMENT" then
context[#context] = nil
else
context[#context+1] = "MULTILINE_COMMENT"
end
elseif token == TOKEN.DOUBLE_QUOTE then
if context[#context] == "DOUBLE_QUOTED_STRING" then
context[#context] = nil
else
context[#context+1] = "DOUBLE_QUOTED_STRING"
end
elseif token == TOKEN.SINGLE_QUOTE then
if context[#context] == "SINGLE_QUOTED_STRING" then
context[#context] = nil
else
context[#context+1] = "SINGLE_QUOTED_STRING"
end
end
end
local function UpdateLine(self, index, lineStart)
local text = self:GetText() -- Text with formatting
local cursor, cursorDiff = self:GetCursorPosition(), 0
debug("GetCursorPosition", cursor)
local context = {strsplit(".", self.lineContexts[index-1])} -- Context from before the line
local lastLine = false
local lineEnd = strfind(text, "\n", lineStart, true)
if lineEnd == nil then
lineEnd = #text + 1
lastLine = true
end
-- If cursor is before the line we dont need to adjust it at all,
-- and if its after the line we handle the adjustment at once
local adjustCursor = cursor + 1 >= lineStart and cursor <= lineEnd
local line
if lastLine then
line = strsub(text, lineStart)
else
line = strsub(text, lineStart, lineEnd + 1)
end
debug(line:gsub("|", "||"))
line = line:gsub("|r", ""):gsub("|c[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]","") -- Line without formatting
local relativeCursorOffset = 0
if adjustCursor then
relativeCursorOffset = cursor - (lineStart - 1)
debug("relativeCursorOffset", relativeCursorOffset, cursor, lineStart)
local cursorLine = strsub(text, lineStart, cursor)
for content in gmatch(cursorLine, "|c[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]") do
relativeCursorOffset = relativeCursorOffset - #content
end
for content in gmatch(cursorLine, "|r") do
relativeCursorOffset = relativeCursorOffset - #content
end
debug("relativeCursorOffset", relativeCursorOffset)
end
local offset = 1
local formatted = {}
local indentFixed = false
while offset < #line do
local token, length = GetNextToken(line, offset, lineEnd, context, self.multilineSize)
local content = strsub(line, offset, offset + length - 1)
if not indentFixed then
local indent = GetExpectedIndentForContext(context)
if token ~= TOKEN.WHITESPACE then
if token == "end" or token == "elseif" or token == "else" or token == TOKEN.MULTILINE_COMMENT_END then -- Reduce indent
indent = indent - 1
end
indent = indent * 4 -- @TODO custom indent
formatted[#formatted+1] = strrep(" ", indent)
if adjustCursor and offset <= relativeCursorOffset + 1 then -- Adjust cursor for indent
cursorDiff = cursorDiff + indent
end
else
local token = GetNextToken(line, offset + length, lineEnd, context, self.multilineSize)
if token == "end" or token == "elseif" or token == "else" or token == TOKEN.MULTILINE_COMMENT_END then -- Reduce indent
indent = indent - 1
end
indent = indent * 4 -- @TODO custom indent
content = strrep(" ", indent)
if adjustCursor then
if relativeCursorOffset + 1 >= offset + length then -- Cursor is after indent
debug(cursor, cursor - length + #content)
cursorDiff = cursorDiff - length + #content
elseif relativeCursorOffset + 1 >= offset then -- Cursor is within indent so set it to max end
cursorDiff = math.min(cursorDiff - length + #content, cursorDiff + (relativeCursorOffset - offset) + #content)
end
end
end
indentFixed = true
end
-- debug(token, "[" .. content .. "]")
local color = tokenColors[token]
if token == TOKEN.MULTILINE_COMMENT_END then
self.multilineSize = length - 4
end
if color then -- DONT COLOR WHITESPACES, it'll cause issues because length is wrong, and needs to be wrong
local markup = color:GenerateHexColorMarkup()
formatted[#formatted+1] = format("%s%s|r", markup, content)
if adjustCursor then
if relativeCursorOffset > offset - 1 then -- Adjust for the |c, place before if its cursor was at the start
cursorDiff = cursorDiff + #markup
debug("Adjust cursor for added ||c", cursorDiff)
end
if relativeCursorOffset >= offset - 1 + length then -- Adjust for the |r
cursorDiff = cursorDiff + 2
debug("Adjust cursor for added ||r", cursorDiff)
end
end
else
formatted[#formatted+1] = content
end
UpdateContext(context, token)
offset = offset + length
end
formatted = table.concat(formatted, "")
self.EditBox:HighlightText(lineStart - 1, lineEnd) -- -1 to highlight from BEFORE the first character
-- if index == 3 then
-- debug("[" .. formatted .. "]", lineStart, lineEnd)
-- error("TEST")
-- end
self.EditBox:Insert(formatted)
-- if index == 3 then
-- debug("[" .. formatted .. "]", lineStart, lineEnd)
-- error("TEST")
-- end
if cursor > lineEnd then
debug("SetCursorPosition 3", cursor - (lineEnd - lineStart) + #formatted - 1)
self:SetCursorPosition(cursor - (lineEnd - lineStart) + #formatted - 1)
elseif cursor >= lineStart - 1 then
debug("SetCursorPosition 2", lineStart - 1, relativeCursorOffset, cursorDiff)
self:SetCursorPosition(lineStart - 1 + relativeCursorOffset + cursorDiff)
else
debug("SetCursorPosition 1", cursor)
self:SetCursorPosition(cursor)
end
lineEnd = lineStart + #formatted - 1
self.lineBytes[index] = lineEnd - lineStart + 1
debug("lengths", lineEnd - lineStart)
local context = table.concat(context, ".")
local contextChanged = self.lineContexts[index] ~= context
self.lineContexts[index] = context
return true, lineEnd + 1, lastLine, contextChanged
end
function BtWTodoConfigEditorMixin:UpdateFromLine(index, endIndex)
self.updating = true
--[=[@alpha@
xpcall(function()
index = index or 1
local offset = 1
for i=1,index-1 do
offset = offset + self.lineBytes[i]
end
local _, lastLine, contextChanged
repeat
debug("UpdateFromLine", index, offset, self.EditBox:GetCursorPosition())
_, offset, lastLine, contextChanged = UpdateLine(self, index, offset)
debug("UpdateFromLine", index, offset, self.EditBox:GetCursorPosition(), lastLine, contextChanged)
index = index + 1
until lastLine or (not contextChanged and index > endIndex)
if lastLine then
while self.lineBytes[index] do
table.remove(self.lineBytes, index)
table.remove(self.lineContexts, index)
end
end
end, geterrorhandler())
debug("----------")
--@end-alpha@]=]
self.updating = nil
end
function BtWTodoConfigEditorMixin:Update()
self.lineContexts[0] = self.initialContext
self:UpdateFromLine()
end
function BtWTodoConfigEditorMixin:GetLineForOffset(offset)
local line = 1
while self.lineBytes[line] ~= nil and offset > self.lineBytes[line] do
offset = offset - self.lineBytes[line]
line = line + 1
end
return line
end
function BtWTodoConfigEditorMixin:OnTextChanged(...)
debug("OnTextChanged", ...)
self:RunScript("OnTextChanged", ...)
end
function BtWTodoConfigEditorMixin:OnChar(text)
-- if not self.updating then
-- local offset = self.EditBox:GetCursorPosition()
-- local line = self:GetLineForOffset(offset)
-- self:UpdateFromLine(line)
-- self:RunScript("OnChar", text)
-- end
end
function BtWTodoConfigEditorMixin:OnSpacePressed(...)
-- if not self.updating then
-- local offset = self.EditBox:GetCursorPosition()
-- local line = self:GetLineForOffset(offset)
-- self.EditBox:Insert(" ")
-- self:UpdateFromLine(line)
-- self:RunScript("OnSpacePressed", ...)
-- end
end
function BtWTodoConfigEditorMixin:OnEnterPressed(...)
if not self.updating then
local offset = self.EditBox:GetCursorPosition()
local line = self:GetLineForOffset(offset)
local indent = ""
-- These are just to push the later values down so we dont have to redo every line
tinsert(self.lineBytes, line, 0)
tinsert(self.lineContexts, line, 0)
self.EditBox:Insert("\n" .. indent)
self:UpdateFromLine(line)
self:RunScript("OnEnterPressed", ...)
end
end
function BtWTodoConfigEditorMixin:OnTabPressed(...)
if not self.updating then
local offset = self.EditBox:GetCursorPosition()
local line = self:GetLineForOffset(offset)
self.EditBox:Insert(" ")
self:UpdateFromLine(line)
self:RunScript("OnTabPressed", ...)
end
end
function BtWTodoConfigEditorMixin:OnKeyUp(key)
if key == "BACKSPACE" or key == "DELETE" then
local offset = self.EditBox:GetCursorPosition()
local line = self:GetLineForOffset(offset)
self:UpdateFromLine(line, line + 1)
elseif key ~= "TAB" and key ~= "ENTER" and not IsModifierKeyDown() then
local offset = self.EditBox:GetCursorPosition()
local line = self:GetLineForOffset(offset)
self:UpdateFromLine(line)
end
self:RunScript("OnKeyUp", key)
end
-- [[ Todo Config Panel ]]
local FUNCTION_TAB_COMPLETED = 1
local FUNCTION_TAB_TEXT = 2
local FUNCTION_TAB_CLICK = 3
local FUNCTION_TAB_TOOLTIP = 4
local MODE_TAB_ADVANCED = 1
local MODE_TAB_BASIC = 2
BtWTodoConfigTodoPanelMixin = {}
function BtWTodoConfigTodoPanelMixin:OnLoad()
self.todos = {} -- All todos currently being edited
self.todo = nil -- Set to the table stored within self.todos when a todo is selected
UIDropDownMenu_SetText(self.TodoDropDown, L["Select a todo to edit"]);
UIDropDownMenu_SetWidth(self.TodoDropDown, 175);
UIDropDownMenu_JustifyText(self.TodoDropDown, "LEFT");
UIDropDownMenu_Initialize(self.TodoDropDown, function (_, level, menuList)
if BtWTodoData == nil then
return
end
local info = UIDropDownMenu_CreateInfo();
info.func = function (_, arg1, arg2, checked)
self:SetTodo(arg1)
end
local tbl = {}
for _,todo in pairs(self.todos) do
tbl[#tbl+1] = todo
end
for id,todo in Internal.IterateTodos() do
if not self.todos[id] then
tbl[#tbl+1] = todo
end
end
table.sort(tbl, function (a, b)
if a.name == b.name then
return tostring(a.id) < tostring(b.id)
end
return a.name < b.name
end)
for _,todo in ipairs(tbl) do
info.text = todo.registered and format("%s *", todo.name) or todo.name
info.arg1 = todo.id
info.checked = self.todo and self.todo.id == todo.id or false
UIDropDownMenu_AddButton(info, level)
end
end);
self.Edit.Name:SetScript("OnTextChanged", function (_)
if self.todo == nil then
return
end
self.todo.name = self.Edit.Name:GetText()
UIDropDownMenu_SetText(self.TodoDropDown, self.todo.name)
end)
self.Edit.AddDropDown:SetScript("OnChange", function (_, key)
if self.todo == nil then
return
end
self.Edit.States:Add(key)
end)
self.Edit.States:SetScript("OnAdd", function (_, state)
if self.todo == nil then
return
end
self.Edit.AddStateText:SetShown(self.Edit.States:GetCount() <= 2)
self:ValidateScript()
--@TODO add defaults to Basic completed/text/click
end)
self.Edit.States:SetScript("OnRemove", function (_)
self.Edit.AddStateText:SetShown(self.Edit.States:GetCount() <= 2)
self:ValidateScript()
end)
self.Edit.Editor:SetScript("OnTextChanged", function (editor)
if self.todo == nil then
return
end
if self.editor == FUNCTION_TAB_COMPLETED then
self.todo.completed = self.Edit.Editor:GetDisplayText()
elseif self.editor == FUNCTION_TAB_TEXT then
self.todo.text = self.Edit.Editor:GetDisplayText()
elseif self.editor == FUNCTION_TAB_CLICK then
self.todo.click = self.Edit.Editor:GetDisplayText()
elseif self.editor == FUNCTION_TAB_TOOLTIP then
self.todo.tooltip = self.Edit.Editor:GetDisplayText()
end
self:ValidateScript()
end)
self.AddItem:SetOnCancelCallback(function ()
self.AddItem:Clear()
self.AddItem:Hide()
end)
InterfaceOptions_AddCategory(self)
do
self.FunctionTabPool = CreateFramePool("Button", self.Edit.FunctionTabHeader, Internal.IsDragonflight() and "PanelTopTabButtonTemplate" or "TabButtonTemplate");
self.Edit.FunctionTabHeader.Tabs = {};
local FunctionTabs = {BTWTODO_COMPLETED, BTWTODO_TEXT, BTWTODO_CLICK, BTWTODO_TOOLTIP};
local previous
for i=1,#FunctionTabs do
local tab = self.FunctionTabPool:Acquire();
tab:SetID(i);
if previous then
tab:SetPoint("LEFT", previous, "RIGHT", 0, 0);
else
tab:SetPoint("TOPLEFT", 0, 0);
end
tab:SetText(FunctionTabs[i]);
tab:SetScript("OnClick", function (self)
self:GetParent():GetParent():GetParent():SetEditor(self:GetID())
PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
end)
self.Edit.FunctionTabHeader.Tabs[tab:GetID()] = tab;
PanelTemplates_TabResize(tab);
tab:Show();
previous = tab;
end
PanelTemplates_UpdateTabs(self.Edit.FunctionTabHeader)
end
do
self.ModeTabPool = CreateFramePool("Button", self.Edit.ModeTabHeader, Internal.IsDragonflight() and "PanelTopTabButtonTemplate" or "TabButtonTemplate");
self.Edit.ModeTabHeader.Tabs = {};
local ModeTabs = {BTWTODO_ADVANCED, BTWTODO_BASIC};
local previous
for i=1,#ModeTabs do
local tab = self.ModeTabPool:Acquire();
tab:SetID(i);
if previous then
tab:SetPoint("RIGHT", previous, "LEFT", 0, 0);
else
tab:SetPoint("TOPRIGHT", 0, 0);
end
tab.isDisabled = i ~= 1;
tab:SetEnabled(i == 1); -- Only Advanced is enabled atm
tab:SetText(ModeTabs[i]);
tab:SetScript("OnClick", function (self)
self:GetParent():GetParent():GetParent():SetMode(self:GetID())
PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON);
end)
self.Edit.ModeTabHeader.Tabs[tab:GetID()] = tab;
PanelTemplates_TabResize(tab);
tab:Show();
previous = tab;
end
PanelTemplates_UpdateTabs(self.Edit.ModeTabHeader)
end
end
function BtWTodoConfigTodoPanelMixin:ValidateScript()
if not self.todo then
return
end
local driver = self.todo.driver
local func, err
if self.editor == FUNCTION_TAB_COMPLETED then
local source = self.todo.completed
func, err = Internal.CreateStateDriverFunction(driver, "Completed", source, false)
elseif self.editor == FUNCTION_TAB_TEXT then
local source = self.todo.text
func, err = Internal.CreateStateDriverFunction(driver, "Text", source, false, 'L')
elseif self.editor == FUNCTION_TAB_CLICK then
local source = self.todo.click
func, err = Internal.CreateStateDriverFunction(driver, "Click", source, false, 'button')
elseif self.editor == FUNCTION_TAB_TOOLTIP then
local source = self.todo.tooltip
func, err = Internal.CreateStateDriverFunction(driver, "Tooltip", source, false, 'L, tooltip')
end
if not func then
self.Edit.ErrorText:SetText(err)
self.Edit.ErrorText:Show()
return
end
if self.editor == FUNCTION_TAB_COMPLETED then
driver.completed = func
elseif self.editor == FUNCTION_TAB_TEXT then
driver.text = func
elseif self.editor == FUNCTION_TAB_CLICK then
driver.click = func
elseif self.editor == FUNCTION_TAB_TOOLTIP then
driver.tooltip = func
end
driver:SetCharacter(Internal.GetPlayer())
local status, err = pcall(function ()
if self.editor == FUNCTION_TAB_COMPLETED then
driver.completed({
GetName = function ()
return driver:GetName()
end,
IsFlaggedCompleted = function ()
return driver:IsFlaggedCompleted()
end,
}, driver:GetCharacter(), driver.states)
elseif self.editor == FUNCTION_TAB_TEXT then
driver.text({
GetName = function ()
return driver:GetName()
end,
IsCompleted = function ()
return driver:IsCompleted()
end,
IsFlaggedCompleted = function ()
return driver:IsFlaggedCompleted()
end,
}, driver:GetCharacter(), driver.states, Internal.L)
elseif self.editor == FUNCTION_TAB_CLICK then
-- Not really sure what to do with this
--[[
driver.click({
GetName = function ()
return driver:GetName()
end,
IsCompleted = function ()
return driver:IsCompleted()
end,
IsFlaggedCompleted = function ()
return driver:IsFlaggedCompleted()
end,
}, driver:GetCharacter(), driver.states, button)
]]
elseif self.editor == FUNCTION_TAB_TOOLTIP then
driver.tooltip({
GetName = function ()
return driver:GetName()
end,
IsCompleted = function ()
return driver:IsCompleted()
end,
IsFlaggedCompleted = function ()
return driver:IsFlaggedCompleted()
end,
}, driver:GetCharacter(), driver.states, Internal.L, GameTooltip)
end
end)
if not status then
self.Edit.ErrorText:SetText(err)
self.Edit.ErrorText:Show()
return
end
self.Edit.ErrorText:Hide()
end
function BtWTodoConfigTodoPanelMixin:IsEdited()
local registered = Internal.GetRegisteredTodo(self.todo.id)
if not registered then
return false
end
return not Internal.CompareTodos(self.todo, registered)
end
function BtWTodoConfigTodoPanelMixin:IsUpdated()
return Internal.CheckTodoForUpdate(self.todo.id, self.todo.version)
end
function BtWTodoConfigTodoPanelMixin:ParseTodo(id)
local tbl = Internal.GetTodo(id)
if not tbl then
error("Unknown todo " .. tostring(id))
end
local todo = {}
todo.uuid = tbl.uuid
todo.id = tbl.id
todo.name = tbl.name
todo.registered = tbl.registered
todo.driver, todo.states = Internal.CreateStateDriver(tbl.id, "Editor", tbl.states, "", "", "", "")
for index,state in ipairs(tbl.states) do
todo.states[index].source = state
end
todo.completed = tbl.completed
todo.text = tbl.text
todo.click = tbl.click
todo.tooltip = tbl.tooltip
if type(id) == "string" then
todo.version = tbl.version or 0
end
self.todos[id] = todo
return todo
end
function BtWTodoConfigTodoPanelMixin:SetTodo(id)
if self.todos[id] then -- Already started editing
self.todo = self.todos[id]
else -- Read the saved todo and cache it locally for editing
self.todo = self:ParseTodo(id)
self.editor = self.editor or FUNCTION_TAB_COMPLETED
self.mode = self.mode or MODE_TAB_ADVANCED
end
UIDropDownMenu_SetText(self.TodoDropDown, self.todo.name)
self.Edit.Name:SetText(self.todo.name)
self.Edit.Name:SetCursorPosition(0)
self.Edit.States:Init(self.todo.states)
self.Edit.AddStateText:SetShown(self.Edit.States:GetCount() <= 2)
self.Edit.RevertButton:SetEnabled(self:IsEdited())
self.Edit:Show()
self.AddItem:Hide()
self.ActiveImport:Hide()
self.Import:Hide()
self.Export:Hide()
self:Update()
-- I dont know why, but if we dont reset the first tabs anchor they wont show, these anchors are in the xml already
-- self.Edit.ModeTabHeader.Tabs[1]:ClearAllPoints()
-- self.Edit.ModeTabHeader.Tabs[1]:SetPoint("TOPRIGHT", 0, 0)
-- self.Edit.FunctionTabHeader.Tabs[1]:ClearAllPoints()
-- self.Edit.FunctionTabHeader.Tabs[1]:SetPoint("TOPLEFT", 0, 0)
return self.todo.driver
end
function BtWTodoConfigTodoPanelMixin:AddTodo()
local count = #BtWTodoData+1
while self.todos[count] or BtWTodoData[count] do
count = count + 1
end
local todo = {
id = count,
name = L["New Todo"],
completed = "return self:IsFlaggedCompleted()",
text = "return self:IsCompleted() and Images.COMPLETE or \"\"",
click = [[self:SetFlaggedCompleted(not self:IsFlaggedCompleted())]],
tooltip = "",
}
todo.driver, todo.states = Internal.CreateStateDriver(todo.id, "Editor", {}, "", "", "", "")
self.editor = self.editor or FUNCTION_TAB_COMPLETED
self.mode = self.mode or MODE_TAB_ADVANCED
self.todos[count] = todo
self:SetTodo(count)
self.Edit.Name:SetFocus()
end
function BtWTodoConfigTodoPanelMixin:CloneTodo()
if not self.todo then
return
end
local count = #BtWTodoData+1
while self.todos[count] or BtWTodoData[count] do
count = count + 1
end
local todo = {
id = count,
name = format(L["%s (Clone)"], self.todo.name),
completed = self.todo.completed,
text = self.todo.text,
click = self.todo.click,
tooltip = self.todo.tooltip,
}
local states = {}
for _,state in ipairs(self.todo.states) do
states[#states+1] = CopyTable(state.source)
end
todo.driver, todo.states = Internal.CreateStateDriver(todo.id, "Editor", states, "", "", "", "")
self.editor = self.editor or FUNCTION_TAB_COMPLETED
self.mode = self.mode or MODE_TAB_ADVANCED
self.todos[count] = todo
self:SetTodo(count)
self.Name:SetFocus()
end
function BtWTodoConfigTodoPanelMixin:ToggleExport()
if self.Export:IsShown() then
self:HideExport()
else
self:ExportTodo()
end
end
function BtWTodoConfigTodoPanelMixin:ExportTodo()
if not self.todo then
return
end
local data = self.todo
if not data.uuid then
if type(data.id) == "number" then
data.uuid = Internal.GenerateUUID()
local target = Internal.GetTodo(data.id)
if target then
target.uuid = data.uuid
end
else
data.uuid = data.id
end
end
local tbl = {}
tbl.id = data.id
tbl.uuid = data.uuid
tbl.name = data.name
tbl.states = {}
for _,state in ipairs(data.states) do
tbl.states[#tbl.states+1] = state.source
end
tbl.completed = data.completed
tbl.text = data.text
tbl.click = data.click
tbl.tooltip = data.tooltip
tbl.version = data.version
self.Export.Scroll.EditBox.text = Internal.Export("todo", tbl)
self.Export.Scroll.EditBox:SetText(self.Export.Scroll.EditBox.text)
self.Edit:Hide()
self.AddItem:Hide()
self.ActiveImport:Hide()
self.Import:Hide()
self.Export:Show()
end
function BtWTodoConfigTodoPanelMixin:HideExport()
self.Edit:SetShown(self.todo ~= nil)
self.AddItem:Hide()
self.ActiveImport:Hide()
self.Import:Hide()
self.Export:Hide()
end
function BtWTodoConfigTodoPanelMixin:ImportTodo(text)
local success, result
if text then
success, result = External.Import(text)
end
if success then
self.todo = nil
UIDropDownMenu_SetText(self.TodoDropDown, L["Select a todo to edit"]);
self.ActiveImport.todo = result
self.ActiveImport.update = nil
if result.uuid then
if self.todos[result.uuid] then
self.ActiveImport.update = self.todos[result.uuid]
end
if not self.ActiveImport.update then
for _,possible in pairs(self.todos) do
if possible.uuid == result.uuid then
self.ActiveImport.update = possible
break
end
end
end
if not self.ActiveImport.update then
local target = Internal.GetTodoByUUID(result.uuid)
if target then
self.ActiveImport.update = self:ParseTodo(target.id)
end
end
end
self.ActiveImport.NameText:SetText(format(L["Importing todo \"%s\""], result.name))
if self.ActiveImport.update then
self.ActiveImport.ImportButton:Hide()
self.ActiveImport.UpdateButton:Show()
self.ActiveImport.CopyButton:Show()
else
self.ActiveImport.ImportButton:Show()
self.ActiveImport.UpdateButton:Hide()
self.ActiveImport.CopyButton:Hide()
end
self.Edit:Hide()
self.AddItem:Hide()
self.ActiveImport:Hide()
self.Import:Hide()
self.Export:Hide()
self.ActiveImport:Show()
elseif self.Import:IsShown() and result then
self.Import.ErrorText:SetText(result)
self.Import.ErrorText:Show()
else
self.todo = nil
UIDropDownMenu_SetText(self.TodoDropDown, L["Select a todo to edit"]);
self.Edit:Hide()
self.AddItem:Hide()
self.ActiveImport:Hide()
self.Import:Hide()
self.Export:Hide()
self.Import:Show()
if result then
self.Import.ErrorText:SetText(result)
self.Import.ErrorText:Show()
else
self.Import.ErrorText:Hide()
end
end
end
function BtWTodoConfigTodoPanelMixin:CompleteImportTodo(source, update)
print(source, update)
local id = update and update.id
if not id then
local count = #BtWTodoData+1
while self.todos[count] or BtWTodoData[count] do
count = count + 1
end
id = count
end
local todo = {
id = id,
uuid = update ~= false and source.uuid or nil,
name = source.name,
completed = source.completed,
text = source.text,
click = source.click,
tooltip = source.tooltip,
}
local states = {}
for _,state in ipairs(source.states) do
states[#states+1] = CopyTable(state)
end
todo.driver, todo.states = Internal.CreateStateDriver(todo.id, "Editor", states, "", "", "", "")
self.editor = self.editor or FUNCTION_TAB_COMPLETED
self.mode = self.mode or MODE_TAB_ADVANCED
self.todos[id] = todo
self:SetTodo(id)
self.Edit.Name:SetFocus()
end
function BtWTodoConfigTodoPanelMixin:HideImport()
self.Import:Hide()
end
function BtWTodoConfigTodoPanelMixin:RevertTodo()
if not self.todo then
return
end
local id = self.todo.id
local tbl = Internal.GetRegisteredTodo(id)
if not tbl then
error("Unknown todo " .. tostring(id))
end
local todo = {}
todo.id = tbl.id
todo.name = tbl.name
todo.registered = tbl.registered
todo.driver, todo.states = Internal.CreateStateDriver(tbl.id, "Editor", tbl.states, "", "", "", "")
for index,state in ipairs(tbl.states) do
todo.states[index].source = state
end
todo.completed = tbl.completed
todo.text = tbl.text
todo.click = tbl.click
todo.tooltip = tbl.tooltip
if type(id) == "string" then
todo.version = tbl.version or 0
end
self.editor = self.editor or FUNCTION_TAB_COMPLETED
self.mode = self.mode or MODE_TAB_ADVANCED
self.todos[id] = todo
self:SetTodo(id)
end
function BtWTodoConfigTodoPanelMixin:GetTodoChangeLog()
if not self.todo then
return
end
return Internal.GetTodoChangeLog(self.todo.id)
end
function BtWTodoConfigTodoPanelMixin:Update()
if self.todo == nil then
return
end
PanelTemplates_SetTab(self.Edit.ModeTabHeader, self.mode)
PanelTemplates_SetTab(self.Edit.FunctionTabHeader, self.editor)
if self.mode == MODE_TAB_ADVANCED then
if self.editor == FUNCTION_TAB_COMPLETED then
self.Edit.Editor:SetText(self.todo.completed or "")
elseif self.editor == FUNCTION_TAB_TEXT then
self.Edit.Editor:SetText(self.todo.text or "")
elseif self.editor == FUNCTION_TAB_CLICK then
self.Edit.Editor:SetText(self.todo.click or "")
elseif self.editor == FUNCTION_TAB_TOOLTIP then
self.Edit.Editor:SetText(self.todo.tooltip or "")
else
error("Unknown editor " .. tostring(self.editor))
end
self.Edit.Editor:Show()
elseif self.mode == MODE_TAB_BASIC then
self.Edit.Editor:Hide()
if self.editor == FUNCTION_TAB_COMPLETED then
elseif self.editor == FUNCTION_TAB_TEXT then
elseif self.editor == FUNCTION_TAB_CLICK then
elseif self.editor == FUNCTION_TAB_TOOLTIP then
else
error("Unknown editor " .. tostring(self.editor))
end
end
self:ValidateScript()
end
function BtWTodoConfigTodoPanelMixin:SetEditor(editor)
self.editor = editor
self:Update()
end
function BtWTodoConfigTodoPanelMixin:SetMode(mode)
self.mode = mode
self:Update()
end
function BtWTodoConfigTodoPanelMixin:IterateTodos()
local tbl = {}
local tmp = {}
for _,todo in ipairs(self.todos) do
tbl[#tbl+1] = todo
tmp[todo.id] = true
end
for _,todo in Internal.IterateTodos() do
if not tmp[todo.id] then
tbl[#tbl+1] = todo
end
end
table.sort(tbl, function (a, b)
if a.name == b.name then
-- Just want a consistent ordering, doesnt matter we are
-- converting ints to strings prevents numerical ordering
return tostring(a.id) < tostring(b.id)
end
return a.name < b.name
end)
return function (tbl, index)
index = index + 1
if tbl[index] then
return index, tbl[index]
end
end, tbl, 0
end
function BtWTodoConfigTodoPanelMixin:okay()
xpcall(function()
for id,data in pairs(self.todos) do
local tbl = {}
tbl.id = id
tbl.name = data.name
tbl.states = {}
for _,state in ipairs(data.states) do
tbl.states[#tbl.states+1] = state.source
end
tbl.completed = data.completed
tbl.text = data.text
tbl.click = data.click
tbl.tooltip = data.tooltip
tbl.version = data.version
Internal.SaveTodo(tbl)
end
External.TriggerEvent("TODOS_CHANGED")
end, geterrorhandler())
end
function BtWTodoConfigTodoPanelMixin:cancel()
xpcall(function()
self.AddItem:Hide()
end, geterrorhandler())
end
function BtWTodoConfigTodoPanelMixin:default()
xpcall(function()
end, geterrorhandler())
end
function BtWTodoConfigTodoPanelMixin:refresh()
xpcall(function()
wipe(self.todos)
self.todo = nil
UIDropDownMenu_SetText(self.TodoDropDown, L["Select a todo to edit"]);
self.Edit:Hide()
self.AddItem:Hide()
self.ActiveImport:Hide()
self.Import:Hide()
self.Export:Hide()
end, geterrorhandler())
end
-- [[ Lists Panel ]]
BtWTodoConfigTodoItemMixin = {}
function BtWTodoConfigTodoItemMixin:Init(data)
self.data = data
self.AddButton:SetShown(data.type == "category")
self.VisibilityButton:SetShown(data.type == "todo")
self.VisibilityButton.texture:SetTexture(data.hidden and 136315 or 136293)
if data.type == "todo" then
if BtWTodoConfigTodoPanel.todos[data.todo] then
self:SetText(BtWTodoConfigTodoPanel.todos[data.todo].name or L["Unnamed"])
else
self:SetText(External.GetTodoName(data.todo, true) or format("|cFF990000%s|r (%s)", L["Missing Todo"], data.todo))
end
self.Text:SetTextColor(1, 1, 1, 1)
elseif data.type == "category" then
self:SetText(data.source.name)
self.Text:SetTextColor(data.source.color:GetRGB())
else
error("Unknown data type " .. tostring(data.type))
end
end
function BtWTodoConfigTodoItemMixin:Add()
if self.data.type == "category" then
local panel = self:GetParent():GetParent():GetParent()
local addTodoCategory = self.data
panel.AddItem:SetTitle(BTWTODO_ADD_TODO, BTWTODO_ADD_TODO_SUBTEXT)
panel.AddItem:SetAutoCompleteCallback(function (_, tbl, text, offset, length)
local text = strsub(text, offset, length):lower()
for _,todo in BtWTodoConfigTodoPanel:IterateTodos() do
local name = todo.name:lower()
if #name >= #text and strsub(name, offset, length) == text then
tbl[#tbl+1] = todo.name
end
end
end)
panel.AddItem:SetOnOkayCallback(function ()
local name = panel.AddItem:GetText()
local lower = name:lower()
local addTodo = nil
for _,todo in BtWTodoConfigTodoPanel:IterateTodos() do
if todo.name:lower() == lower then
addTodo = todo
break
end
end
if addTodo then
panel.AddItem:Clear()
panel.AddItem:Hide()
local list = panel:GetList()
local dataProvider = list.todos
for i=addTodoCategory.orderIndex+1,dataProvider:GetSize() do
local item = dataProvider:Find(i)
item.orderIndex = item.orderIndex+1
end
local elementData = { type = "todo", todo = addTodo.id, orderIndex = addTodoCategory.orderIndex+1 }
dataProvider:Insert(elementData);
panel.TodoScrollBox:ScrollToElementDataIndex(elementData.orderIndex + 1, ScrollBoxConstants.AlignNearest)
else
UIErrorsFrame:AddMessage(format(L["Unknown todo %s"], name), 1.0, 0.1, 0.1, 1.0);
end
end)
panel.AddItem:Show()
end
end
function BtWTodoConfigTodoItemMixin:ToggleVisibility()
if self.data.type == "todo" then
self.data.hidden = not self.data.hidden
self.VisibilityButton.texture:SetTexture(self.data.hidden and 136315 or 136293)
end
end
function BtWTodoConfigTodoItemMixin:Edit()
if self.data.type == "todo" then
InterfaceOptionsFrame_OpenToCategory(BTWTODO_TODOS)
BtWTodoConfigTodoPanel:SetTodo(self.data.todo)
elseif self.data.type == "category" then
local source = self.data.source
ColorPickerFrame.previousValues = { source.color:GetRGB() }
ColorPickerFrame:SetColorRGB(source.color:GetRGB())
ColorPickerFrame.hasOpacity = false
ColorPickerFrame.func = function ()
source.color:SetRGBA(ColorPickerFrame:GetColorRGB())
self.Text:SetTextColor(source.color:GetRGB())
end
ColorPickerFrame.cancelFunc = function (previousValues)
source.color:SetRGBA(unpack(previousValues))
self.Text:SetTextColor(source.color:GetRGB())
end
ColorPickerFrame:Hide() -- Incase its already visible
ColorPickerFrame:Show()
end
end
function BtWTodoConfigTodoItemMixin:Delete()
self:GetParent():GetParent():Remove(self.data.orderIndex)
end
local function ListDropDown_Initialize(self, level, menuList)
local frame = self:GetParent()
local selected = frame:GetList()
local info = UIDropDownMenu_CreateInfo();
info.func = function (self, arg1, arg2, checked)
frame:SetList(arg1)
end
local tbl = {}
for _,item in IterateLists() do
tbl[#tbl+1] = item
end
table.sort(tbl, function (a, b)
if a.name == b.name then
return tostring(a.id) < tostring(b.id)
end
return a.name < b.name
end)
for _,item in ipairs(tbl) do
info.text = item.name or item.id
info.arg1 = item.id
info.checked = selected == item
UIDropDownMenu_AddButton(info, level);
end
end
BtWTodoConfigListsPanelMixin = {}
function BtWTodoConfigListsPanelMixin:OnLoad()
self.lists = {}
self.list = nil
UIDropDownMenu_SetWidth(self.ListDropDown, 175);
UIDropDownMenu_JustifyText(self.ListDropDown, "LEFT");
UIDropDownMenu_Initialize(self.ListDropDown, ListDropDown_Initialize);
self.Name:SetScript("OnTextChanged", function (_)
if self.list == nil then
return
end
self.list.name = self.Name:GetText()
UIDropDownMenu_SetText(self.ListDropDown, self.list.name)
end)
do -- Todos
local view = CreateScrollBoxListLinearView();
view:SetElementExtent(30);
if Internal.IsDragonflight() then
view:SetElementInitializer("BtWTodoConfigTodoItemTemplate", function(button, elementData)
button:Init(elementData);
local startIndex, endIndex = self.TodoScrollBox:GetDragRange()
button.Drag:SetShown(startIndex and elementData.orderIndex >= startIndex and elementData.orderIndex <= endIndex)
end);
else
view:SetElementInitializer("Button", "BtWTodoConfigTodoItemTemplate", function(button, elementData)
button:Init(elementData);
local startIndex, endIndex = self.TodoScrollBox:GetDragRange()
button.Drag:SetShown(startIndex and elementData.orderIndex >= startIndex and elementData.orderIndex <= endIndex)
end);
end
ScrollUtil.InitScrollBoxListWithScrollBar(self.TodoScrollBox, self.TodoScrollBar, view);
end
self.AddItem:SetOnCancelCallback(function ()
self.AddItem:Clear()
self.AddItem:Hide()
end)
InterfaceOptions_AddCategory(self)
end
function BtWTodoConfigListsPanelMixin:GetList()
return self.list
end
function BtWTodoConfigListsPanelMixin:SetList(id)
self.list = self.lists[id]
if not self.list then
local tbl = Internal.GetList(id)
if not tbl then
error("Unknown list " .. tostring(id))
end
local list = {}
list.id = tbl.id
list.name = tbl.name
list.version = tbl.version
local listData = {};
local previousCategory
for _,item in ipairs(tbl.todos) do
if previousCategory ~= item.category then
listData[#listData+1] = { type = "category", category = item.category, source = self.categories[item.category], orderIndex = #listData+1 }
previousCategory = item.category
end
listData[#listData+1] = { type = "todo", todo = item.id, hidden = item.hidden, version = item.version, orderIndex = #listData+1 }
end
local dataProvider = CreateDataProvider(listData);
dataProvider:SetSortComparator(function (a, b)
return a.orderIndex < b.orderIndex
end, true)
list.todos = dataProvider
self.lists[id] = list
self.list = list
end
UIDropDownMenu_SetText(self.ListDropDown, self.list.name)
self.Name:SetText(self.list.name)
self.Name:SetCursorPosition(0)
self.TodoScrollBox:SetDataProvider(self.list.todos)
end
function BtWTodoConfigListsPanelMixin:AddList()
local count = #BtWTodoLists+1
while self.lists[count] or BtWTodoLists[count] do
count = count + 1
end
local list = {
id = count,
name = L["New List"],
todos = {},
}
local dataProvider = CreateDataProvider()
dataProvider:SetSortComparator(function (a, b)
return a.orderIndex < b.orderIndex
end, true)
list.todos = dataProvider
self.lists[count] = list
self:SetList(count)
self.Name:SetFocus()
end
function BtWTodoConfigListsPanelMixin:CloneList()
if not self.list then
return
end
local count = #BtWTodoLists+1
while self.lists[count] or BtWTodoLists[count] do
count = count + 1
end
local list = {
id = count,
name = format(L["%s (Clone)"], self.list.name),
todos = {},
}
local dataProvider = CreateDataProvider(self.list.todos.collection)
dataProvider:SetSortComparator(function (a, b)
return a.orderIndex < b.orderIndex
end, true)
list.todos = dataProvider
self.lists[count] = list
self:SetList(count)
self.Name:SetFocus()
end
function BtWTodoConfigListsPanelMixin:GetCategoryByName(name)
for id,category in pairs(self.categories) do
if category.name == name then
return category
end
end
local category = { id = #self.categories+1, name = name, color = CreateColor(math.random(), math.random(), math.random(), 1) }
self.categories[category.id] = category
return category
end
function BtWTodoConfigListsPanelMixin:OnAddCategoryClicked()
self.AddItem:SetTitle(BTWTODO_ADD_CATEGORY, BTWTODO_ADD_CATEGORY_SUBTEXT)
self.AddItem:SetAutoCompleteCallback(function (_, tbl, text, offset, length)
local text = strsub(text, offset, length):lower()
for _,category in Internal.IterateCategories() do
local name = category.name:lower()
if #name >= #text and strsub(name, offset, length) == text then
tbl[#tbl+1] = category.color:WrapTextInColorCode(category.name)
end
end
end)
self.AddItem:SetOnOkayCallback(function (_, text)
local category = self:GetCategoryByName(text)
local list = self:GetList()
local dataProvider = list.todos
local orderIndex = dataProvider:GetSize() + 1
local elementData = { type = "category", category = category.id, source = category, orderIndex = orderIndex }
dataProvider:Insert(elementData);
self.TodoScrollBox:ScrollToElementDataIndex(orderIndex, ScrollBoxConstants.AlignNearest)
self.AddItem:Clear()
self.AddItem:Hide()
end)
self.AddItem:Show()
end
function BtWTodoConfigListsPanelMixin:okay()
xpcall(function()
do
for id,category in pairs(self.categories) do
External.UpdateCategory(id, category.name, category.color)
end
end
for id,list in pairs(self.lists) do
local categories, todos = {["none"] = 0}, {}
local category = nil
local categoryOrderIndex, orderIndex = 1, 1
for _,item in list.todos:Enumerate() do
if item.type == "category" then
categories[item.category] = categoryOrderIndex
categoryOrderIndex = categoryOrderIndex + 1
category = item.category
else
todos[#todos+1] = { id = item.todo, category = category, hidden = item.hidden, version = item.version, orderIndex = orderIndex }
orderIndex = orderIndex + 1
end
end
sort(todos, function (a, b)
if a == nil and b == nil then
return false
end
if a == nil then
return true
end
if b == nil then
return false
end
if categories[a.category or "none"] ~= categories[b.category or "none"] then
return categories[a.category or "none"] < categories[b.category or "none"]
end
if a.orderIndex ~= b.orderIndex then
return a.orderIndex < b.orderIndex
end
return a.id < b.id
end)
if Internal.UpdateList({
id = id,
name = list.name,
version = list.version,
todos = todos,
}) then
External.TriggerEvent("LIST_CHANGED", id)
end
end
end, geterrorhandler())
end
function BtWTodoConfigListsPanelMixin:cancel()
xpcall(function()
self.AddItem:Hide()
-- If we live update lists we should reset them here
end, geterrorhandler())
end
-- function BtWTodoConfigListsPanelMixin:default()
-- xpcall(function()
-- end, geterrorhandler())
-- end
function BtWTodoConfigListsPanelMixin:refresh()
xpcall(function()
do
local categories = {}
for id,category in Internal.IterateCategories() do
categories[id] = category
end
self.categories = categories
end
wipe(self.lists)
if self:IsShown() then
self:SetList(BtWTodoWindows.main.list)
end
end, geterrorhandler())
end
-- [[ Windows Panel ]]
BtWTodoConfigCharacterItemMixin = {}
function BtWTodoConfigCharacterItemMixin:Init(data)
self.data = data
if data.character == "PLAYER" then
self:SetText(L["Current Player"])
else
local character = Internal.GetCharacter(data.character)
self:SetText(character:GetDisplayName(true))
end
self.Text:SetTextColor(1, 1, 1, 1)
end
function BtWTodoConfigCharacterItemMixin:Delete()
self:GetParent():GetParent():Remove(self.data.orderIndex)
end
BtWTodoConfigWindowsPanelMixin = {}
function BtWTodoConfigWindowsPanelMixin:OnLoad()
self.frames = {}
UIDropDownMenu_SetWidth(self.FrameDropDown, 175);
UIDropDownMenu_JustifyText(self.FrameDropDown, "LEFT");
UIDropDownMenu_Initialize(self.FrameDropDown, function (_, level, menuList)
local selected = self:GetFrame()
local info = UIDropDownMenu_CreateInfo();
info.func = function (_, arg1, arg2, checked)
self:SwitchFrame(arg1)
end
local tbl = {}
for id in pairs(self.frames) do
tbl[#tbl+1] = id
end
table.sort(tbl)
for _,id in ipairs(tbl) do
info.text = frameNames[id]
info.arg1 = id
info.checked = selected == self.frames[id];
UIDropDownMenu_AddButton(info, level);
end
end);
UIDropDownMenu_SetWidth(self.ListDropDown, 150);
UIDropDownMenu_JustifyText(self.ListDropDown, "LEFT");
UIDropDownMenu_Initialize(self.ListDropDown, function (_, level, menuList)
if not self.frame then
return
end
local selected = self.frame.list
local info = UIDropDownMenu_CreateInfo();
info.func = function (_, arg1, arg2, checked)
self.frame.list = arg1
UIDropDownMenu_SetText(self.ListDropDown, GetList(arg1).name)
end
local tbl = {}
for _,item in IterateLists() do
tbl[#tbl+1] = item
end
table.sort(tbl, function (a, b)
if a.name == b.name then
return tostring(a.id) < tostring(b.id)
end
return a.name < b.name
end)
for _,item in ipairs(tbl) do
info.text = item.name or item.id
info.arg1 = item.id
info.checked = selected == item.id
UIDropDownMenu_AddButton(info, level);
end
end);
do -- Characters
local view = CreateScrollBoxListLinearView();
view:SetElementExtent(30);
if Internal.IsDragonflight() then
view:SetElementInitializer("BtWTodoConfigCharacterItemTemplate", function(button, elementData)
button:Init(elementData);
local startIndex, endIndex = self.CharacterScrollBox:GetDragRange()
button.Drag:SetShown(startIndex and elementData.orderIndex >= startIndex and elementData.orderIndex <= endIndex)
end);
else
view:SetElementInitializer("Button", "BtWTodoConfigCharacterItemTemplate", function(button, elementData)
button:Init(elementData);
local startIndex, endIndex = self.CharacterScrollBox:GetDragRange()
button.Drag:SetShown(startIndex and elementData.orderIndex >= startIndex and elementData.orderIndex <= endIndex)
end);
end
ScrollUtil.InitScrollBoxListWithScrollBar(self.CharacterScrollBox, self.CharacterScrollBar, view);
end
self.AddItem:SetOnCancelCallback(function ()
self.AddItem:Clear()
self.AddItem:Hide()
end)
InterfaceOptions_AddCategory(self)
end
function BtWTodoConfigWindowsPanelMixin:GetFrame()
return self.frame
end
function BtWTodoConfigWindowsPanelMixin:SwitchFrame(id)
local frame = self.frames[id]
if not frame then
error("Unknown frame " .. tostring(id))
end
self.frame = frame
UIDropDownMenu_SetText(self.FrameDropDown, frameNames[id])
UIDropDownMenu_SetText(self.ListDropDown, GetList(frame.list).name)
self.CharacterScrollBox:SetDataProvider(frame.characters);
self.ItemWidthEditBox:SetNumber(frame.itemWidth)
self.ItemHeightEditBox:SetNumber(frame.itemHeight)
self.AutoAddPlayerCheckbox:SetChecked(frame.addPlayer)
self.ItemWidthEditBox:SetCursorPosition(0)
self.ItemHeightEditBox:SetCursorPosition(0)
end
local currentPlayerMatch = {
["current player"] = true,
["player"] = true,
[L["Current Player"]:lower()] = true,
[L["Player"]:lower()] = true,
}
function BtWTodoConfigWindowsPanelMixin:OnAddCharacterClicked()
self.AddItem:SetTitle(BTWTODO_ADD_CHARACTER, BTWTODO_ADD_CHARACTER_SUBTEXT)
self.AddItem:SetAutoCompleteCallback(function (_, tbl, text, offset, length)
local text = strsub(text, offset, length):lower()
for _,character in Internal.IterateCharacters() do
local name = character:GetDisplayName(true, true):lower()
if #name >= #text and strsub(name, offset, length) == text then
tbl[#tbl+1] = character:GetDisplayName(true, false)
end
end
end)
self.AddItem:SetOnOkayCallback(function (_, text)
local key = nil
if currentPlayerMatch[text:lower()] then
key = "PLAYER"
else
local character = Internal.FindCharacter(text)
if character == nil then
UIErrorsFrame:AddMessage(format(L["Unknown character %s"], text), 1.0, 0.1, 0.1, 1.0);
return
end
key = character.key
end
local frame = self:GetFrame()
local dataProvider = frame.characters
local orderIndex = dataProvider:GetSize() + 1
local elementData = { type = "character", character = key, orderIndex = dataProvider:GetSize() + 1 }
dataProvider:Insert(elementData);
self.CharacterScrollBox:ScrollToElementDataIndex(orderIndex, ScrollBoxConstants.AlignNearest)
self.AddItem:Clear()
self.AddItem:Hide()
end)
self.AddItem:Show()
end
function BtWTodoConfigWindowsPanelMixin:ToggleAutoAddPlayer()
local frame = self:GetFrame()
if frame then
frame.addPlayer = not frame.addPlayer
end
end
function BtWTodoConfigWindowsPanelMixin:SetItemWidth(value)
local frame = self:GetFrame()
if frame then
frame.itemWidth = value
end
end
function BtWTodoConfigWindowsPanelMixin:SetItemHeight(value)
local frame = self:GetFrame()
if frame then
frame.itemHeight = value
end
end
function BtWTodoConfigWindowsPanelMixin:okay()
xpcall(function()
for id,frame in pairs(self.frames) do
BtWTodoWindows[id].list = frame.list
BtWTodoWindows[id].addPlayer = frame.addPlayer
BtWTodoWindows[id].itemWidth = frame.itemWidth
BtWTodoWindows[id].itemHeight = frame.itemHeight
local characters = {}
for _,item in frame.characters:Enumerate() do
characters[#characters+1] = item.character
end
BtWTodoWindows[id].characters = characters
External.TriggerEvent("FRAME_CHANGED", id)
end
end, geterrorhandler())
end
function BtWTodoConfigWindowsPanelMixin:cancel()
xpcall(function()
self.AddItem:Hide()
end, geterrorhandler())
end
-- function BtWTodoConfigWindowsPanelMixin:default()
-- xpcall(function()
-- end, geterrorhandler())
-- end
function BtWTodoConfigWindowsPanelMixin:refresh()
xpcall(function()
for id,settings in pairs(BtWTodoWindows) do
local result = {}
result.list = settings.list
result.addPlayer = settings.addPlayer
result.itemWidth = settings.itemWidth or 120
result.itemHeight = settings.itemHeight or 24
do -- Characters
local listData = {};
for _,item in ipairs(settings.characters) do
listData[#listData+1] = { type = "character", character = item, orderIndex = #listData+1 }
end
local dataProvider = CreateDataProvider(listData);
dataProvider:SetSortComparator(function (a, b)
return a.orderIndex < b.orderIndex
end, true)
result.characters = dataProvider
end
self.frames[id] = result
end
self:SwitchFrame("main")
end, geterrorhandler())
end
function External.OpenConfiguration()
if InterfaceOptionsFrame and not InterfaceOptionsFrame:IsShown() then
InterfaceOptionsFrame_Show()
end
InterfaceOptionsFrame_OpenToCategory(ADDON_NAME)
end