posted by Jean-Louis Villecroze on Wed 8th Dec 2004 21:40 UTC

"Zinzala, Page 9/11"
Derivating Here's the declaration of the class cMyConsoleView:

class cMyConsoleView : public cDConsoleView
{
   public:
      static cMyConsoleView *NewL(cDSkin &aSkin);

      void Reset();

   protected:
      void PlayPressed(tBool aActive);
      void PausePressed(tBool aActive);
      void StopPressed(tBool aActive);
      void FwdPressed(tBool aActive);
      void BwdPressed(tBool aActive);

   protected:
      cMyConsoleView();
};

No surprise here. The class will implement the callback for each button of the console.

The method NewL() needs to be re-implemented, even though it is already in the base class:

cMyConsoleView::cMyConsoleView()
	: cDConsoleView()
{
}

cMyConsoleView *cMyConsoleView::NewL(cDSkin &aSkin)
{
   cMyConsoleView *lView = new cMyConsoleView();
   cMyConsoleView::VerifyLC(lView);
   lView->ConstructL(aSkin);
   sCleanupStack::PopL(lView);
   return lView;
}

The implementation of NewL() is very close to the one we have in the base class. In fact, such methods are always very similar, since their purpose is to create a new object and construct it. The method NewL() from the base class is not really necessary because it is very unlikely that someone will want to use that class without derivating it, but you never know ... :)

I have added the method Reset() to the class. Its purpose is to reset the console to its stopped state:

void cMyConsoleView::Reset()
{
   SetState(eStopped);
}

Our implementation of the button callbacks that will be presented, is as simple as it can get. Real world implementations will likely be a bit more complex, although they will perform the same basic actions as here, such as changing the console state:

void cMyConsoleView::PlayPressed(tBool aActive)
{
   if(aActive)
      SetState(ePlaying);	
}

void cMyConsoleView::PausePressed(tBool aActive)
{
   if(aActive)
   {
      if(GetState() != ePaused)
         SetState(ePaused);	
      else
         SetState(ePlaying);	
   }
}

void cMyConsoleView::StopPressed(tBool aActive)
{
   if(aActive)
      SetState(eStopped);	
}

void cMyConsoleView::FwdPressed(tBool aActive)
{
   if(aActive)
   {
      if(GetState() != eForward)
         SetState(eForward);	
      else
         SetState(ePlaying);
   }
}

void cMyConsoleView::BwdPressed(tBool aActive)
{
   if(aActive)
   {
      if(GetState() != eBackward)
         SetState(eBackward);	
      else
         SetState(ePlaying);
   }
}

If you would like to see the complete source code of the cMyConsoleView class, here's the header and the C++ file.

Window and Application

Now that we have implemented the console class and specialized it, we can move on to the window and application. We will also have a look at the main of the test program.

cDWindow

Here's the window declaration from DWindow.h:

class cDWindow : public cWindow
{
   public:
      static cDWindow *NewL(cSettings *aSettings,cDSkin &aSkin);
      virtual ~cDWindow();
		
   protected:
      void Ready();
      bool CloseRequested();
		
   protected:
      cDWindow(cSettings *aSettings);
      void ConstructL(cDSkin &aSkin);

   private:
      cSettings*        iSettings;
      cMyConsoleView*   iConsole;
};

The cSettings class provides access to the application specific settings, which will be saved and restored each time the application runs. We will use it to save the position of the window on screen. This is not a requirement for any window or application with the Zinzala SDK, but rather a personal preference. I prefer that my window always appears at the last place I left it.

As you might guess by now, our window class will use the two-phase construction idiom. Note, although the SDK provides everything that is needed to use this idiom, it does not force the developer to use it, nor does it enforce the use of exceptions as the means of error handling.

Here's the C++ constructor of the class:

cDWindow::cDWindow(cSettings *aSettings)
   : cWindow(uRect(0,0),"window",eWinNormal,kFlgResizeToFit | kFlgNotResizable,
             kCurrentWorkspace)
{
   iSettings = aSettings;
   iConsole	 = NULL;
	
   if(!iFault)
   {
      uPoint *lPos;

      SetTitle("Zinzala : Custom Widget Demo ...");
      SetPadding(5,5);
      SetBGColor(kColorWhite);

If the base class construction turns out to be a success, we set the title of the window, then specify a 5x5 padding for the widget area. This padding doesn't mean that each widget placed on the window will be surrounded by 5 pixels of empty space. It simply means that there will be an inset of 5 pixels to the window bounds. We also set the background color to white.

      // Get position of the window is possible
      if(iSettings->HasItem("pos"))
      {
         iSettings->GetData("pos",eDataBuffer,(char **)&lPos,sizeof(uPoint));
         MoveTo(*lPos);
      }

If the demo application was already used once, the settings should contain the position of the window on screen. If this is the case, we read that position from the settings and move the window accordingly.

   }
}

