Quantcast
Using a chat frame as a log for addon? - WoWInterface
Thread Tools Display Modes
07-30-16, 12:35 PM   #1
Hiketeia
An Aku'mai Servant
 
Hiketeia's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2016
Posts: 33
Using a chat frame as a log for addon?

I'm trying to re-use as much as I can within the UI and thinking about the ChatFrame as a way to log stuff about my addon.

My idea was to have a window available on the screen which keeps a record, that you can go back and look at throughout your play session.

I can't seem to find an easy way to make a new chat window with the settings I want? I've glanced through FloatingChatFrame and didn't see anything obvious, and nothing seems to be on the wikis. Is this just not a common thing to do?

One thought was I just give them an option to pick a chat frame for their log, going to default, and give instructions to make their own frame? Doesn't seem to user-friendly though.

Thanks for the brainstorming help.
  Reply With Quote
07-30-16, 01:18 PM   #2
Seerah
Fishing Trainer
 
Seerah's Avatar
WoWInterface Super Mod
Featured
Join Date: Oct 2006
Posts: 10,768
You want a ScrollingMessageFrame.
http://wowprogramming.com/docs/widge...ngMessageFrame
__________________
"You'd be surprised how many people violate this simple principle every day of their lives and try to fit square pegs into round holes, ignoring the clear reality that Things Are As They Are." -Benjamin Hoff, The Tao of Pooh

  Reply With Quote
07-30-16, 02:14 PM   #3
Kanegasi
A Molten Giant
 
Kanegasi's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2007
Posts: 507
If you want to simply use a player's existing chat tabs, here's some relevant info:

The first object returned by GetChatWindowInfo(index) is the name of the tab, so GetChatWindowInfo(1) will return "General" in the vast majority of clients. Index is the same number of ChatFrame, so General is ChatFrame1 and the Combat Log is ChatFrame2.

Here's what I use in my addon:

Code:
local notfound,c=true,ChatTypeInfo.SYSTEM
for i=1,NUM_CHAT_WINDOWS do if _G['ChatFrame'..i]:IsEventRegistered('CHAT_MSG_SYSTEM') then
    notfound=false
    _G['ChatFrame'..i]:AddMessage(message,c.r,c.g,c.b,c.id)
end end
if notfound then DEFAULT_CHAT_FRAME:AddMessage(message,c.r,c.g,c.b,c.id) end
This copies the data used by the yellow system messages and then prints to any window registered to handle system messages. If there are no windows that show system messages, it prints to whatever window is the default one.

If someone were to create a chatframe named "addonlog" and wanted the above code to print to that, you would do this:

Code:
local notfound,c=true,ChatTypeInfo.SYSTEM
for i=1,NUM_CHAT_WINDOWS do if GetChatWindowInfo(i)=='addonlog' then
    notfound=false
    _G['ChatFrame'..i]:AddMessage(message,c.r,c.g,c.b,c.id)
end end
if notfound then DEFAULT_CHAT_FRAME:AddMessage(message,c.r,c.g,c.b,c.id) end
If you wanted to save the log between sessions while still keeping it in the chatframe, you can save every message into a table, save that table in a SavedVariable, and then loop through it in order and dump it to the chatframe every time the UI is loaded.
  Reply With Quote
07-31-16, 02:19 PM   #4
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 1,979
I have this running in my development addon.

