Pig! — Building a SkyOS Application in C++


Chapter 2

At the end of Chapter 1, Pig! displayed a plain grey window with non-functional menus, so the first order of business this round is to make the menus work. We'll also add a second 'Splash' window to test out the message handling and demonstrate how the window template class works. 

The initial set of C++ classes that appeared in the last chapter has taken on a whole new look, as it has been split into two parts: the application and window classes unique to Pig! and a more robust set of generic classes that can be used (and re-used) over and over again.  To distinguish between the two, I have prefixed all of the generic class names with an 'H', i.e., HApp, HRect, HWindow, and so on.  It's still way too early to call this a 'class library'—and other SkyOS developers are pursuing that goal—but it does at least offer some ideas on creating reusable code for SkyOS.  This set of generic classes will be collectively referred to as the Humble Framework, and will be documented separately from the Pig! application classes and code.

All of the source code and a precompiled binary for this chapter can be downloaded here: PigChapter02.zip.

Humble Framework

As previously mentioned, the Humble framework consists of a set of generic C++ classes that are designed to help create native SkyOS applications more efficiently by re-using as much code as practical.  Moreover, classes are provided that enhance several SkyOS primitive data types by adding better error checking and additional capabilities.  Since the Humble framework has the potential to grow quite large—not to mention complex—I have started using the open-source Doxygen tool to generate a on-line documentation for the Humble framework.  To briefly summarize the framework's contents:

High-Level Classes

Diagnostic / Error Handling

Utility Classes

For details or to download the latest version of the source code, please refer to the Humble Framework on-line documentation.

Development Tools

The single most important tool for creating our SkyOS application is the C++ compiler (gcc) which is available for a variety of platforms and operating systems from the GCC Home Page.  It doesn't matter if you are developing under Windows, Linux, or SkyOS itself, but what does matter is that you  must be using version 2.95.3-10 of gcc and it's accompanying libraries.  Newer versions will compile the code, but you will not be able to successfully link them, because the C++ runtime libraries for SkyOS have not been updated yet.  (Robert, are you listening?)  The hardest part of building the first C++ application is getting the make files properly set up so that all of the necessary libraries and startup code are linked.  We will be using two separate make files: rules.mak to define a global set of dependencies and makefile to define the dependencies unique to Pig!.  The following example is for building Pig! on a Windows system, using the Cygwin version of GCC; if you are using a different development environment, you will have to tailor the make files somewhat.  Your mileage may vary.

rules.mak

The only significant change to the rules.mak make file since the previous Chapter is the addition of a new symbol (DIR_HUMBLE) that points to the directory where the Humble class headers are stored.

makefile

The makefile file has been altered to reflect the new naming conventions, and now makes both application source files dependent on the entire set of Humble Framework headers. The Pig! application currently consists of only two (2) source files: Pig.cpp and WSplash.cpp, each of which has it's own header file as well.

#
# makefile for Pig! -- Thu 12 Aug '04
#
include ../../sdk/rules.mak

HUMBLE = $(DIR_HUMBLE)/Humble.h \
    $(DIR_HUMBLE)/HAL.h  \
    $(DIR_HUMBLE)/HApp.h \
    $(DIR_HUMBLE)/HBase.h \
    $(DIR_HUMBLE)/HDebug.h \
    $(DIR_HUMBLE)/HError.h \
    $(DIR_HUMBLE)/HString.h \
    $(DIR_HUMBLE)/HWindow.h

TARGET  = Pig.app
OBJFILES = Pig.o WSplash.o

EXTRA_CFLAGS = -I$(DIR_HUMBLE) -fno-exceptions -fno-const-strings
#
# Object files
#
Pig.o:  Pig.cpp \
    Pig.h WSplash.h $(HUMBLE)

WSplash.o: WSplash.cpp \
    Pig.h WSplash.h $(HUMBLE)

$(TARGET) : $(OBJFILES) $(DEFAULT_LIBS)
    $(LD) $(LD_FLAGS) $(OBJFILES) $(DEFAULT_LIBS) -o $(TARGET)
#
#
#
all : $(OBJFILES) $(TARGET)

