Redirecting Lua Output

From CEGUI Wiki - Crazy Eddie's GUI System (Open Source)
Jump to: navigation, search

Written for CEGUI 0.7


Works with versions 0.7.x (obsolete)

What is the idea

I think you are here because you want to redirect Lua output to a window like a console or something similar. Then i will give you the answer. There is more than one way to do this. I will use the easy lua way here.

The setup

We will use a similar console window like in CEGUI_In_Practice_-_A_Game_Console. So if you don't know this Tutorial yet you can take a look but it is not a must. If you don't know how to use Lua with CEGUI take a look at Getting_Started_with_Lua_and_CEGUI.

This will be the Layout for our console. If your CEGUI version does not have Vanilla then you can use "TaharezLook". This layout is copied from "VanillaConsole.layout" which comes with CEGUI examples at the moment.

<?xml version="1.0" ?>
<GUILayout>
   <Window Type="Vanilla/FrameWindow" Name="Vanilla/Console">
       <Property Name="AlwaysOnTop" Value="True" />
       <Property Name="UnifiedMinSize" Value="{{0.2,0},{0.2,0}}" />
       <Property Name="UnifiedMaxSize" Value="{{0.8,0},{0.8,0}}" />
       <Property Name="UnifiedPosition" Value="{{0.5,0},{0.5,0}}" />
       <Property Name="UnifiedSize" Value="{{0.5,0},{0.45,0}}" />
       <Property Name="Text" Value="Console" />
       <Property Name="CloseButtonEnabled" Value="False" />
       <Window Type="Vanilla/Button" Name="Vanilla/Console/Submit">
           <Property Name="ID" Value="1" />
           <Property Name="VerticalAlignment" Value="Bottom" />
           <Property Name="HorizontalAlignment" Value="Right" />
           <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
           <Property Name="UnifiedPosition" Value="{{0,-7},{0,-7}}" />
           <Property Name="UnifiedSize" Value="{{0.25,0},{0,30}}" />
           <Property Name="Text" Value="Submit" />
           <Event Name="Clicked" Function="luabtn_clicked" />
       </Window>
       <Window Type="Vanilla/Editbox" Name="Vanilla/Console/Editbox">
           <Property Name="ID" Value="2" />
           <Property Name="VerticalAlignment" Value="Bottom" />
           <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
           <Property Name="UnifiedPosition" Value="{{0,7},{0,-7}}" />
           <Property Name="UnifiedSize" Value="{{0.75,-21},{0,30}}" />
           <Property Name="Text" Value="" />
       </Window>
       <Window Type="Vanilla/MultiLineEditbox" Name="Vanilla/Console/History">
           <Property Name="ID" Value="3" />
           <Property Name="ReadOnly" Value="True" />
           <Property Name="UnifiedMaxSize" Value="{{1,0},{1,0}}" />
           <Property Name="UnifiedPosition" Value="{{0,7},{0,7}}" />
           <Property Name="UnifiedSize" Value="{{1,-14},{1,-47}}" />
           <Property Name="Text">Console ready for input:</Property>
       </Window>
   </Window>
</GUILayout>

Please note that i have added the following line in the layout above to connect the event with the script i show later.

<Event Name="Clicked" Function="luabtn_clicked" />

After loading CEGUI we will do some basic setup. I use a Lua init script but you can also code this in C. My Lua init script looks like the following:

-- get CEGUI singletons
local logger = CEGUI.Logger:getSingleton()
logger:logEvent( ">>> Init script says hello" )
--logger:setLoggingLevel( CEGUI.Informative )

-- get a local reference to the singletons we use (not required)
local system    = CEGUI.System:getSingleton()
local fontman   = CEGUI.FontManager:getSingleton()
local schememan = CEGUI.SchemeManager:getSingleton()

-- load schemes
schememan:create( "TaharezLook.scheme" )
schememan:create( "WindowsLook.scheme" )
schememan:create( "VanillaSkin.scheme" )

-- load a default font
fontman:create( "DejaVuSans-10.font" )