Lua Code:
  1. local tostring=tostring;
  2.  
  3. local ProxyLookup={};
  4. local ProxyMeta={__index={
  5.     Print=function(self,...)
  6.         local frame=ProxyLookup[self];
  7.         if frame then
  8.             local msg="";
  9.             for i=1,select("#",...) do msg=(msg~="" and msg.." " or "")..tostring(select(i,...)); end
  10.             frame:AddMessage(msg);
  11.             if not frame:IsShown() then FCF_StartAlertFlash(frame); end
  12.         end
  13.     end;
  14.  
  15.     Show=function(self)
  16.         local frame=ProxyLookup[self];
  17.         if frame then
  18.             local tab=_G[frame:GetName().."Tab"];
  19.             if not tab:IsShown() then
  20.                 frame:Show();
  21.                 tab:Show();
  22.                 FCF_DockFrame(frame,#FCFDock_GetChatFrames(GENERAL_CHAT_DOCK)+1,nil);
  23.             end
  24.         end
  25.     end;
  26.     Hide=function(self)
  27.         local frame=ProxyLookup[self];
  28.         if frame then
  29.             local tab=_G[frame:GetName().."Tab"];
  30.             if tab:IsShown() then-- Mimic FCF_Close(), but don't release frame
  31.                 FCF_UnDockFrame(frame);
  32.                 HideUIPanel(frame);
  33.                 tab:Hide();
  34.                 FCF_FlagMinimizedPositionReset(frame);
  35.                 if frame.minFrame and frame.minFrame:IsShown() then frame.minFrame:Hide(); end
  36.             end
  37.         end
  38.     end;
  39.  
  40.     SetShown=function(self,show)
  41.         local frame=ProxyLookup[self];
  42.         if frame then self[show and "Show" or "Hide"](self); end
  43.     end;
  44.     IsShown=function()
  45.         local frame=ProxyLookup[self];
  46.         if frame then return _G[frame:GetName().."Tab"]:IsShown(); end
  47.     end;
  48.  
  49.     Close=function(self)
  50.         local frame=ProxyLookup[self];
  51.         if frame then FCF_Close(frame); end
  52.     end;
  53. }};
  54.  
  55. hooksecurefunc("FCF_Close",function(frame,other)
  56.     frame=other or frame;
  57.     local proxy=ProxyLookup[frame];
  58.     if proxy then ProxyLookup[proxy],ProxyLookup[frame]=nil,nil; end
  59. end);
  60.  
  61. function CreateDebugMessageFrame(name,r,g,b,a)
  62.     local frame=FCF_OpenTemporaryWindow("SYSTEM");
  63.     ChatFrame_RemoveMessageGroup(frame,"SYSTEM");
  64.  
  65.     FCF_SetWindowName(frame,name,true);
  66.     if r and g and b then FCF_SetWindowColor(frame,r,g,b,true); end
  67.     if a then FCF_SetWindowAlpha(frame,a,true); end
  68.  
  69.     local proxy=setmetatable({},ProxyMeta);
  70.     ProxyLookup[proxy],ProxyLookup[frame]=frame,proxy;
  71.     return proxy;
  72. end
  73.  
  74. function CreateDebugPrint(...)
  75.     local frame=CreateDebugMessageFrame(...);
  76.     return function(...) return frame:Print(...); end,frame;
  77. end

Two global functions are available in this API, CreateDebugMessageFrame() and CreateDebugPrint(). The first returns a proxy object that can be used to print to and show/hide the frame as well as release the chat window to the default UI (closing it). The second quickly creates the frame and returns just the print handler function you can call to print to the window.
__________________
ESOUI AddOns | WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)
  Reply With Quote
09-18-16, 03:46 PM   #5
rhiorg
A Deviate Faerie Dragon
AddOn Author - Click to view addons
Join Date: Sep 2016
Posts: 10
Thumbs up Awesome.

I was beating my head against the keyboard trying to figure out how to spawn a new chat frame. This did the trick.

Off-topic question: I'm curious...what does this line do?

local tostring=tostring;
  Reply With Quote
09-18-16, 04:38 PM   #6
Seerah
Fishing Trainer
 
Seerah's Avatar
WoWInterface Super Mod
Featured
Join Date: Oct 2006
Posts: 10,768
It creates a local upvalue of the global variable.
__________________
"You'd be surprised how many people violate this simple principle every day of their lives and try to fit square pegs into round holes, ignoring the clear reality that Things Are As They Are." -Benjamin Hoff, The Tao of Pooh

  Reply With Quote
09-18-16, 11:31 PM   #7
rhiorg
A Deviate Faerie Dragon
AddOn Author - Click to view addons
Join Date: Sep 2016
Posts: 10
Ah, ok. I'm still trying to grasp that concept.

Thanks!

Last edited by rhiorg : 09-18-16 at 11:31 PM. Reason: thanks
  Reply With Quote
09-19-16, 02:46 AM   #8
myrroddin
A Pyroguard Emberseer
 