clean :  rm -f $(OBJFILES) $(TARGET)
#
# End of makefile
#

Application Classes

The Pig! application currently consists of two unique classes, each of which has an interface (header) file and an implementation (source) file.  The classes are as follows:

The PigApp Class

We'll start with the top-level class, which represents the entire application.  This class—called App in Chapter 1 but now renamed PigApp—is declared in the header file Pig.h and is implemented in the source file Pig.cpp.  Unlike the original version, PigApp is declared as a subclass of HApp, so it inherits all of the functionality embedded in the HApp class, which includes the top level window.  To instantiate the PigApp application class, it is necessary to implement several functions that are declared as pure virtual by the base class:

The Create() method is almost identical to the original version, except that the last thing is does  now is call the HApp::manageWindow() method.  This turns the newly-created window over to the framework for management.  The Draw() function is also unchanged, except that the rDirty parameter is now references an HRect object instead of a raw s_region.  In addition to these mandatory functions, the PigApp class also overrides the Init() method so that it can automatically display a splash screen when the application starts up.  It accomplishes this by sending itself a CMD_HELP_ABOUT command just before entering the main event loop in HApp::Run().

 HRESULT
 PigApp::Init(int32 argc, TextPtr argv[])
 {
 if (base_class::Init(argc, argv) != S_OK)
     return S_FAILED;

 SendCommand(CMD_HELP_ABOUT);

 return S_OK;
 }

To make our non-functional menu bar actually work, two new command handlers have been added to the PigApp class: onAppExit() and onHelpAbout() which will receive the CMD_APP_EXIT and CMD_HELP_ABOUT commands.  To connect the commands to their respective handlers, some new entries have been made to the message map in the class declaration:

     BEGIN_MSG_MAP("PigApp")
         MAP_CMD( CMD_APP_EXIT,          onAppExit )
         MAP_CMD( CMD_HELP_ABOUT,        onHelpAbout )

         INCLUDE_MAP(base_class)
     END_MSG_MAP

Also new to the message map is the INCLUDE_MAP(base_class) statement, which routes any messages NOT handled by the PigApp class back to HApp for final disposition.  This technique works for not only the application, but also for window classes derived from HWindow;  (which is logical, because HApp is derived from HWindow as well).  The generic template for a command handler is bool myCommandHandler(uint32, HRESULT &) which returns true if the command is handled, or false otherwise.  The first parameter is the command identifier being handled, and the second is a success / failure flag; the flag defaults to S_OK, but should be set to any non-zero value if something went wrong during the command processing.  The new command handler for the CMD_APP_EXIT—declared inline in the Pig.h header file—now looks like this:

  bool onAppExit(uint32 uCmd, HRESULT & hr)
     {
     (void) Quit();
     return true;
     }

The WSplash Class

The 'splash window' displayed by the Pig! application is a simple, unadorned window that displays a JPEG image read in from an external file.  It is declared in the WSplash.h header file, and implemented in WSplash.cpp; and like virtually all windows in the Humble framework, it is sub-classed from the HWindow template class.  This causes all of the standard window processing to be inherited, so the WSplash class must also implement the two required methods:

The Create() method for the WSplash window accomplishes several tasks—with a surprisingly small amount of code—including (1) loading the JPEG image, (2) sizing the window to perfectly fit the image, and (3) centering the window on the screen.   The reason the code is so small is because a bulk of the processing and error-checking takes place behind the scenes in the framework code.

 HRESULT
 WSplash::Create(s_window * pwParent)
 {
 //
 //  Load the Splash image as a DIB; we have to do this BEFORE the window is
 //  created because we'll use the DIB dimensions as the window size.
 //
 if (!m_dib.LoadFromFile(kSplashImage))
     return S_FAILED;
 //
 //  Find the screen resolution and create the window in a hidden state.
 //
 HRect   rBounds;

 rBounds.Set(0, 0, m_dib.GetWidth(), m_dib.GetHeight());
 CenterToScreen(rBounds);

 return base_class::Create(pwParent, theApp.GetAppName(), rBounds,
                             WF_DONT_EREASE_BACKGROUND |
                             WF_FRAME_NO_ROUND_EDGES_BOTTOM | WF_FRAME_NO_ROUND_EDGES_TOP |
                             WF_HIDE | WF_NOT_SIZEABLE);
 }

