Lesson 02 - SignalHandler, SignalSender and SignalReceiver

 *//* 
This lesson will show you how to use the SignalHandler system -- a fancy abstraction for object communication. The GUI system is the primary user of this system to signal when certain events happen, such as a Button being pressed, or the text of a LineEdit being changed.

The SignalHandler system can be thought of a way to plug objects into each other to handle communication. For instance, when a Button is pressed, it emits a signal indicating that it was pressed. This signal can be connected to other objects so that when said Button is pressed, particular methods in the other objects are called. The advantages are that the signal sender doesn't have to check or even care if there is or isn't anything connected to it, and that when an object is destructed, it disconnects all its connections automatically, making SignalHandler code very simple.

The system is facilitated by three classes:

A signal can be composed of any number or type of arguments (including none). This is accomplished by the templatization of SignalSender1, SignalSender2, SignalReceiver1 and SignalReceiver2. Emitting a signal is syntactically identical to making a simple function call. If you still don't know what the fuck this is all about, looking at the example code should make things clear.

Procedural Overview -- Items in bold are additions/changes to the previous lesson.

Comments explaining previously covered material will be made more terse or deleted entirely in each successive lesson. If something is not explained well enough, it was probably already explained in previous lessons.

Code Diving!

 */
// This header MUST be included in every source/header file.
#include "xrb.hpp"

#include "xrb_button.hpp"         // For use of the Button widget class.
#include "xrb_event.hpp"          // For use of the Event classes.
#include "xrb_eventqueue.hpp"     // For use of the EventQueue class.
#include "xrb_inputstate.hpp"     // For use of the InputState class (via Singleton::).
#include "xrb_label.hpp"          // For use of the Label widget class.
#include "xrb_layout.hpp"         // For use of the Layout widget class.
#include "xrb_lineedit.hpp"       // For use of the LineEdit widget class.
#include "xrb_screen.hpp"         // For use of the necessary Screen widget class.
#include "xrb_sdlpal.hpp"         // For use of the SDLPal platform abstraction layer.
#include "xrb_transformation.hpp" // For use of Transformation::Lowercase and Uppercase.

// Used so we don't need to qualify every library type/class/etc with Xrb::
using namespace Xrb;

// This is just a helper function to group all the shutdown code together.
void CleanUp ()
{
    fprintf(stderr, "CleanUp();\n");

    // Shutdown the platform abstraction layer.
    Singleton::Pal().Shutdown();
    // Shutdown the game engine singletons.  Do this OR PERISH.
    Singleton::Shutdown();
}