-- set default mouse cursor
system:setDefaultMouseCursor( "Vanilla-Images", "MouseArrow" )

-- set default tooltip type
system:setDefaultTooltip("TaharezLook/Tooltip")

logger:logEvent( "<<< Init script says goodbye" )

The Trick

Here comes now the interresting part (MyConsole.lua):

-----------------------------------------
-- Start of handler functions
-----------------------------------------
-----------------------------------------
-- Handler to add line which will be executed and execute it
-----------------------------------------
function luabtn_clicked(args)
   local winMgr = CEGUI.WindowManager:getSingleton()
   
   local newText = winMgr:getWindow("Vanilla/Console/Editbox"):getText()
   local oldText = winMgr:getWindow("Vanilla/Console/History"):getText()
   
   print("executing: " .. newText)
   
   --execute code
   local f = loadstring(newText)
   if f then 
       print(f())
   else
       print("invalid syntax!")
   end
end

-----------------------------------------
-- Our print function which overrides default print behavior
-----------------------------------------
function consolePrint(s)
   local winMgr = CEGUI.WindowManager:getSingleton()
   local oldText = winMgr:getWindow("Vanilla/Console/History"):getText()
   if s then
       CEGUI.toMultiLineEditbox(winMgr:getWindow("Vanilla/Console/History")):setText(oldText .. s)
   else
        CEGUI.toMultiLineEditbox(winMgr:getWindow("Vanilla/Console/History")):setText(oldText .. "")
   end
end

-----------------------------------------
-- Script Entry Point
-----------------------------------------
local guiSystem = CEGUI.System:getSingleton()
local winMgr = CEGUI.WindowManager:getSingleton()

-- load our demo8 window layout
local root = winMgr:loadWindowLayout("VanillaConsole.layout")
-- set the layout as the root
guiSystem:setGUISheet(root)

--print/output redirect to cegui console window - overriding normal behavior
print = consolePrint
io.write = consolePrint
out = consolePrint

As you can see we execute luabtn_clicked if someone clicks on the button. This function then trys to get a function out of the sting and trys to execute it if it is a function. If Lua is not able to build a function out of it the syntax is wrong and we will print that.
Because we have replaced the normal print function with our print version this function gets called every "print" instead. It just adds the string to print to our history window.

Don't forget to start the script

System::getSingletonPtr()->executeScriptFile("MyConsole.lua");

Error handling

Because we can make a typo in console input and do not want to crash our app we now need a error handling. We can not use try/catch in Lua but we can do the error handling using the pcall function. I decided to do the error handling in C++ because i like try catch more and we get a good error message prepared from CEGUI ;)
We execute code in Lua if we click the button so we need to try/catch at the clickInjection. Because i don't know how i will use Lua in future i added the following try/catch everywhere i call a Lua script or inject something into CEGUI (also to the script execution call in the upper code block).

try {
    CEGUI::System::getSingleton().injectMouseButtonDown(convertOgreButtonToCegui(id));
} catch( CEGUI::Exception& e ) {
    showErrorMsgWindow((e.getMessage()+"\n\nFile: "+e.getFileName()+"\nLine: ").c_str(),__FILE__,__LINE__);
} catch (...) {
    showErrorMsgWindow("Unknown CEGUI error!",__FILE__,__LINE__);
}
inline void showErrorMsgWindow(const char* c_str, const char *file, const unsigned long line) {
    std::string str = c_str;
    str.append(file);
    str.append(CEGUI::PropertyHelper::intToString(line).c_str());//bad hack don't do this at home
#ifdef _WIN32
    MessageBox( NULL, str.c_str(), "Error",MB_OK | MB_ICONERROR | MB_TASKMODAL);
#else
    std::cerr << str.c_str() << std::endl;
#endif
}

Don't forget the right includes ;)
Now we will get a message window in windows systems and a error message in other systems.

Conclusion

This shows by example how to redirect Lua output to a CEGUI window.

User:DEvil HUnter 22:20 - 28. August 2012 (MEZ)