Likewise, the Draw() method is also fairly compact, as it sets up and carries out a 'blit' of the JPEG preloaded image to the device context.

 HRESULT
 WSplash::Draw(
     HRect & rDirty)
 {
 sBlit       blit;

 if (!m_dib.IsValid() || rDirty.IsEmpty())
     return S_FAILED;

 GC_ResetBlit(&blit);

 blit.hDIB       = m_dib.GetPtr();
 blit.iDestX     = rDirty.x1;
 blit.iDestY     = rDirty.y1;
 blit.iSrcX      = rDirty.x1;
 blit.iSrcY      = rDirty.y1;
 blit.iWidth     = rDirty.GetWidth() + 1;
 blit.iHeight    = rDirty.GetHeight() + 1;

 GC_blit(m_pGC, &blit);

 return S_OK;
 }

Since we don't want the user to have to stare at our beautiful splash screen forever, the splash window will immediately close if the left mouse button is clicked anywhere in the window's client area, or after 15 seconds has elapsed (whichever comes first).  Detecting the mouse click is fairly simple: we add the line MAP_MSG(MSG_MOUSE_BUT1_RELEASED, onMouseBut1Released) to the message map, and add the message handler function, which need only call the Destroy() method inherited from the HWindow template class.

 bool
 onMouseBut1Released(s_gi_msg const & msg, HRESULT & hr)
     {
     Destroy();  // clicking in this window destroys it.
     return true;
     }

Having the window automatically self-destruct in 15 seconds takes a little more work, but not much.  First of all, the WSplash class overrides the onWindowCreate() function so that when it is created, a timer can be started.  It then waits for the MSG_TIMER_REACHED message to be received, whereupon it destroys the window in the same manner as if the user clicked the mouse button.  Lastly, whenever the window is destroyed, the timer is killed as well; strictly speaking, the timer may already have expired, but it never hurts to clean up after yourself.  Also notice how the onWindowCreate() and onWindowDestroy() methods both invoke the base class version as well:

 bool onTimerReached(s_gi_msg const & msg, HRESULT & hr)
     {
     Destroy();  // automagically destroy the window
     return true;
     }

 bool onWindowCreate(s_gi_msg const & msg, HRESULT & hr)
     {
     if (kTimeOut)
         m_tid = GI_set_timer(GetHandle(), kTimeOut);

     return base_class::onWindowCreate(msg, hr);
     }

 bool onWindowDestroy(s_gi_msg const & msg, HRESULT & hr)
     {
     if (m_tid)
         GI_kill_timer(m_tid);
     m_tid = 0;

     m_dib.Destroy();

     return base_class::onWindowDestroy(msg, hr);
     }
 

Where did main() go?

One thing conspicuously absent from this new version of Pig! is the standard C/C++ main() function, previously implemented in the Main.cpp source file (which is also missing!).  Since the Humble framework provides the HApp application class, it also provides the DECLARE_APP() macro, which is invoked at the top of the Pig.cpp source file:

  DECLARE_APP(PigApp);

This one line not only creates the global variable theApp, but also implements a complete main() function!  Needless to say, forgetting to include this one line—or declaring it more than once—will result in all kinds of horrible linker errors.

Conclusion

There may not be a lot of changes on the outside, but there is a ton of improvement on the inside.  As always, I highly recommend that you grab the Pig! source code and build your own version, but there  is also a precompiled binary that can be loaded and run 'as is'.  You'll also need to save the Splash JPEG to /boot/home/Splash.jpg to see the pretty new splash window.  Either way, you will still be able to read the pig-debug.txt file and see how the program flow went, and what messages were routed where. 

Coming Soon...

In Chapter 3, we'll look at saving (and restoring) application settings between sessions, using a user preferences file.

Until the next chapter…Tschüß!

,,,^..^,,,

2004.08.20-14:10