*//*
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; } /*
*/ { // 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); /*
*/ Layout *button_signal_demo_layout = new Layout(ROW, 2, main_layout, "button signal demo layout"); /*
*/ 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"); /*
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()); /*
*/ Layout *text_signal_demo_layout = new Layout(ROW, 2, main_layout, "text signal demo layout"); /*
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. /*
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());
/*
*/
SignalHandler::Connect0(
quit_button->SenderReleased(),
screen->ReceiverRequestQuit());
/*
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());
/*
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
true
. You will be able to quit the app because of the AWESOME quit button we created and hooked up. text_signal_demo_layout
to different values (ROW
or COLUMN
for major direction, and a positive integer for the major count) and see what effect it has on the locations of each of its child widgets. std::string
transformation which performs ROT13 encryption on the passed-in value (see http://en.wikipedia.org/wiki/ROT13). Use this transformation in the newly added signal connection. Thus concludes lesson02. Three down, countless more to go.