The instantiation of our console will be placed in the ConstructL() method of the window, as it can throw an exception:

void cDWindow::ConstructL(cDSkin &aSkin)
{
   sEnv::LeaveIfError(iFault);

   iConsole = cMyConsoleView::NewL(aSkin);
   AddChild(iConsole);
}

If the cWindow class does not delete all its children views when it is destroyed, we would have had to add the destruction of iConsole in it. However since it does, the class destructor is empty.

Here is the method NewL() of the window class:

cDWindow *cDWindow::NewL(cSettings *aSettings,cDSkin &aSkin)
{
   cDWindow *lWin = new cDWindow(aSettings);
   cDWindow::VerifyLC(lWin);
   lWin->ConstructL(aSkin);
   sCleanupStack::PopL(lWin);
   return lWin;
}

The method Ready() and CloseRequested() are implemented in our class and defined in cWindow. The first one is called after the window is displayed on screen. We will be using it to set the beat rate of the window, which our console widget needs for the active button animation:

void cDWindow::Ready()
{
   SetBeatRate(kBeatRate);
   cWindow::Ready();
}

The constant kBeatRate is defined as const tUint32 kBeatRate = 150 and is expressed in milliseconds.

When the user asks to close the window, the method CloseRequested() will be executed. If it returns true, the window will be closed, else it will remain open. This is the perfect time for us to save the current position of the window on screen, for future usage:

tBool cDWindow::CloseRequested()
{
   // Store current position of the window in the settings
   uPoint pos = GetPosition();
   iSettings->SetData("pos",eDataBuffer,(char *)&pos,sizeof(uPoint));
   return true;
}

Here's the header and the C++ file of cDWindow.

cDApplication

We are now going to derive the application class to create and display the window. We will also be retreiving the application settings. Here's the application declaration from DApplication.h:

class cDApplication : public cApplication
{
   public:
      cDApplication();
      ~cDApplication();

   protected:	
      void Ready();

   private:
      cDWindow*   iWindow;
      cSettings*  iSettings;
};

The method Ready() is called once the application starts to run. We will use this method to construct and display the window. First, let's see the class constructor:

cDApplication::cDApplication()
   : cApplication("hexaZen/Custom",0,0)
{
   iWindow = NULL;

   if(!iFault)
   {
      // Create the object to read/write application settings
      iSettings = new cSettings("Custom");

      if(!iSettings)
         iFault = kErrOutOfMemory;
      else
         iFault = iSettings->GetFault();
   }
   else
      iSettings = NULL;
}

The first argument of the base class constructor is the name of the application. This name will be used to identify the application if we wanted to send some scripting messages to it. We will come back to that later. The class cSettings from which we instantiate iSettings provides access to the application settings. If something is wrong with it, or if the object cannot be created, the data member iFault of the application will be set to something other than kErrNone.

If you recall the destructor of cDWindow, you may remember that we didn't delete the console widget in it, since the window took care of that. The application class, does not take care of deleting the window for us, so we will need to delete iWindow in it:

cDApplication::~cDApplication()
{
   if(iSettings)
   {
      // save settings
      iSettings->Save();
      delete iSettings;
   }

   // delete the window
   if(iWindow)
      delete iWindow;
}

Calling the method Save() on the cSettings object will save the last position of the window in the application settings file. Storing data in it, like we did in the method CloseRequested(), does not automatically save the changes to a file.

Before we look into the method Ready(), which is quiet interesting, we first need to talk a bit about how the skin shared object will be loaded and then passed down to the window. When the application is launched, the skin to be used will be passed in the command line argument. We will be using the switch -s for that. The static class sArgs provided by the SDK allows for an easy parsing of the command line. It will be used in the main of the application. Since it is static, we can use it in the Ready() method to get the path of the skin.

Now, we can start looking at Ready():