myrroddin's Avatar
AddOn Author - Click to view addons
Join Date: Oct 2008
Posts: 1,124
Always nice to see tutorial code, but gah, guys! Where are the spaces? Let it breathe, as it adds readability. I know it is user preference, but really!
Lua Code:
  1. local tostring=tostring -- ouch on the brain and eyes
  2. local tostring = tostring -- ah, much better
  3.  
  4. -- which of these looks better?
  5. for i=1,select("#",...) do msg=(msg~="" and msg.." " or "")..tostring(select(i,...)); end
  6.  
  7. for i = 1, select("#", ...) do msg = (msg ~= "" and msg .." " or "") .. tostring(select(i, ...)); end
  8.  
  9. for i = 1, select("#", ...) do
  10.     msg = (msg ~= "" and msg .." " or "") ..tostring(select(i ,...));
  11. end
  Reply With Quote
09-19-16, 05:44 AM   #9
Kanegasi
A Molten Giant
 
Kanegasi's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2007
Posts: 507
Originally Posted by myrroddin View Post
Always nice to see tutorial code, but gah, guys! Where are the spaces? Let it breathe, as it adds readability. I know it is user preference, but really!
Lua Code:
  1. local tostring=tostring -- ouch on the brain and eyes
  2. local tostring = tostring -- ah, much better
  3.  
  4. -- which of these looks better?
  5. for i=1,select("#",...) do msg=(msg~="" and msg.." " or "")..tostring(select(i,...)); end
  6.  
  7. for i = 1, select("#", ...) do msg = (msg ~= "" and msg .." " or "") .. tostring(select(i, ...)); end
  8.  
  9. for i = 1, select("#", ...) do
  10.     msg = (msg ~= "" and msg .." " or "") ..tostring(select(i ,...));
  11. end
It's personal preference. This looks better to me:

Lua Code:
  1. for i=1,select('#',...) do
  2.     msg=(msg~='' and msg..' ' or '')..tostring(select(i,...))
  3. end

I only use spaces when absolutely necessary, and sometimes I don't even use returns. I usually just put a block onto one line if it's shorter than ~100 characters, but it depends on how it looks around other code.

Also, I refuse to use semicolons and unless I'm expecting apostrophes, I'll use single quotes.

Last edited by Kanegasi : 09-19-16 at 09:34 AM.
  Reply With Quote
09-19-16, 01:28 PM   #10
rhiorg
A Deviate Faerie Dragon
AddOn Author - Click to view addons
Join Date: Sep 2016
Posts: 10
OK, so I understand what this does (and the concept of closures in general), but I don't know why I would want to do this. I'm digging into the Lua docs to try to figure it out, but if anybody has a succinct, practical example or explanation...that would be great. I can't for the life of me figure out the why.
  Reply With Quote
09-19-16, 02:34 PM   #11
Kanegasi
A Molten Giant
 
Kanegasi's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2007
Posts: 507
Originally Posted by rhiorg View Post
OK, so I understand what this does (and the concept of closures in general), but I don't know why I would want to do this. I'm digging into the Lua docs to try to figure it out, but if anybody has a succinct, practical example or explanation...that would be great. I can't for the life of me figure out the why.
Is it still the confusion with the local tostring=tostring? If you use any global values, including any default Lua functions, you are querying _G. Every time you use a global, you query _G. That is the giant table that contains everything accessible by your code. For example, there's a global variable named MAX_QUESTS in WoW that currently equals 25. You can access this variable just by its name, but you can also use _G.MAX_QUESTS or _G["MAX_QUESTS"] and both equal 25.

Lua is optimized around table access, so performance with "normal" usage is irrelevant. However, if your code loops through something or does something repeatedly within a short amount of time, like an OnUpdate script going through a sizeable table, it's good practice to "localize" any globals you are using. Depending on how heavy your processes are, there is a measurable performance increase with using local objects versus global objects.

Besides the minor performance increase with heavy usage, it's just good practice to "localize" globals you intend to repeatedly use.

Last edited by Kanegasi : 09-19-16 at 02:38 PM.
  Reply With Quote
09-19-16, 03:16 PM   #12
rhiorg
A Deviate Faerie Dragon
AddOn Author - Click to view addons
Join Date: Sep 2016
Posts: 10
Smile

That was perfect. Thanks for the explanation. I tend to dive into the middle of these things and miss the fundamentals sometimes.
  Reply With Quote
