00001 // /////////////////////////////////////////////////////////////////////////// 00002 // lesson02_main.cpp by Victor Dods, created 2006/08/06 00003 // /////////////////////////////////////////////////////////////////////////// 00004 // Unless a different license was explicitly granted in writing by the 00005 // copyright holder (Victor Dods), this software is freely distributable under 00006 // the terms of the GNU General Public License, version 2. Any works deriving 00007 // from this work must also be released under the GNU GPL. See the included 00008 // file LICENSE for details. 00009 // /////////////////////////////////////////////////////////////////////////// 00010 00011 00012 // /////////////////////////////////////////////////////////////////////////// 00013 // Lesson 02 - SignalHandler, SignalSender and SignalReceiver 00014 // /////////////////////////////////////////////////////////////////////////// 00015 00016 /* @endcode 00019 This lesson will show you how to use the SignalHandler system -- a fancy 00020 abstraction for object communication. The GUI system is the primary user of 00021 this system to signal when certain events happen, such as a Button being 00022 pressed, or the text of a LineEdit being changed. 00023 00024 <ul> 00025 <li>@ref lesson02_main.cpp "This lesson's source code"</li> 00026 <li>@ref lessons "Main lesson index"</li> 00027 </ul> 00028 00029 The SignalHandler system can be thought of a way to plug objects into each 00030 other to handle communication. For instance, when a Button is pressed, it 00031 emits a signal indicating that it was pressed. This signal can be connected 00032 to other objects so that when said Button is pressed, particular methods in 00033 the other objects are called. The advantages are that the signal sender 00034 doesn't have to check or even care if there is or isn't anything connected to 00035 it, and that when an object is destructed, it disconnects all its connections 00036 automatically, making SignalHandler code very simple. 00037 00038 The system is facilitated by three classes: 00039 00040 <ul> 00041 <li>SignalHandler must be inherited by any class which contains one or 00042 more instances of SignalSender or SignalReceiver. It is what 00043 disconnects all its own remaining connections upon destruction.</li> 00044 <li>SignalSender (actually SignalSender0, SignalSender1 and SignalSender2, 00045 whose numbers indicate the number of parameters they broadcast) 00046 is what broadcasts signals out into space, to be received only by 00047 those objects connected to it.</li> 00048 <li>SignalReceiver (actually SignalReceiver0, SignalReceiver1 and 00049 SignalReceiver2, whose numbers indicate the number of parameters 00050 their callbacks accept) is what implements a handler method to be 00051 called when a signal is received.</li> 00052 </ul> 00053 00054 A signal can be composed of any number or type of arguments (including none). 00055 This is accomplished by the templatization of SignalSender1, SignalSender2, 00056 SignalReceiver1 and SignalReceiver2. Emitting a signal is syntactically 00057 identical to making a simple function call. If you still don't know what the 00058 fuck this is all about, looking at the example code should make things clear. 00059 00060 <strong>Procedural Overview</strong> -- Items in bold are additions/changes to the previous lesson. 00061 00062 <ul> 00063 <li>Main function</li> 00064 <ul> 00065 <li>Initialize the platform abstraction layer (Pal) and game engine singletons. 00066 Create the Screen object. This was covered in previous lesson(s).</li> 00067 <li>Execute game-specific code.</li> 00068 <ul> 00069 <li>Create formatted layouts of GUI widgets.</li> 00070 <li><strong>Connect SignalSenders up to SignalReceivers.</strong></li> 00071 <li>Run the game loop</li> 00072 <ul> 00073 <li>Handle events (user and system-generated).</li> 00074 <li>Perform off-screen processing.</li> 00075 <li>Draw the Screen object's entire widget hierarchy.</li> 00076 </ul> 00077 </ul> 00078 <li>Delete the Screen object. Shutdown the Pal and game engine singletons. 00079 This was covered in previous lesson(s).</li> 00080 </ul> 00081 </ul> 00082 00083 Comments explaining previously covered material will be made more terse or 00084 deleted entirely in each successive lesson. If something is not explained 00085 well enough, it was probably already explained in 00086 @ref lessons "previous lessons". 00087 00088 <strong>Code Diving!</strong> 00089 00090 @code */ 00091 // This header MUST be included in every source/header file. 00092 #include "xrb.hpp" 00093 00094 #include "xrb_button.hpp" // For use of the Button widget class. 00095 #include "xrb_event.hpp" // For use of the Event classes. 00096 #include "xrb_eventqueue.hpp" // For use of the EventQueue class. 00097 #include "xrb_inputstate.hpp" // For use of the InputState class (via Singleton::). 00098 #include "xrb_label.hpp" // For use of the Label widget class. 00099 #include "xrb_layout.hpp" // For use of the Layout widget class. 00100 #include "xrb_lineedit.hpp" // For use of the LineEdit widget class. 00101 #include "xrb_screen.hpp" // For use of the necessary Screen widget class. 00102 #include "xrb_sdlpal.hpp" // For use of the SDLPal platform abstraction layer. 00103 #include "xrb_transformation.hpp" // For use of Transformation::Lowercase and Uppercase. 00104 00105 // Used so we don't need to qualify every library type/class/etc with Xrb:: 00106 using namespace Xrb; 00107 00108 // This is just a helper function to group all the shutdown code together. 00109 void CleanUp () 00110 { 00111 fprintf(stderr, "CleanUp();\n"); 00112 00113 // Shutdown the platform abstraction layer. 00114 Singleton::Pal().Shutdown(); 00115 // Shutdown the game engine singletons. Do this OR PERISH. 00116 Singleton::Shutdown(); 00117 } 00118 00119 int main (int argc, char **argv) 00120 { 00121 fprintf(stderr, "main();\n"); 00122 00123 // Initialize the game engine singleton facilities. 00124 Singleton::Initialize(SDLPal::Create, "none"); 00125 // Initialize the Pal. 00126 if (Singleton::Pal().Initialize() != Pal::SUCCESS) 00127 return 1; 00128 // Set the window caption. 00129 Singleton::Pal().SetWindowCaption("XuqRijBuh Lesson 02"); 00130 // Create Screen object and initialize given video mode. 00131 Screen *screen = Screen::Create(800, 600, 32, false); 00132 // If the Screen failed to initialize, print an error message and quit. 00133 if (screen == NULL) 00134 { 00135 CleanUp(); 00136 return 2; 00137 } 00138 00139 /* @endcode 00140 At this point, the singletons and the Pal have been initialized, the video 00141 mode has been set, and the engine is ready to go. Here is where the 00142 application-specific code begins. We will create widgets different from 00143 the ones created in @ref lesson01 "lesson01". 00144 @code */ 00145 { 00146 // Create a Layout widget to contain everything within the Screen. 00147 Layout *main_layout = new Layout(VERTICAL, screen, "main layout"); 00148 // Cause @c main_layout to always fill the Screen. 00149 screen->SetMainWidget(main_layout); 00150 00151 /* @endcode 00152 Here we'll create the grid-type Layout widget mentioned in 00153 @ref lesson01 "lesson01". A grid Layout can be row-major 00154 (successively added widgets constitute left-to-right rows) or 00155 column-major (successively added widgets constitute top-to-bottom 00156 columns). The size of the major dimension must be specified, so 00157 the Layout knows when to wrap the rows/columns. 00158 @code */ 00159 Layout *button_signal_demo_layout = new Layout(ROW, 2, main_layout, "button signal demo layout"); 00160 /* @endcode 00161 Create four Buttons which will change different properties of the 00162 Label which will be created below. Two of the Buttons, when pressed, 00163 will disable/enable the Label, while the other two Buttons, when 00164 pressed <i>and</i> released, will disable/enable the Label. 00165 @code */ 00166 Button *disable_label_via_press_button = 00167 new Button( 00168 "Press to disable Label below", 00169 button_signal_demo_layout, 00170 "disable label via press button"); 00171 Button *enable_label_via_press_button = 00172 new Button( 00173 "Press to enable Label below", 00174 button_signal_demo_layout, 00175 "enable label via press button"); 00176 Button *disable_label_via_release_button = 00177 new Button( 00178 "Press and release to disable Label below", 00179 button_signal_demo_layout, 00180 "disable label via release button"); 00181 Button *enable_label_via_release_button = 00182 new Button( 00183 "Press and release to enable Label below", 00184 button_signal_demo_layout, 00185 "enable label via release button"); 00186 Label *mabel_the_disabled_label = 00187 new Label( 00188 "MY NAME IS MABEL THE LABEL\nIf the text is white, it is enabled.\nIf the text is darkened, it is disabled.", 00189 main_layout, 00190 "mabel the disabled label"); 00191 /* @endcode 00192 Create a "QUIT" button in the vertical @c main_layout which we will 00193 later hook up to the Screen's Screen::RequestQuit SignalReceiver. 00194 We'll also double the font size to make it look "HELLA TUFF." 00195 @code */ 00196 Button *quit_button = new Button("Press and release this button to QUIT", main_layout, "quit button"); 00197 quit_button->SetFontHeight(2 * quit_button->GetFont()->PixelHeight()); 00198 /* @endcode 00199 Create another grid Layout for another set of demo widgets. 00200 @code */ 00201 Layout *text_signal_demo_layout = new Layout(ROW, 2, main_layout, "text signal demo layout"); 00202 /* @endcode 00203 Create four pairs of widgets -- a Label/LineEdit, and three Label/Label 00204 pairs. The LineEdit will be used to enter text, and will emit signals 00205 when its text is changed. The second Labels in each pair will set their 00206 own values to the received text. The signal sent to the second and third 00207 labels -- <tt>lowercase_label</tt> and <tt>uppercase_label</tt> -- will 00208 be transformed en route to lowercase and uppercase text respectively. 00209 The signal connections and transformations will be handled after we 00210 create all our widgets. 00211 @code */ 00212 Label *generic_label = new Label("Enter text here:", text_signal_demo_layout); 00213 generic_label->SetAlignment(Dim::X, RIGHT); 00214 00215 LineEdit *enter_text_line_edit = new LineEdit(40, text_signal_demo_layout, "enter text lineedit"); 00216 00217 generic_label = new Label("Verbatim text:", text_signal_demo_layout); 00218 generic_label->SetAlignment(Dim::X, RIGHT); 00219 00220 Label *verbatim_label = new Label("", text_signal_demo_layout, "verbatim label"); 00221 verbatim_label->SetAlignment(Dim::X, LEFT); 00222 00223 generic_label = new Label("Text in lowercase:", text_signal_demo_layout); 00224 generic_label->SetAlignment(Dim::X, RIGHT); 00225 00226 Label *lowercase_label = new Label("", text_signal_demo_layout, "lowercase label"); 00227 lowercase_label->SetAlignment(Dim::X, LEFT); 00228 00229 generic_label = new Label("Text in UPPERCASE:", text_signal_demo_layout); 00230 generic_label->SetAlignment(Dim::X, RIGHT); 00231 00232 Label *uppercase_label = new Label("", text_signal_demo_layout, "UPPERCASE label"); 00233 uppercase_label->SetAlignment(Dim::X, LEFT); 00234 00235 // Remember that we don't need to worry about the apparently dangling 00236 // pointers left by all the above calls to new, because when a Widget 00237 // is destroyed, it deletes all its children. Thus, when the Screen 00238 // is deleted, its entire widget hierarchy is pulverized. 00239 00240 /* @endcode 00241 Here is where we'll hook up all the signals that were being talked up 00242 so much earlier. First, we'll hook up the simpler Button signals. 00243 00244 Signal connections are made through a series of static SignalHandler 00245 methods prefixed with <tt>Connect</tt>, suffixed with the number of 00246 parameters used by the signal being connected. The first parameter 00247 to this method is a pointer to the SignalSender -- these are available 00248 through public accessor methods in the object being connected -- 00249 Button::SenderPressed for example. The second parameter is the 00250 SignalReceiver -- also available through accessor methods -- 00251 Widget::ReceiverDisable for 00252 example. 00253 00254 Once a connection is made, sender signals from the connected object 00255 will cause the receiver object's connected receiver handler to be 00256 called. This is a direct function call, as if the sender object 00257 was calling the receiver object's handler itself -- there is no 00258 asynchronous behavior or event passing going on. 00259 00260 Note that there is more than one SignalSender being connected to 00261 <tt>mabel_the_disabled_label</tt>'s ReceiverDisable and ReceiverEnable. 00262 You can connect as many SignalSenders to a single SignalReceiver as 00263 you'd like. We'll see below that the converse is also true. 00264 @code */ 00265 SignalHandler::Connect0( 00266 disable_label_via_press_button->SenderPressed(), 00267 mabel_the_disabled_label->ReceiverDisable()); 00268 SignalHandler::Connect0( 00269 enable_label_via_press_button->SenderPressed(), 00270 mabel_the_disabled_label->ReceiverEnable()); 00271 SignalHandler::Connect0( 00272 disable_label_via_release_button->SenderReleased(), 00273 mabel_the_disabled_label->ReceiverDisable()); 00274 SignalHandler::Connect0( 00275 enable_label_via_release_button->SenderReleased(), 00276 mabel_the_disabled_label->ReceiverEnable()); 00277 00278 /* @endcode 00279 Here is where we hook up the quit Button. We use Button::SenderReleased 00280 instead of Button::SenderPressed, because the defacto behavior for GUI 00281 buttons is to activate when they're released after being pressed. 00282 @code */ 00283 SignalHandler::Connect0( 00284 quit_button->SenderReleased(), 00285 screen->ReceiverRequestQuit()); 00286 00287 /* @endcode 00288 We've only dealt with zero-parameter signals so far, but what use is a 00289 fancy signal-connection system without the ability to pass values 00290 around? The SignalSender and Receiver classes are templatized, so 00291 there is no restriction on the type of parameters that can be passed. 00292 00293 We will connect the text signal demo signals now. Each signal involved 00294 here will pass a single parameter -- either a <tt>std::string const &</tt> 00295 or a <tt>std::string</tt>. The difference is subtle but important: we 00296 want to avoid passing strings around by value if possible (to avoid 00297 unnecessary calls to <tt>std::string</tt>'s copy constructor), but in 00298 certain situations which will be explained shortly, we <i>must</i> pass 00299 by value. 00300 00301 LineEdit provides us with several options in terms of SignalSenders: 00302 00303 <ul> 00304 <li>LineEdit::SenderTextUpdated passes the LineEdit's text content 00305 by const reference and is signaled when the text has been 00306 changed via LineEdit::SetText, when the user changes the text 00307 and hits enter, or when the user changes the text and removes 00308 GUI focus from the LineEdit.</li> 00309 <li>LineEdit::SenderTextUpdatedV is identical to 00310 LineEdit::SenderTextUpdated but it passes the text 00311 <i>by value</i> instead.</li> 00312 <li>LineEdit::SenderTextSetByEnterKey is identical to 00313 LineEdit::SenderTextUpdated but is only signaled when the user 00314 hits the enter key.</li> 00315 <li>LineEdit::SenderTextSetByEnterKeyV is identical to 00316 LineEdit::SenderTextSetByEnterKey but it passes the text 00317 <i>by value</i> instead.</li> 00318 </ul> 00319 00320 In each of SignalHandler's Connect methods, there are optional parameters 00321 to specify transformations to the values being passed in the signal. 00322 Specifically, in one instance we will be transforming the text to 00323 lowercase, and another analogously for uppercase. There is no limit 00324 on the type or purpose for the transformations. They're simply regular 00325 global functions (or static methods) which take one parameter -- using 00326 the exact signal parameter type as the parameter type and return value 00327 type. For signals with more than one parameter, the transformations 00328 are the same -- one parameter, one return value -- the difference is 00329 that you specify as many transformations as there are parameters (or 00330 NULL if you do not want to transform the respective value). 00331 00332 For connecting <tt>enter_text_line_edit</tt> to <tt>verbatim_label</tt>, 00333 we do not need to employ a transformation, so we can use the preferred 00334 <tt>std::string const &</tt> (pass by const reference) signal, 00335 LineEdit::SenderTextUpdated. 00336 00337 For connecting <tt>enter_text_line_edit</tt> to <tt>lowercase_label</tt> 00338 and <tt>uppercase_label</tt>, we must use Transformation::Lowercase and 00339 Transformation::Uppercase respectively. Here is where we <i>must</i> 00340 use the <tt>std::string</tt> (pass by value) signal, 00341 LineEdit::SenderTextUpdatedV, and here's why: say we pass a const 00342 reference into a transformation function. That function can't modify 00343 the value passed in because it's const, so it must make its own copy 00344 of the value on the stack (or the heap, but I wouldn't touch that 00345 scandalousness with a 20 foot poo-stick). Transformations must accept 00346 and return the exact type used by the signal, so in this case the 00347 transformation function would have to also return a const reference. 00348 A const reference to what? The locally declared stack value which 00349 will be destructed upon the function returning. Now we've got a const 00350 reference to whatever abomination is left where the stack variable 00351 used to be, which by the way, is an invalid address. QED, motherfucker. 00352 00353 Note that we're connecting a single SignalSender to many 00354 SignalReceivers. There is no limit on the number of connections. 00355 When a sender signals, it will call the connected receivers in the 00356 order they were connected. 00357 @code */ 00358 SignalHandler::Connect1( 00359 enter_text_line_edit->SenderTextUpdated(), 00360 verbatim_label->ReceiverSetText()); 00361 SignalHandler::Connect1( 00362 enter_text_line_edit->SenderTextUpdatedV(), 00363 &Transformation::Lowercase, 00364 lowercase_label->ReceiverSetTextV()); 00365 SignalHandler::Connect1( 00366 enter_text_line_edit->SenderTextUpdatedV(), 00367 &Transformation::Uppercase, 00368 uppercase_label->ReceiverSetTextV()); 00369 /* @endcode 00370 From here on out is identical to the previous lesson -- the game loop 00371 will not change very much over the course of these lessons. 00372 00373 Notice that we don't have to do anything to shutdown or destruct the 00374 signal connections we made above. They are automatically disconnected 00375 when the respective objects are destroyed. Thus you can even manually 00376 delete an object with active connections and it will disconnect itself 00377 automatically from everything it's connected to, making maintenance 00378 of SignalHandler code very much fire-and-forget. 00379 @code */ 00380 // Run the game loop until the Screen feels like committing seppuku. 00381 while (!screen->IsQuitRequested()) 00382 { 00383 // Sleep for 33 milliseconds to limit the framerate and avoid 00384 // hogging up too much CPU just for this crappy little GUI app. 00385 Singleton::Pal().Sleep(33); 00386 // Retrieve the current time in seconds as a Float. 00387 Float time = 0.001f * Singleton::Pal().CurrentTime(); 00388 // Process events until there are no more. 00389 Event *event = NULL; 00390 while ((event = Singleton::Pal().PollEvent(screen, time)) != NULL) 00391 { 00392 // Let the InputState singleton "have a go" at keyboard/mouse events. 00393 if (event->IsKeyEvent() || event->IsMouseButtonEvent()) 00394 Singleton::InputState().ProcessEvent(event); 00395 // Hand the event to the top of the GUI hierarchy for 00396 // delegatory (is that a real word?) processing. 00397 screen->ProcessEvent(event); 00398 // Must delete the event ourselves here. 00399 Delete(event); 00400 } 00401 00402 // Turn the crank on the EventQueue. 00403 screen->OwnerEventQueue()->ProcessFrame(time); 00404 // Perform all off-screen GUI hierarchy processing. 00405 screen->ProcessFrame(time); 00406 // Turn the crank on the EventQueue again. 00407 screen->OwnerEventQueue()->ProcessFrame(time); 00408 // Draw the whole mu'erfucking thing. 00409 screen->Draw(); 00410 } 00411 } 00412 00413 // Delete the Screen (and GUI hierarchy). 00414 Delete(screen); 00415 // this shuts down the Pal and the singletons. 00416 CleanUp(); 00417 // return with success value. 00418 return 0; 00419 } 00420 /* @endcode 00421 00422 <strong>Exercises</strong> 00423 00424 <ul> 00425 <li>Once again change the video mode flag parameter of the call to 00426 Screen::Create to <tt>true</tt>. You will be able to quit the 00427 app because of the AWESOME quit button we created and hooked up.</li> 00428 <li>Change the major direction (first parameter) and the major count 00429 (second parameter) of the Layout constructor for 00430 <tt>text_signal_demo_layout</tt> to different values (<tt>ROW</tt> or 00431 <tt>COLUMN</tt> for major direction, and a positive integer for the 00432 major count) and see what effect it has on the locations of each of 00433 its child widgets.</li> 00434 <li>Screw around with the enabling/disabling buttons by pressing 00435 each and dragging the mouse cursor off the button before releasing, 00436 or clicking somewhere outside a button and dragging onto it before 00437 releasing, and see how they react. Also note the visual 00438 highlighting while doing this.</li> 00439 <li>Add four more buttons analogous to the ones which disable/enable the 00440 label below them. Hook them up to the hide/show receivers of said Label 00441 -- using ReceiverHide and ReceiverShow in place of ReceiverDisable and 00442 ReceiverEnable. See what these new buttons do to the Label.</li> 00443 <li>Add another pair of Labels and signal connection analogous to that of 00444 uppercase_label. Write your own custom <tt>std::string</tt> 00445 transformation which performs ROT13 encryption on the passed-in value 00446 (see http://en.wikipedia.org/wiki/ROT13). Use this transformation in 00447 the newly added signal connection.</li> 00448 </ul> 00449 00450 Thus concludes lesson02. Three down, countless more to go. 00451 */