int main (int argc, char **argv)
{
    fprintf(stderr, "main();\n");

    // Initialize the game engine singleton facilities.
    Singleton::Initialize(SDLPal::Create, "none");
    // Initialize the Pal.
    if (Singleton::Pal().Initialize() != Pal::SUCCESS)
        return 1;
    // Set the window caption.
    Singleton::Pal().SetWindowCaption("XuqRijBuh Lesson 02");
    // Create Screen object and initialize given video mode.
    Screen *screen = Screen::Create(800, 600, 32, false);
    // If the Screen failed to initialize, print an error message and quit.
    if (screen == NULL)
    {
        CleanUp();
        return 2;
    }

    /* 
At this point, the singletons and the Pal have been initialized, the video mode has been set, and the engine is ready to go. Here is where the application-specific code begins. We will create widgets different from the ones created in lesson01.
 */
    {
        // Create a Layout widget to contain everything within the Screen.
        Layout *main_layout = new Layout(VERTICAL, screen, "main layout");
        // Cause @c main_layout to always fill the Screen.
        screen->SetMainWidget(main_layout);

        /* 
Here we'll create the grid-type Layout widget mentioned in lesson01. A grid Layout can be row-major (successively added widgets constitute left-to-right rows) or column-major (successively added widgets constitute top-to-bottom columns). The size of the major dimension must be specified, so the Layout knows when to wrap the rows/columns.
 */
        Layout *button_signal_demo_layout = new Layout(ROW, 2, main_layout, "button signal demo layout");
        /* 
Create four Buttons which will change different properties of the Label which will be created below. Two of the Buttons, when pressed, will disable/enable the Label, while the other two Buttons, when pressed and released, will disable/enable the Label.
 */
        Button *disable_label_via_press_button =
            new Button(
                "Press to disable Label below",
                button_signal_demo_layout,
                "disable label via press button");
        Button *enable_label_via_press_button =
            new Button(
                "Press to enable Label below",
                button_signal_demo_layout,
                "enable label via press button");
        Button *disable_label_via_release_button =
            new Button(
                "Press and release to disable Label below",
                button_signal_demo_layout,
                "disable label via release button");
        Button *enable_label_via_release_button =
            new Button(
                "Press and release to enable Label below",
                button_signal_demo_layout,
                "enable label via release button");
        Label *mabel_the_disabled_label =
            new Label(
                "MY NAME IS MABEL THE LABEL\nIf the text is white, it is enabled.\nIf the text is darkened, it is disabled.",
                main_layout,
                "mabel the disabled label");
        /* 
Create a "QUIT" button in the vertical main_layout which we will later hook up to the Screen's Screen::RequestQuit SignalReceiver. We'll also double the font size to make it look "HELLA TUFF."
 */
        Button *quit_button = new Button("Press and release this button to QUIT", main_layout, "quit button");
        quit_button->SetFontHeight(2 * quit_button->GetFont()->PixelHeight());
        /* 
Create another grid Layout for another set of demo widgets.
 */
        Layout *text_signal_demo_layout = new Layout(ROW, 2, main_layout, "text signal demo layout");
        /* 
Create four pairs of widgets -- a Label/LineEdit, and three Label/Label pairs. The LineEdit will be used to enter text, and will emit signals when its text is changed. The second Labels in each pair will set their own values to the received text. The signal sent to the second and third labels -- lowercase_label and uppercase_label -- will be transformed en route to lowercase and uppercase text respectively. The signal connections and transformations will be handled after we create all our widgets.
 */
        Label *generic_label = new Label("Enter text here:", text_signal_demo_layout);
        generic_label->SetAlignment(Dim::X, RIGHT);

        LineEdit *enter_text_line_edit = new LineEdit(40, text_signal_demo_layout, "enter text lineedit");

        generic_label = new Label("Verbatim text:", text_signal_demo_layout);
        generic_label->SetAlignment(Dim::X, RIGHT);

        Label *verbatim_label = new Label("", text_signal_demo_layout, "verbatim label");
        verbatim_label->SetAlignment(Dim::X, LEFT);

        generic_label = new Label("Text in lowercase:", text_signal_demo_layout);
        generic_label->SetAlignment(Dim::X, RIGHT);

        Label *lowercase_label = new Label("", text_signal_demo_layout, "lowercase label");
        lowercase_label->SetAlignment(Dim::X, LEFT);

        generic_label = new Label("Text in UPPERCASE:", text_signal_demo_layout);
        generic_label->SetAlignment(Dim::X, RIGHT);

        Label *uppercase_label = new Label("", text_signal_demo_layout, "UPPERCASE label");
        uppercase_label->SetAlignment(Dim::X, LEFT);

        // Remember that we don't need to worry about the apparently dangling
        // pointers left by all the above calls to new, because when a Widget
        // is destroyed, it deletes all its children.  Thus, when the Screen
        // is deleted, its entire widget hierarchy is pulverized.

        /* 
Here is where we'll hook up all the signals that were being talked up so much earlier. First, we'll hook up the simpler Button signals.

Signal connections are made through a series of static SignalHandler methods prefixed with Connect, suffixed with the number of parameters used by the signal being connected. The first parameter to this method is a pointer to the SignalSender -- these are available through public accessor methods in the object being connected -- Button::SenderPressed for example. The second parameter is the SignalReceiver -- also available through accessor methods -- Widget::ReceiverDisable for example.

Once a connection is made, sender signals from the connected object will cause the receiver object's connected receiver handler to be called. This is a direct function call, as if the sender object was calling the receiver object's handler itself -- there is no asynchronous behavior or event passing going on.

Note that there is more than one SignalSender being connected to mabel_the_disabled_label's ReceiverDisable and ReceiverEnable. You can connect as many SignalSenders to a single SignalReceiver as you'd like. We'll see below that the converse is also true.

 */
        SignalHandler::Connect0(
            disable_label_via_press_button->SenderPressed(),
            mabel_the_disabled_label->ReceiverDisable());
        SignalHandler::Connect0(
            enable_label_via_press_button->SenderPressed(),
            mabel_the_disabled_label->ReceiverEnable());
        SignalHandler::Connect0(
            disable_label_via_release_button->SenderReleased(),
            mabel_the_disabled_label->ReceiverDisable());
        SignalHandler::Connect0(
            enable_label_via_release_button->SenderReleased(),
            mabel_the_disabled_label->ReceiverEnable());

        /* 
Here is where we hook up the quit Button. We use Button::SenderReleased instead of Button::SenderPressed, because the defacto behavior for GUI buttons is to activate when they're released after being pressed.
 */
        SignalHandler::Connect0(
            quit_button->SenderReleased(),
            screen->ReceiverRequestQuit());

        /* 
We've only dealt with zero-parameter signals so far, but what use is a fancy signal-connection system without the ability to pass values around? The SignalSender and Receiver classes are templatized, so there is no restriction on the type of parameters that can be passed.

We will connect the text signal demo signals now. Each signal involved here will pass a single parameter -- either a std::string const & or a std::string. The difference is subtle but important: we want to avoid passing strings around by value if possible (to avoid unnecessary calls to std::string's copy constructor), but in certain situations which will be explained shortly, we must pass by value.

LineEdit provides us with several options in terms of SignalSenders:

In each of SignalHandler's Connect methods, there are optional parameters to specify transformations to the values being passed in the signal. Specifically, in one instance we will be transforming the text to lowercase, and another analogously for uppercase. There is no limit on the type or purpose for the transformations. They're simply regular global functions (or static methods) which take one parameter -- using the exact signal parameter type as the parameter type and return value type. For signals with more than one parameter, the transformations are the same -- one parameter, one return value -- the difference is that you specify as many transformations as there are parameters (or NULL if you do not want to transform the respective value).

For connecting enter_text_line_edit to verbatim_label, we do not need to employ a transformation, so we can use the preferred std::string const & (pass by const reference) signal, LineEdit::SenderTextUpdated.

For connecting enter_text_line_edit to lowercase_label and uppercase_label, we must use Transformation::Lowercase and Transformation::Uppercase respectively. Here is where we must use the std::string (pass by value) signal, LineEdit::SenderTextUpdatedV, and here's why: say we pass a const reference into a transformation function. That function can't modify the value passed in because it's const, so it must make its own copy of the value on the stack (or the heap, but I wouldn't touch that scandalousness with a 20 foot poo-stick). Transformations must accept and return the exact type used by the signal, so in this case the transformation function would have to also return a const reference. A const reference to what? The locally declared stack value which will be destructed upon the function returning. Now we've got a const reference to whatever abomination is left where the stack variable used to be, which by the way, is an invalid address. QED, motherfucker.

Note that we're connecting a single SignalSender to many SignalReceivers. There is no limit on the number of connections. When a sender signals, it will call the connected receivers in the order they were connected.

 */
        SignalHandler::Connect1(
            enter_text_line_edit->SenderTextUpdated(),
            verbatim_label->ReceiverSetText());
        SignalHandler::Connect1(
            enter_text_line_edit->SenderTextUpdatedV(),
            &Transformation::Lowercase,
            lowercase_label->ReceiverSetTextV());
        SignalHandler::Connect1(
            enter_text_line_edit->SenderTextUpdatedV(),
            &Transformation::Uppercase,
            uppercase_label->ReceiverSetTextV());
        /* 
From here on out is identical to the previous lesson -- the game loop will not change very much over the course of these lessons.

Notice that we don't have to do anything to shutdown or destruct the signal connections we made above. They are automatically disconnected when the respective objects are destroyed. Thus you can even manually delete an object with active connections and it will disconnect itself automatically from everything it's connected to, making maintenance of SignalHandler code very much fire-and-forget.

 */
        // Run the game loop until the Screen feels like committing seppuku.
        while (!screen->IsQuitRequested())
        {
            // Sleep for 33 milliseconds to limit the framerate and avoid
            // hogging up too much CPU just for this crappy little GUI app.
            Singleton::Pal().Sleep(33);
            // Retrieve the current time in seconds as a Float.
            Float time = 0.001f * Singleton::Pal().CurrentTime();
            // Process events until there are no more.
            Event *event = NULL;
            while ((event = Singleton::Pal().PollEvent(screen, time)) != NULL)
            {
                // Let the InputState singleton "have a go" at keyboard/mouse events.
                if (event->IsKeyEvent() || event->IsMouseButtonEvent())
                    Singleton::InputState().ProcessEvent(event);
                // Hand the event to the top of the GUI hierarchy for
                // delegatory (is that a real word?) processing.
                screen->ProcessEvent(event);
                // Must delete the event ourselves here.
                Delete(event);
            }

            // Turn the crank on the EventQueue.
            screen->OwnerEventQueue()->ProcessFrame(time);
            // Perform all off-screen GUI hierarchy processing.
            screen->ProcessFrame(time);
            // Turn the crank on the EventQueue again.
            screen->OwnerEventQueue()->ProcessFrame(time);
            // Draw the whole mu'erfucking thing.
            screen->Draw();
        }
    }

    // Delete the Screen (and GUI hierarchy).
    Delete(screen);
    // this shuts down the Pal and the singletons.
    CleanUp();
    // return with success value.
    return 0;
}
/* 

Exercises

Thus concludes lesson02. Three down, countless more to go.


Hosted by SourceForge.net Logo -- Generated on Fri Aug 21 21:46:38 2009 for XuqRijBuh by doxygen 1.5.8