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.

661 lines
20 KiB

local addonName, vars = ...
local L = vars.L
Paste = LibStub("AceAddon-3.0"):NewAddon(addonName)
local addon = Paste
local AceGUI = LibStub("AceGUI-3.0")
vars.svnrev = vars.svnrev or {}
local svnrev = vars.svnrev
svnrev["core.lua"] = tonumber(("$Revision: 54 $"):match("%d+"))
local defaults = {
profile = {
debug = false, -- for addon debugging
minimap = {
hide = false,
},
stripempty = true,
trimwhitespace = false,
windowscale = 1.0,
editscale = 1.0,
shiftenter = false,
}
}
local settings = defaults.profile
local optionsFrame
local charName
local hiddenFrame = CreateFrame("Button", addonName.."HiddenFrame", UIParent)
local revision = tonumber(("$Revision: 54 $"):match("%d+"))
local minimapIcon = LibStub("LibDBIcon-1.0")
local LDB, LDBo
local linelimit = 254
local function chatMsg(msg)
DEFAULT_CHAT_FRAME:AddMessage(addonName..": "..msg)
end
local function debug(msg)
if addon.db.profile.debug then
chatMsg(msg)
end
end
function addon:myOptions()
return {
type = "group",
set = function(info,val)
local s = settings ; for i = 2,#info-1 do s = s[info[i]] end
s[info[#info]] = val; debug(info[#info].." set to: "..tostring(val))
addon:Update()
end,
get = function(info)
local s = settings ; for i = 2,#info-1 do s = s[info[i]] end
return s[info[#info]] end,
args = {
general = {
type = "group",
inline = true,
name = L["General"],
args = {
debug = {
name = L["Debug"],
desc = L["Toggle debugging output"],
type = "toggle",
guiHidden = true,
},
config = {
name = L["Config"],
desc = L["Open the configuration GUI"],
type = "execute",
guiHidden = true,
func = function() addon:Config() end,
},
show = {
name = L["Show"],
desc = L["Show/Hide the Paste window"],
type = "execute",
guiHidden = true,
func = function() addon:ToggleWindow() end,
},
minimap = {
order = 15,
name = L["Minimap Icon"],
desc = L["Display minimap icon"],
type = "toggle",
set = function(info,val)
settings.minimap.hide = not val
addon:Update()
end,
get = function() return not settings.minimap.hide end,
},
stripempty = {
order = 17,
name = L["Strip Empty Lines"],
desc = L["Strip empty lines (those containing only whitespace) from the output. Note some channels automatically drop fully empty lines."],
type = "toggle",
},
trimwhitespace = {
order = 18,
name = L["Trim Whitespace"],
desc = L["Trim whitespace from the beginning and end of lines in the output."],
type = "toggle",
},
aheader = {
name = APPEARANCE_LABEL,
type = "header",
cmdHidden = true,
order = 300,
},
windowscale = {
order = 310,
type = 'range',
name = L["Window Scale"],
desc = L["Scale the Paste window and all its contents"],
min = 0.1,
max = 5,
step = 0.1,
bigStep = 0.1,
isPercent = true,
},
editscale = {
order = 320,
type = 'range',
name = L["Edit font scale"],
desc = L["Scale the text font used in the Paste edit box"],
min = 0.1,
max = 5,
step = 0.1,
bigStep = 0.1,
isPercent = true,
},
bheader = {
name = KEY_BINDINGS,
type = "header",
cmdHidden = true,
order = 900,
},
shiftenter = {
order = 905,
name = L["Shift-Enter to Paste"],
desc = L["Shift-Enter hotkey while typing in the edit box will Paste and Close"],
type = "toggle",
},
togglebind = {
desc = L["Bind a key to toggle the Paste window"],
type = "keybinding",
name = L["Show/Hide the Paste window"],
cmdHidden = true,
order = 910,
width = "double",
set = function(info,val)
local b1, b2 = GetBindingKey("PASTE")
if b1 then SetBinding(b1) end
if b2 then SetBinding(b2) end
SetBinding(val, "PASTE")
SaveBindings(GetCurrentBindingSet())
end,
get = function(info) return GetBindingKey("PASTE") end,
},
},
},
}
}
end
BINDING_NAME_PASTE = L["Show/Hide the Paste window"]
BINDING_HEADER_PASTE = addonName
local function table_clone(t)
if not t then return nil
elseif type(t) == "table" then
local res = {}
for k,v in pairs(t) do
res[table_clone(k)] = table_clone(v)
end
return res
else
return t
end
end
function addon:RefreshConfig()
-- things to do after load or settings are reset
debug("RefreshConfig")
settings = addon.db.profile
addon.settings = settings
charName = UnitName("player")
for k,v in pairs(defaults.profile) do
if settings[k] == nil then
settings[k] = table_clone(v)
end
end
settings.loaded = true
addon:Update()
end
function addon:Update()
-- things to do when settings change
if LDBo then
minimapIcon:Refresh(addonName)
end
addon:UpdateCount()
if addon.gui then -- scale the window
local frame = addon.gui.frame
local old = frame:GetScale()
local new = settings.windowscale
if old ~= new then
local top, left = frame:GetTop(), frame:GetLeft()
frame:ClearAllPoints()
frame:SetScale(new)
left = left * old / new
top = top * old / new
frame:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", left, top)
end
local file, oldpt, flags = addon.editfont:GetFont()
local newpt = addon.editfontnorm * settings.editscale
if math.abs(oldpt - newpt) > 0.25 then
addon.editfont:SetFont(file, newpt, flags)
end
end
end
function addon:SetupVersion()
local svnrev = 0
local files = vars.svnrev
files["X-Build"] = tonumber((C_AddOns.GetAddOnMetadata(addonName, "X-Build") or ""):match("%d+"))
files["X-Revision"] = tonumber((C_AddOns.GetAddOnMetadata(addonName, "X-Revision") or ""):match("%d+"))
for _,v in pairs(files) do -- determine highest file revision
if v and v > svnrev then
svnrev = v
end
end
addon.revision = svnrev
files["X-Curse-Packaged-Version"] = C_AddOns.GetAddOnMetadata(addonName, "X-Curse-Packaged-Version")
files["Version"] = C_AddOns.GetAddOnMetadata(addonName, "Version")
addon.version = files["X-Curse-Packaged-Version"] or files["Version"] or "@"
if string.find(addon.version, "@") then -- dev copy uses "@.project-version.@"
addon.version = "r"..svnrev
end
end
function addon:OnInitialize()
addon.db = LibStub("AceDB-3.0"):New("PasteDB", defaults)
addon:SetupVersion()
addon:RefreshConfig()
local options = addon:myOptions()
LibStub("AceConfigRegistry-3.0"):ValidateOptionsTable(options, addonName)
LibStub("AceConfig-3.0"):RegisterOptionsTable(addonName, options, {"paste"})
optionsFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions(addonName, addonName, nil, "general")
optionsFrame.default = function()
for k,v in pairs(defaults.profile) do settings[k] = table_clone(v) end
addon:RefreshConfig()
if InterfaceOptionsFrame:IsShown() then
addon:Config(); addon:Config()
end
end
options.args.profiles = LibStub("AceDBOptions-3.0"):GetOptionsTable(addon.db)
LibStub("AceConfigDialog-3.0"):AddToBlizOptions(addonName, L["Profiles"], addonName, "profiles")
debug("OnInitialize")
self.db.RegisterCallback(self, "OnProfileChanged", "RefreshConfig")
self.db.RegisterCallback(self, "OnProfileCopied", "RefreshConfig")
self.db.RegisterCallback(self, "OnProfileReset", "RefreshConfig")
self.db.RegisterCallback(self, "OnDatabaseReset", "RefreshConfig")
end
function addon:Config()
if optionsFrame then
if ( InterfaceOptionsFrame:IsShown() ) then
InterfaceOptionsFrame:Hide();
else
InterfaceOptionsFrame_OpenToCategory(optionsFrame)
end
end
end
function addon:OnEnable()
debug("OnEnable")
--self:RegisterEvent("READY_CHECK")
if LDB then
return
end
if AceLibrary and AceLibrary:HasInstance("LibDataBroker-1.1") then
LDB = AceLibrary("LibDataBroker-1.1")
elseif LibStub then
LDB = LibStub:GetLibrary("LibDataBroker-1.1",true)
end
if LDB then
LDBo = LDB:NewDataObject(addonName, {
type = "launcher",
label = addonName,
icon = "Interface\\Icons\\inv_scroll_08",
OnClick = function(self, button)
if button == "RightButton" then
addon:Config()
else
addon:ToggleWindow()
end
end,
OnTooltipShow = function(tooltip)
if tooltip and tooltip.AddLine then
tooltip:SetText(addonName)
tooltip:AddLine("|cffff8040"..L["Left Click"].."|r "..L["to toggle window"])
tooltip:AddLine("|cffff8040"..L["Right Click"].."|r "..L["for options"])
tooltip:Show()
end
end,
})
end
if LDBo then
minimapIcon:Register(addonName, LDBo, settings.minimap)
end
addon:Update()
end
function addon:ToggleWindow(keystate)
if keystate == "down" then return end -- ensure keybind doesnt end up in the text box
debug("ToggleWindow")
if not addon.gui then
addon:CreateWindow()
end
if addon.gui:IsShown() then
addon.gui:Hide()
else
addon.gui:Show()
addon.edit:SetFocus()
addon:Update()
end
end
function addon:CreateWindow()
if addon.gui then
return
end
local f = AceGUI:Create("Frame")
f.frame:SetFrameStrata("MEDIUM")
f.frame:Raise()
f.content:SetFrameStrata("MEDIUM")
f.content:Raise()
f:Hide()
addon.gui = f
f:SetTitle(addonName.." "..addon.version)
--addon:fixTitle()
f:SetCallback("OnClose", OnClose)
f:SetLayout("Fill")
f.frame:SetClampedToScreen(true)
settings.pos = settings.pos or {}
f:SetStatusTable(settings.pos)
addon.minwidth = 500
addon.minheight = 320
f:SetWidth(addon.minwidth)
f:SetHeight(addon.minheight)
f:SetAutoAdjustHeight(true)
addon:setEscapeHandler(f, function() addon:ToggleWindow() end)
local c = AceGUI:Create("SimpleGroup")
c:SetLayout("List")
c:SetFullWidth(true)
c:SetFullHeight(true)
f:AddChild(c)
local edit = AceGUI:Create("MultiLineEditBox")
c:AddChild(edit)
edit:SetMaxLetters(0)
local shortcut = L["Control-V"]
if IsMacClient() then
shortcut = L["Command-V"]
end
edit:SetLabel(string.format(L["Use %s to paste the clipboard into this box"], shortcut))
edit:SetNumLines(10)
edit:SetHeight(170)
edit:DisableButton(true)
edit:SetFullWidth(true)
edit:SetCallback("OnTextChanged", function(widget, t) addon:UpdateCount() end)
edit:SetText("")
addon.edit = edit
addon.editfont = CreateFont("PasteEditFont")
addon.editfont:CopyFontObject(ChatFontNormal)
edit.editBox:SetFontObject(addon.editfont)
addon.editfontnorm = select(2, addon.editfont:GetFont())
-- AceGUI fails at enforcing minimum Frame resize for a container, so fix it
hooksecurefunc(f,"OnHeightSet", function(widget, height)
if (widget ~= addon.gui) then return end
if (height < addon.minheight) then
f:SetHeight(addon.minheight)
else
edit:SetHeight(height - addon.minheight + 170)
end
end)
hooksecurefunc(f,"OnWidthSet", function(widget, width)
if (widget ~= addon.gui) then return end
if (width < addon.minwidth) then
f:SetWidth(addon.minwidth)
end
end)
local oldhandler = edit.editBox:GetScript("OnEnterPressed")
edit.editBox:SetScript("OnEnterPressed", function(self)
if settings.shiftenter and IsShiftKeyDown() then
f:Hide()
addon:PasteText(edit:GetText())
elseif oldhandler then
oldhandler(self)
else
edit.editBox:Insert("\n")
end
end)
local w = AceGUI:Create("SimpleGroup")
w:SetLayout("Flow")
w:SetFullWidth(true)
c:AddChild(w)
local target = AceGUI:Create("EditBox")
settings.whispertarget = settings.whispertarget or ""
target:SetText(settings.whispertarget)
target:SetMaxLetters(30)
target:SetWidth(200)
target:SetCallback("OnTextChanged",function(widget, text)
settings.whispertarget = target:GetText()
end)
target:SetCallback("OnEnterPressed",function(widget)
target:ClearFocus()
end)
local where = AceGUI:Create("Dropdown")
addon.wherewidget = where
where:SetMultiselect(false)
where:SetLabel(L["Paste to:"])
where:SetWidth(200)
where:SetCallback("OnEnter",addon.UpdateWhere)
where:SetCallback("OnValueChanged",function(widget, event, key)
settings.where = key
if key == CHAT_MSG_WHISPER_INFORM or key == BN_WHISPER then
target:SetDisabled(false)
target:SetFocus()
else
target:SetDisabled(true)
end
end)
settings.where = settings.where or CHAT_DEFAULT
addon.UpdateWhere()
where:SetValue(settings.where)
target:SetDisabled(settings.where ~= CHAT_MSG_WHISPER_INFORM and settings.where ~= BN_WHISPER)
w:AddChild(where)
w:AddChild(target)
local b = AceGUI:Create("SimpleGroup")
b:SetLayout("Flow")
b:SetFullWidth(true)
c:AddChild(b)
local bwidth = 150
local pcbutton = AceGUI:Create("Button")
pcbutton:SetText(L["Paste and Close"])
pcbutton:SetWidth(bwidth)
pcbutton:SetCallback("OnClick", function(widget, button)
f:Hide()
addon:PasteText(edit:GetText())
end)
b:AddChild(pcbutton)
local pbutton = AceGUI:Create("Button")
pbutton:SetText(L["Paste"])
pbutton:SetWidth(bwidth)
pbutton:SetCallback("OnClick", function(widget, button)
addon:PasteText(edit:GetText())
end)
b:AddChild(pbutton)
local clear = AceGUI:Create("Button")
clear:SetText(L["Clear"])
clear:SetWidth(bwidth)
clear:SetCallback("OnClick", function(widget, button)
edit:SetText("")
addon:UpdateCount()
edit:SetFocus()
end)
b:AddChild(clear)
end
addon.wherefn = {
[CHAT_MSG_SAY] = function(str) SendChatMessage(str, "SAY") end,
[CHAT_MSG_YELL] = function(str) SendChatMessage(str, "YELL") end,
[CHAT_MSG_PARTY] = function(str) SendChatMessage(str, "PARTY") end,
[CHAT_MSG_RAID] = function(str) SendChatMessage(str, "RAID") end,
[INSTANCE_CHAT] = function(str) SendChatMessage(str, "INSTANCE_CHAT") end,
[CHAT_MSG_GUILD] = function(str) SendChatMessage(str, "GUILD") end,
[CHAT_MSG_OFFICER] = function(str) SendChatMessage(str, "OFFICER") end,
[CHAT_MSG_WHISPER_INFORM] = function(str)
local t = settings.whispertarget
if not t then
chatMsg(L["You must select a whisper target!"])
return
end
SendChatMessage(str, "WHISPER", nil, t)
end,
[BN_WHISPER] = function(str)
local t = settings.whispertarget
if not t then
chatMsg(L["You must select a whisper target!"])
return
end
local pID = BNet_GetBNetIDAccount(t)
if pID then
BNSendWhisper(pID, str)
return
end
chatMsg(ERR_FRIEND_NOT_FOUND)
end,
[CHAT_DEFAULT] = function(str)
ChatFrame_OpenChat("")
local edit = ChatEdit_GetActiveWindow();
edit:SetText(str)
ChatEdit_SendText(edit,1)
ChatEdit_DeactivateChat(edit)
end,
}
function addon.UpdateWhere()
addon.wherelist = addon.wherelist or {}
wipe(addon.wherelist)
local w = addon.wherelist
w[CHAT_DEFAULT] = CHAT_DEFAULT
w[CHAT_MSG_SAY] = CHAT_MSG_SAY
w[CHAT_MSG_YELL] = CHAT_MSG_YELL
w[CHAT_MSG_WHISPER_INFORM] = CHAT_MSG_WHISPER_INFORM
if BNFeaturesEnabledAndConnected() then
w[BN_WHISPER] = BN_WHISPER
end
if GetNumGroupMembers() > 0 then
w[CHAT_MSG_PARTY] = CHAT_MSG_PARTY
end
if IsInRaid() then
w[CHAT_MSG_RAID] = CHAT_MSG_RAID
end
if IsInGroup(LE_PARTY_CATEGORY_INSTANCE) then
w[INSTANCE_CHAT] = INSTANCE_CHAT
end
if IsInGuild() then
w[CHAT_MSG_GUILD] = CHAT_MSG_GUILD
w[CHAT_MSG_OFFICER] = CHAT_MSG_OFFICER
end
local widget = addon.wherewidget
if widget and not widget.open then
widget:SetList(addon.wherelist)
if not addon.wherelist[settings.where] then
settings.where = CHAT_DEFAULT
widget:SetValue(settings.where)
end
end
end
function addon:UpdateCount()
if not addon.edit then return end
local text = addon.edit:GetText()
if not text then return end
text = addon:normalizeText(text)
local lines = 1
local chars = #text - lines + 1
for _ in text:gmatch("\n") do lines = lines + 1 end
addon.gui:SetStatusText(lines.." "..L["lines"]..", "..chars.." "..L["characters"])
end
function addon:normalizeText(text)
if not text then return nil end
text = text:gsub("\r\n","\n")
text = text:gsub("\r","\n")
if settings.stripempty then
text = text:gsub("\n%s*\n","\n")
text = text:gsub("^%s*\n","\n")
text = text:gsub("\n%s*$","\n")
end
if settings.trimwhitespace then
text = text:gsub("\n%s*","\n")
text = text:gsub("%s*\n","\n")
text = text:gsub("^%s*","")
text = text:gsub("%s*$","")
end
text = strtrim(text)
return text
end
function addon:PasteText(text)
addon.UpdateWhere()
local where = settings.where
local sendfn = addon.wherelist[where] and addon.wherefn[where]
if not sendfn then return end
text = addon:normalizeText(text)
if where ~= CHAT_DEFAULT and not addon.slashwarned and
(text:find("^/%w") or text:find("\n/%w")) then
StaticPopup_Show("PASTE_SLASHWARN")
addon.slashwarned = text
return
end
local lines = { strsplit("\n", text) }
for idx, line in ipairs(lines) do
while line and #line > 0 do
local curr = line
if #curr > linelimit then -- break long lines
local bpt = linelimit
for i = linelimit, linelimit-30, -1 do -- look for break characters near the end
if string.match(string.sub(curr,i), "^[%p%s]") then
bpt = i
break
end
end
line = curr:sub(bpt+1)
curr = curr:sub(1,bpt)
else
line = ""
end
sendfn(curr)
end
end
end
StaticPopupDialogs["PASTE_SLASHWARN"] = {
preferredIndex = 3, -- reduce the chance of UI taint
text = L["It looks like you're pasting some slash commands to a chat channel. Would you like to execute them instead?"],
button1 = YES,
button2 = NO,
button3 = CANCEL,
OnAccept = function()
settings.where = CHAT_DEFAULT
addon.wherewidget:SetValue(CHAT_DEFAULT)
addon:PasteText(addon.slashwarned)
end,
OnCancel = function() addon:PasteText(addon.slashwarned) end,
timeout = 0,
whileDead = true,
hideOnEscape = true,
enterClicksFirstButton = false,
showAlert = true,
}
----------------------------------------------------------------------------------
-- AceGUI hacks --
-- hack to hook the escape key for closing the window
function addon:setEscapeHandler(widget, fn)
widget.origOnKeyDown = widget.frame:GetScript("OnKeyDown")
widget.frame:SetScript("OnKeyDown", function(self,key)
widget.frame:SetPropagateKeyboardInput(true)
if key == "ESCAPE" then
widget.frame:SetPropagateKeyboardInput(false)
fn()
elseif widget.origOnKeyDown then
widget.origOnKeyDown(self,key)
end
end)
widget.frame:EnableKeyboard(true)
widget.frame:SetPropagateKeyboardInput(true)
end