-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLibTextDump-1.0.lua
executable file
·402 lines (309 loc) · 11.8 KB
/
LibTextDump-1.0.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
-----------------------------------------------------------------------
-- Upvalued Lua API.
-----------------------------------------------------------------------
-- Functions
local date = _G.date
local error = _G.error
local type = _G.type
-- Libraries
local table = _G.table
-----------------------------------------------------------------------
-- Library namespace.
-----------------------------------------------------------------------
local LibStub = _G.LibStub
local MAJOR = "LibTextDump-1.0"
_G.assert(LibStub, MAJOR .. " requires LibStub")
local MINOR = 4 -- Should be manually increased
local lib, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
if not lib then
return
end -- No upgrade needed
-----------------------------------------------------------------------
-- Migrations.
-----------------------------------------------------------------------
lib.prototype = lib.prototype or {}
lib.metatable = lib.metatable or { __index = lib.prototype }
lib.buffers = lib.buffers or {}
lib.frames = lib.frames or {}
lib.num_frames = lib.num_frames or 0
-----------------------------------------------------------------------
-- Constants and upvalues.
-----------------------------------------------------------------------
local prototype = lib.prototype
local metatable = lib.metatable
local buffers = lib.buffers
local frames = lib.frames
local METHOD_USAGE_FORMAT = MAJOR .. ":%s() - %s."
local DEFAULT_FRAME_WIDTH = 750
local DEFAULT_FRAME_HEIGHT = 600
-----------------------------------------------------------------------
-- Helper functions.
-----------------------------------------------------------------------
local function round(number, places)
local mult = 10 ^ (places or 0)
return _G.floor(number * mult + 0.5) / mult
end
local function NewInstance(width, height, useFauxScroll)
lib.num_frames = lib.num_frames + 1
local frameName = ("%s_CopyFrame%d"):format(MAJOR, lib.num_frames)
local copyFrame = _G.CreateFrame("Frame", frameName, _G.UIParent, "UIPanelDialogTemplate")
copyFrame:SetSize(width, height)
copyFrame:SetPoint("CENTER", _G.UIParent, "CENTER")
copyFrame:SetFrameStrata("DIALOG")
copyFrame:EnableMouse(true)
copyFrame:SetMovable(true)
copyFrame:SetToplevel(true)
copyFrame:Hide()
copyFrame.title = copyFrame.Title
local titleBackground = _G[frameName.."TitleBG"]
local dragFrame = _G.CreateFrame("Frame", nil, copyFrame)
dragFrame:SetPoint("TOPLEFT", titleBackground, 16, 0)
dragFrame:SetPoint("BOTTOMRIGHT", titleBackground)
dragFrame:EnableMouse(true)
dragFrame:SetScript("OnMouseDown", function(self, button)
copyFrame:StartMoving()
end)
dragFrame:SetScript("OnMouseUp", function(self, button)
copyFrame:StopMovingOrSizing()
end)
local scrollArea
if useFauxScroll then
scrollArea = _G.CreateFrame("ScrollFrame", ("%sScroll"):format(frameName), copyFrame, "FauxScrollFrameTemplate")
function scrollArea:Update(start, wrappedLines, maxDisplayLines, lineHeight)
local lineIndex = start - 1
local linesToDisplay = 0
repeat
lineIndex = lineIndex + 1
linesToDisplay = linesToDisplay + (wrappedLines[lineIndex] or 0)
until linesToDisplay > maxDisplayLines or not wrappedLines[lineIndex]
local stop = lineIndex - 1
self:Show()
local name = self:GetName()
local scrollBar = _G[name .. "ScrollBar"]
scrollBar:SetStepsPerPage(linesToDisplay - 1)
-- This block should only be run when the buffer is changed.
-- Possible variations in linesToDisplay from scroll to scroll will affect the height of the scroll frame.
-- This will then result in inconsistent scrolling behaviour.
if lineHeight then
local scrollChildFrame = _G[name .. "ScrollChildFrame"]
local scrollFrameHeight = (wrappedLines.all - linesToDisplay) * lineHeight
local scrollChildHeight = wrappedLines.all * lineHeight
if scrollFrameHeight < 0 then
scrollFrameHeight = 0
end
self.height = scrollFrameHeight
scrollChildFrame:Show()
scrollChildFrame:SetHeight(scrollChildHeight)
scrollBar:SetMinMaxValues(0, scrollFrameHeight)
scrollBar:SetValueStep(lineHeight)
end
-- Arrow button handling
local scrollUpButton = _G[name .. "ScrollBarScrollUpButton"]
local scrollDownButton = _G[name .. "ScrollBarScrollDownButton"]
if scrollBar:GetValue() == 0 then
scrollUpButton:Disable()
else
scrollUpButton:Enable()
end
if scrollBar:GetValue() - self.height == 0 then
scrollDownButton:Disable()
else
scrollDownButton:Enable()
end
return start, stop
end
-- lineDummy is used to get the height of a line *after* word wrap to calculate the proper scroll position of the FauxScrollFrame.
local lineDummy = copyFrame:CreateFontString()
lineDummy:SetJustifyH("LEFT")
lineDummy:SetNonSpaceWrap(true)
lineDummy:SetFontObject("ChatFontNormal")
lineDummy:SetPoint("TOPLEFT", 5, 100)
lineDummy:SetPoint("BOTTOMRIGHT", copyFrame, "TOPRIGHT", -28, 0)
lineDummy:Hide()
copyFrame.lineDummy = lineDummy
else
scrollArea = _G.CreateFrame("ScrollFrame", ("%sScroll"):format(frameName), copyFrame, "UIPanelScrollFrameTemplate")
scrollArea:SetScript("OnMouseWheel", function(self, delta)
_G.ScrollFrameTemplate_OnMouseWheel(self, delta, self.ScrollBar)
end)
scrollArea.ScrollBar:SetScript("OnMouseWheel", function(self, delta)
_G.ScrollFrameTemplate_OnMouseWheel(self, delta, self)
end)
end
local dialogBackground = _G[frameName.."DialogBG"]
scrollArea:SetPoint("TOPLEFT", dialogBackground, 5, -7)
scrollArea:SetPoint("BOTTOMRIGHT", dialogBackground, -25, 5)
copyFrame.scrollArea = scrollArea
local editBox = _G.CreateFrame("EditBox", nil, copyFrame)
editBox:SetMultiLine(true)
editBox:SetMaxLetters(0)
editBox:EnableMouse(true)
editBox:SetAutoFocus(false)
editBox:SetFontObject("ChatFontNormal")
editBox:SetScript("OnEscapePressed", function()
copyFrame:Hide()
end)
copyFrame.edit_box = editBox
-- While using a standard scroll frame, editBox will be positioned automatcialy when set as its scrollChild.
-- With the faux scroll, we have to position it ourself.
if useFauxScroll then
editBox:SetAllPoints(scrollArea)
else
editBox:SetSize(scrollArea:GetSize())
scrollArea:SetScrollChild(editBox)
end
local highlightButton = _G.CreateFrame("Button", nil, copyFrame)
highlightButton:SetSize(16, 16)
highlightButton:SetPoint("TOPLEFT", titleBackground)
highlightButton:SetScript("OnMouseUp", function(self, button)
self.texture:ClearAllPoints()
self.texture:SetAllPoints(self)
editBox:HighlightText(0)
editBox:SetFocus()
copyFrame:RegisterEvent("PLAYER_LOGOUT")
end)
highlightButton:SetScript("OnMouseDown", function(self, button)
self.texture:ClearAllPoints()
self.texture:SetPoint("RIGHT", self, "RIGHT", 1, -1)
end)
highlightButton:SetScript("OnEnter", function(self)
self.texture:SetVertexColor(0.75, 0.75, 0.75)
end)
highlightButton:SetScript("OnLeave", function(self)
self.texture:SetVertexColor(1, 1, 1)
end)
local highlightIcon = highlightButton:CreateTexture()
highlightIcon:SetAllPoints()
highlightIcon:SetTexture([[Interface\BUTTONS\UI-GuildButton-PublicNote-Up]])
highlightButton.texture = highlightIcon
local instance = _G.setmetatable({}, metatable)
frames[instance] = copyFrame
buffers[instance] = {
wrappedLines = {}
}
return instance
end
-----------------------------------------------------------------------
-- Library methods.
-----------------------------------------------------------------------
--- Create a new dump frame.
-- @param frameTitle The title text of the frame.
-- @param width (optional) The width of the frame.
-- @param height (optional) The height of the frame.
-- @param save (optional) A function that will be called when the copy button is clicked.
-- @return A handle for the dump frame.
function lib:New(frameTitle, width, height, save)
local titleType = type(frameTitle)
if titleType ~= "nil" and titleType ~= "string" then
error(METHOD_USAGE_FORMAT:format("New", "frame title must be nil or a string."), 2)
end
local widthType = type(width)
if widthType ~= "nil" and widthType ~= "number" then
error(METHOD_USAGE_FORMAT:format("New", "frame width must be nil or a number."))
end
local heightType = type(height)
if heightType ~= "nil" and heightType ~= "number" then
error(METHOD_USAGE_FORMAT:format("New", "frame height must be nil or a number."))
end
local saveType = type(save)
if saveType ~= "nil" and saveType ~= "function" then
error(METHOD_USAGE_FORMAT:format("New", "save must be nil or a function."))
end
local instance = NewInstance(width or DEFAULT_FRAME_WIDTH, height or DEFAULT_FRAME_HEIGHT, not not save)
local frame = frames[instance]
frame.title:SetText(frameTitle)
if save then
frame:SetScript("OnEvent", function(event, ...)
buffers[instance].wrappedLines = nil
save(buffers[instance])
end)
end
return instance
end
-----------------------------------------------------------------------
-- Instance methods.
-----------------------------------------------------------------------
function prototype:AddLine(text, dateFormat)
self:InsertLine(#buffers[self] + 1, text, dateFormat)
if lib.frames[self]:IsVisible() then
self:Display()
end
end
function prototype:Clear()
table.wipe(buffers[self])
buffers[self].wrappedLines = {}
end
function prototype:Display(separator)
local frame = frames[self]
if frame.UpdateText then
frame:UpdateText("Display")
else
local displayText = self:String(separator)
if displayText == "" then
error(METHOD_USAGE_FORMAT:format("Display", "buffer must be non-empty"), 2)
end
frame.edit_box:SetText(displayText)
frame.edit_box:SetCursorPosition(0)
end
frame:Show()
end
function prototype:InsertLine(position, text, dateFormat)
if type(position) ~= "number" then
error(METHOD_USAGE_FORMAT:format("InsertLine", "position must be a number."))
end
if type(text) ~= "string" or text == "" then
error(METHOD_USAGE_FORMAT:format("InsertLine", "text must be a non-empty string."), 2)
end
local buffer = buffers[self]
if dateFormat and dateFormat ~= "" then
table.insert(buffer, position, ("[%s] %s"):format(date(dateFormat), text))
else
table.insert(buffer, position, text)
end
local lineDummy = frames[self].lineDummy
if lineDummy then
lineDummy:SetText(buffer[position])
table.insert(buffer.wrappedLines, position, lineDummy:GetNumLines())
buffer.wrappedLines.all = (buffer.wrappedLines.all or 0) + buffer.wrappedLines[position]
end
end
function prototype:Lines()
return #buffers[self]
end
function prototype:String(separator)
local separatorType = type(separator)
if separatorType ~= "nil" and separatorType ~= "string" then
error(METHOD_USAGE_FORMAT:format("String", "separator must be nil or a string."), 2)
end
separator = separator or "\n"
local buffer = buffers[self]
local frame = frames[self]
local lineDummy = frame.lineDummy
if lineDummy then
local _, lineHeight = lineDummy:GetFont()
local maxDisplayLines = round(frame.edit_box:GetHeight() / lineHeight)
local allWrappedLines = buffer.wrappedLines.all
local offset = 1
local start, stop = frame.scrollArea:Update(offset, buffer.wrappedLines, maxDisplayLines, lineHeight)
function frame:UpdateText()
local newWrappedLines = buffer.wrappedLines.all
if newWrappedLines > allWrappedLines then
allWrappedLines = newWrappedLines
start, stop = frame.scrollArea:Update(offset, buffer.wrappedLines, maxDisplayLines, lineHeight)
else
start, stop = frame.scrollArea:Update(offset, buffer.wrappedLines, maxDisplayLines)
end
frame.edit_box:SetText(table.concat(buffer, separator, start, stop))
end
frame.scrollArea:SetScript("OnVerticalScroll", function(scrollArea, value)
local scrollbar = scrollArea.ScrollBar
local _, scrollMax = scrollbar:GetMinMaxValues()
local scrollPer = round(value / scrollMax, 2)
offset = round((1 - scrollPer) * 1 + scrollPer * #buffer)
frame:UpdateText()
end)
return table.concat(buffer, separator, start, stop)
else
return table.concat(buffer, separator)
end
end