void cDApplication::Ready()
{
   // load settings
   if(iSettings)
      iSettings->Load();

Although we have had iSettings since the constructor, we have not loaded the settings file until now. If it is the very first time the application is started, the settings file will not exist. The method Load() will fail, but it doesn't really matter since we will create the file when we save the settings in the application destructor. Now, we are going to use the method sArgs::Presents() to test if the switch was present on the command line:

   if(sArgs::Presents("s"))
   {
      // load the skin
      cDSkin lSkin(sArgs::Value("s"));

Using the class cDSkin presented earlier, we have loaded the shared object containing the skin from the path name retrieved from the value associated with the switch -s. However, it is only after testing the validity of lSkin, will we know if the skin object was loaded correctly:

      if(lSkin.IsValid())
      {

If the skin was loaded properly, we can go ahead and create the window. Since the window creation can throw an exception, we will be putting it in a try...catch block:

	 // create & show window
         try
         {
            sCleanupStack::WindL();

Calling the method WindL() is mandatory in the beginning of the block. Its purpose is to prepare the cleanup stack. Note that this method can leave if something is not right. However, since we are already in the try...catch block, it's okay. The creation of the window is no surprise; we use the method NewL(). If there is no exception, the window is ready to go and we simply show it on screen:

            iWindow = cDWindow::NewL(iSettings,lSkin);
            iWindow->Show();

The last statement inside the try block, must be a call to the method UnWind(). This will un-prepare the cleanup stack and all the objects still present on the stack will be destroyed:

            sCleanupStack::UnWind();
         } catch (uException &lException) {

If an exception is raised in the try block, we will be displaying the exception information and request the application to terminate:

            sEnv::PrintToStream("The exception \"%s\" was raised in %s\n",
                                sEnv::ErrorToString(lException.iWhy),lException.iWhere);
            Quit();

Like in the try block, we must end the catch block by a call to UnWind(). This will get all the objects on the cleanup stack to be destroyed, which is afterall the whole point of the idiom:

            sCleanupStack::UnWind();
         }
      }
      else
      {
         sEnv::PrintToStream("Invalid skin\n");
         Quit();
      }
   }
   else	
   {
      sEnv::PrintToStream("No skin given\n");
      Quit();
   }

Last but not least, we will call the base class Ready() method as required by the framework:

   cApplication::Ready();
}

Here's the header and the C++ file of cDApplication.

Main

Finally, we are going to look at the contents of the main. We need to parse the command line arguments, then create and start the application:

int main(tInt32 aArgc,const tChar **aArgv)
{
   tErr           lErr;
   cDApplication  lApp;

   if(!(lErr = lApp.GetFault()))
   {
      // if there is args on the command line,
      // extract them and give them to the app
      if(aArgc>1)
      {
         cCan lCan(0);
	
         if(!cCan::Verify(&lCan))
         {
            sArgs::SetCan(&lCan);	
            sArgs::Extract(aArgc,aArgv);

	    // Start the app
            if(!(lErr = lApp.Run()))
	       // and wait for it to end
               lApp.WaitEnd();
            else
               sEnv::PrintToStream("The application failed to run : %s\n",
                                   sEnv::ErrorToString(lErr));
         }
         else
            sEnv::PrintToStream("The application failed to run : %s\n",
                                sEnv::ErrorToString(kErrOutOfMemory));
      }
      else
         sEnv::PrintToStream("No skin specified.\n");
   }
   else
      sEnv::PrintToStream("The application construction failed : %s\n",
                          sEnv::ErrorToString(lErr));
}

Once the application object is created, we parse the command line arguments then ask the application to run. Once this is done, we just wait for it to end using the WaitEnd() method. The class sArgs requires a cCan object in order to parse the command line. We create one on the stack.

Throughout this article, we have been using the static class sEnv a couple of times. This is an handy static class which provides access to various useful methods, such as PrintToStream(), LeaveIfError() or ErrorToString(). The latter method transforms an error code into a human readable string.

Here's the C++ file for the main.

Now that our test application is complete, we can try it out with an alternate skin:

#> ./custom -s ./Skins/Orange/Orange.so

Here's an animated GIF assembled from screenshots:

Table of contents
  1. "Zinzala, Page 1/11"
  2. "Zinzala, Page 2/11"
  3. "Zinzala, Page 3/11"
  4. "Zinzala, Page 4/11"
  5. "Zinzala, Page 5/11"
  6. "Zinzala, Page 6/11"
  7. "Zinzala, Page 7/11"
  8. "Zinzala, Page 8/11"
  9. "Zinzala, Page 9/11"
  10. "Zinzala, Page 10/11"
  11. "Zinzala, Page 11/11"
e p (0)    12 Comment(s)

Technology White Papers

See More