09-19-16, 04:23 PM   #13
Lombra
A Molten Giant
 
Lombra's Avatar
AddOn Author - Click to view addons
Join Date: Nov 2006
Posts: 554
Originally Posted by Kanegasi View Post
Besides the minor performance increase with heavy usage, it's just good practice to "localize" globals you intend to repeatedly use.
Disagree. It just clutters the code needlessly. I only do it when I expect extremely frequent use, such as iterating a large database, handling combat log events, or maybe unthrottled OnUpdates.

But sure, if that doesn't bother you there's no downside to doing it. Well, except getting any potentially hooked functions, but that's most likely irrelevant.
__________________
Grab your sword and fight the Horde!
  Reply With Quote
09-19-16, 08:57 PM   #14
Seerah
Fishing Trainer
 
Seerah's Avatar
WoWInterface Super Mod
Featured
Join Date: Oct 2006
Posts: 10,768
Originally Posted by Lombra View Post
Disagree. It just clutters the code needlessly. I only do it when I expect extremely frequent use, such as iterating a large database, handling combat log events, or maybe unthrottled OnUpdates.
These ^^. Repeat table lookups are nice to upvalue, too.
__________________
"You'd be surprised how many people violate this simple principle every day of their lives and try to fit square pegs into round holes, ignoring the clear reality that Things Are As They Are." -Benjamin Hoff, The Tao of Pooh

  Reply With Quote
09-20-16, 12:41 AM   #15
rhiorg
A Deviate Faerie Dragon
AddOn Author - Click to view addons
Join Date: Sep 2016
Posts: 10
Sounds kinda like caching. (I'm coming from the C#, server-side world mostly.)
  Reply With Quote
09-20-16, 03:23 AM   #16
Resike
A Pyroguard Emberseer
AddOn Author - Click to view addons
Join Date: Mar 2010
Posts: 1,289
Originally Posted by Lombra View Post
Disagree. It just clutters the code needlessly. I only do it when I expect extremely frequent use, such as iterating a large database, handling combat log events, or maybe unthrottled OnUpdates.

But sure, if that doesn't bother you there's no downside to doing it. Well, except getting any potentially hooked functions, but that's most likely irrelevant.
Except if you don't do it and you use a find global tool it will print thousand lines of string, math, and etc functions makes it harder to actually catch a typo/global/whatever you looking for.
  Reply With Quote
09-20-16, 03:29 AM   #17
Ketho
A Molten Giant
 
Ketho's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2010
Posts: 946
Smile

Write everything in XML just like Semlar did
  Reply With Quote
09-20-16, 04:13 AM   #18
Kanegasi
A Molten Giant
 
Kanegasi's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2007
Posts: 507
Originally Posted by rhiorg View Post
Sounds kinda like caching. (I'm coming from the C#, server-side world mostly.)
That's actually an apt description of "local". Whatever follows is "cached" to your code's scope. Any object declared by "local" in your code, either a new one or one equal to a global one, is only usable by your code. A slight exception is a global table. When you declare a new object equal to a table, you are actually creating a reference to that table. Every time your code changes the object you made, the global table is changed as well, and vice versa (your local object sees changes to the global one). Other code still cannot use this local reference but can still manipulate the table it points to.
  Reply With Quote
09-20-16, 03:47 PM   #19
rhiorg
A Deviate Faerie Dragon
AddOn Author - Click to view addons
Join Date: Sep 2016
Posts: 10
Thanks for the explanations and debate. This is great stuff.
  Reply With Quote
09-20-16, 03:55 PM   #20
rhiorg
A Deviate Faerie Dragon
AddOn Author - Click to view addons
Join Date: Sep 2016
Posts: 10
Originally Posted by Ketho View Post
Write everything in XML just like Semlar did
I try to write as much in XML as possible (bear in mind that I've made two rather small addons thus far). I like doing the layout declaratively because it separates concerns and it's natural for me since I'm a web guy.

Side note: I wish a lot of the XML stuff could be handled with attributes instead of sub-elements (dimensions, fonts, etc.).
  Reply With Quote

WoWInterface » Developer Discussions » General Authoring Discussion » Using a chat frame as a log for addon?

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off