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

"Zinzala, Page 6/11"
3. The cDConsoleView class

To build the multimedia console widget, we are going to derive the widget cDrawView. This class defines and implements a very basic type of widget, making it the perfect starting point for building a custom widget. It provides various drawing methods that we will be using to render the console contents.

Here's the declaration of cDConsoleView:

class cDConsoleView : public cDrawView
{
   public:
      static cDConsoleView *NewL(cDSkin &aSkin);
      virtual ~cDConsoleView();

      void SetState(tConsoleState aState);
      tConsoleState GetState() const {return iState;};
	
   protected:
      virtual void PlayPressed(tBool aActive) 	{};
      virtual void PausePressed(tBool aActive) 	{};
      virtual void StopPressed(tBool aActive) 	{};
      virtual void FwdPressed(tBool aActive) 	{};
      virtual void BwdPressed(tBool aActive) 	{};

   protected:
      void OnScreen();
      void Beat();
	
      void MouseUp(uPoint aPoint);
      void MouseDown(uPoint aPoint);
      void OutBound(uPoint aPoint);

   protected:
      cDConsoleView();
      virtual void ConstructL(cDSkin &aSkin);

   private:
      tUint8 FindButton(uPoint aPoint) const;
      void Render(tUint8 aButton,tBool aBackground = false);
      void Pressed(tUint8 aButton);
      void Invoked();

   private:
      typedef struct tButton; 

   private:
      tConsoleState  iState;           // Console state
      cBitmap*       iBackground;      // Background bitmap
      cBitmap**      iBStates;         // Button's states bitmaps
      cBitmap**      iBLabelsN;        // Button's labels bitmaps (non dimmed)
      cBitmap**      iBLabelsD;        // Button's labels bitmaps (dimmed)
      tButton*       iButtons;         // Button's data
      tUint8         iPressedButton;   // ID of the button currently pressed
      tUint8         iActiveButton;    // ID of the button currently active
      tUint8         iPrevState;       // Previous state of the current pressed button
      tInt8          iActiveDir;       // Direction of the Active button animation
};

The console defines five callback methods to be implemented by derived classes. PlayPressed() will respond to a user action on the Play button, PausePressed() on the Pause button, and so on. The console implements how to handle user actions in the methods MouseDown(), MouseUp() and OutBound().

The state of the console can be changed at anytime with the method SetState(). The console itself will not change its state, however; the state will be used when rendering the console. Here's the definition of tConsoleState:

enum tConsoleState {
   ePlaying,
   eStopped,
   ePaused,
   eForward,
   eBackward
};

Construction & Destruction

Implementing a two-phase construction is not difficult. However, it does require a bit more coding than just a standard C++ constructor. The following methods are involved in the construction of an object: cDConsoleView(), ConstructL() and NewL(). NewL() is a factory function, the frontend of the creation of an object. Instead of using the new C++ operator, we will call this method to instantiate a new object. To inforce that NewL() is the only possibilty for object instantiation, we have set the constructor as a protected method.

Let's see the code of the C++ constructor cDConsoleView():

cDConsoleView::cDConsoleView()
   : cDrawView(uRect(kConsoleWidth,kConsoleHeight),"console",kFollowNone,
               kFlgBeatNeeded | kFlgInteractive | kFlgOffscreen)
{
   iBackground    = NULL;
   iBStates       = NULL;
   iBLabelsN      = NULL;
   iBLabelsD      = NULL;
   iButtons       = NULL;
   iPressedButton = kButtonsCount;
   iActiveButton  = kButtonsCount;
   iActiveDir     = 1;
   iState         = eStopped;
	
   if(!iFault)
   {
      iButtons = new tButton[kButtonsCount];
      if(!iButtons)
         iFault = kErrOutOfMemory;
      else
      {
         iBStates = new (cBitmap *)[kStatesCount];
         if(!iBStates)
            iFault = kErrOutOfMemory;
         else
         {
            memset(iBStates,0,sizeof(cBitmap *) * kStatesCount);
            iBLabelsN = new (cBitmap *)[kLabelsCount];
            if(!iBLabelsN)
               iFault = kErrOutOfMemory;
            else
            {
               memset(iBLabelsN,0,sizeof(cBitmap *) * kLabelsCount);
               iBLabelsD = new (cBitmap *)[kLabelsCount];
               if(!iBLabelsD)
                  iFault = kErrOutOfMemory;
               else
                  memset(iBLabelsD,0,sizeof(cBitmap *) * kLabelsCount);
            }
         }
      }
   }
}

Using the two-phase construction idiom doesn't mean that no memory allocation should ever be done in the C++ constructor. But if we choose to do so, we must make sure that it can still be deleted if the construction fails and that the partially constructed object is destroyed.

After some initialization, we try to allocate the 4 arrays that will contain all the buttons data (iButtons) and the various corresponding bitmaps (iBStates, iBLabelsN, iBLabelsD). If any of the memory allocation fails, we will mark the object as faulty by setting the data member iFault to kErrOutOfMemory. This member variable is inherited from deep inside the Zinzala SDK (class pBase from the Cincinella Kit). Because the constructor of the base class cDrawView could lead to an invalid object, it is recommended that you test whether or not iFault already indicates a fault. There is no point in allocating more memory if we going to delete the object right away because it is faulty.

The flags passed to the base class cDrawView sets a couple of behaviors for the widget. kFlgBeatNeeded indicates that the widget will make use of the window beat. At a given interval, the method Beat() of the widget will be called. We need this flag to render the animation of the active button. kFlgInteractive specifies that the widget will accept user inputs. The flag kFlgOffscreen is specific to the widget cDrawView, and indicates that we will be using an offscreen bitmap to render the widget in order to avoid any flickering. For instance, when drawing the animation.

Here's the definition of the various constants we have used in the constructor:

const tUint16 kConsoleWidth   = 321;
const tUint16 kConsoleHeight  =  98;
const tUint8  kButtonsCount   =   4;
const tUint8  kStatesCount    =   7;
const tUint8  kLabelsCount    =   5;

We also need to define tButton whose implementation is hidden from the class users:

typedef struct cDConsoleView::tButton 
{
   uRect    iBFrame; // frame of the button
   uRect    iLFrame; // frame of the label
   tUint8   iState;  // button state
   tUint8   iLabel;  // label displayed on the button
	
} tButton;

This type holds all the button specific information, such as its position within the console or its state.

The method ConstructL() contains the rest of the widget's construction where exceptions can be raised. Here's its implementation:

void cDConsoleView::ConstructL(cDSkin &aSkin)
{
   uPoint lPoint;

   // If the view is already faulty, we will leave right away
   sEnv::LeaveIfError(iFault);

Because this method will be called right after the class constructor is called, the widget might already be faulty. If this is the case, there is no need to proceed, we will leave right away.

   // Instantiate the background bitmap
   iBackground             = CreateBitmapFromResourceL(aSkin.GetResourceL(eResBackground),false);

   // Instantiate the button bitmaps
   iBStates[kStateNormal]  = CreateBitmapFromResourceL(aSkin.GetResourceL(eResNormal));
   iBStates[kStateDimmed]  = CreateBitmapFromResourceL(aSkin.GetResourceL(eResDimmed));
   iBStates[kStatePressed] = CreateBitmapFromResourceL(aSkin.GetResourceL(eResPressed));
   iBStates[kStateActive]  = iBStates[kStateNormal]; // use same bitmap as the normal state
   iBStates[kStateActive1] = CreateBitmapFromResourceL(aSkin.GetResourceL(eResAnimate1));
   iBStates[kStateActive2] = CreateBitmapFromResourceL(aSkin.GetResourceL(eResAnimate2));
   iBStates[kStateActive3] = CreateBitmapFromResourceL(aSkin.GetResourceL(eResAnimate3));
   // Instantiate the label bitmaps
   iBLabelsN[kLabelBwd]    = CreateBitmapFromResourceL(aSkin.GetResourceL(eResBackwardN));
   iBLabelsN[kLabelFwd]    = CreateBitmapFromResourceL(aSkin.GetResourceL(eResForwardN));
   iBLabelsN[kLabelStop]   = CreateBitmapFromResourceL(aSkin.GetResourceL(eResStopN));
   iBLabelsN[kLabelPlay]   = CreateBitmapFromResourceL(aSkin.GetResourceL(eResPlayN));
   iBLabelsN[kLabelPause]  = CreateBitmapFromResourceL(aSkin.GetResourceL(eResPauseN));
   iBLabelsD[kLabelBwd]    = CreateBitmapFromResourceL(aSkin.GetResourceL(eResBackwardD));
   iBLabelsD[kLabelFwd]    = CreateBitmapFromResourceL(aSkin.GetResourceL(eResForwardD));
   iBLabelsD[kLabelStop]   = CreateBitmapFromResourceL(aSkin.GetResourceL(eResStopD));
   iBLabelsD[kLabelPlay]   = CreateBitmapFromResourceL(aSkin.GetResourceL(eResPlayD));
   iBLabelsD[kLabelPause]  = CreateBitmapFromResourceL(aSkin.GetResourceL(eResPauseD));

The function CreateBitmapFromResourceL() that we will see later, instantiates a cBitmap from the resource contained in the skin. If something goes wrong, it will leave.

   tUint16 lX = kPositionX;

   // initialise the buttons data (all are dimmed)
   for(tUint8 i=0;i<kButtonsCount;i++)
   {
      iButtons[i].iState = kStateDimmed;
      iButtons[i].iLabel = i;
      iButtons[i].iBFrame.Set(lX,kPositionY,lX + kButtonWidth,kPositionY + kButtonHeight);
      lPoint.Set(lX + kButtonWidth / 2,kPositionY + kButtonHeight / 2);
      lPoint.iX -= kLabelWidth / 2;
      lPoint.iY -= kLabelHeight / 2;
      iButtons[i].iLFrame.Set(lPoint.iX,lPoint.iY,lPoint.iX+kLabelWidth,lPoint.iY+kLabelHeight);
      lX += kButtonWidth + kSpacing;
   }

Beside initializing the button's data, we compute its position within the console and its label's position, which is always centered within the button.

   // The play button is not dimmed by default
   iButtons[kButtonPlay].iState = kStateNormal;
}

Here's the definition of the various constants we have used in ConstructL():

const tUint16 kButtonWidth    = 64;
const tUint16 kButtonHeight   = 64;
const tUint16 kLabelWidth     = 25;
const tUint16 kLabelHeight    = 25;
const tUint16 kPositionX      = 18;
const tUint16 kPositionY      = 17;
const tUint16 kSpacing        = 10;

const tUint8  kButtonBwd      =  0;
const tUint8  kButtonPlay     =  1;
const tUint8  kButtonPause    =  2;
const tUint8  kButtonFwd      =  3;

const tUint8  kStateDimmed    =  0;
const tUint8  kStatePressed   =  1;
const tUint8  kStateNormal    =  2;
const tUint8  kStateActive    =  3;
const tUint8  kStateActive1   =  4;
const tUint8  kStateActive2   =  5;
const tUint8  kStateActive3   =  6;

const tUint8  kLabelBwd       =  0;
const tUint8  kLabelPlay      =  1;
const tUint8  kLabelPause     =  2;
const tUint8  kLabelFwd       =  3;
const tUint8  kLabelStop      =  4;

In order to instantiate the class cDConsoleView, we now need to implement the method NewL(). It is simple and short:

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

As you can see, we first construct a new object using the standard C++ operator new. Then we use the method VerifyLC(), inherited from the base class pBase, to verify that the object is valid. The LC at the end of the method's name indicates that it can leave. If it doesn't leave, the object in lView will be left on the cleanup stack. We need the object to be on the stack before we call the ConstructL() method on the object. Since this method can leave, we need to make sure that it will be deleted automatically when the exception is handled. If there is no problem in the construction of the object, we will pop the object from the stack then return it.

We already saw the method cSkin::GetResourceL() earlier, so here's CreateBitmapFromResourceL():

cBitmap *CreateBitmapFromResourceL(const tBitmapResource &aRes,tBool aTransparent = true)
{
   tErr     lErr;
   cBitmap *lBitmap = NULL;

   lBitmap  = new cBitmap(aRes.iWidth,aRes.iHeight,eSpaceColor8Bit,true,aRes.iColors);
   lErr     = cBitmap::VerifyD(lBitmap);
	
   if(!lErr)
   {	
      lBitmap->SetPalette(aRes.iPalette);
      lBitmap->SetBits(aRes.iBits,aRes.iLength,0);
      if(aTransparent)
         lBitmap->MakeTransparent(aRes.iIndex);
      return lBitmap;
   }
   else
      throw uException(lErr,"CreateBitmapFromResourceL()");
}

Its implementation is quite straightforward. A bitmap is created with the size and number of colors required. Then we use VerifyD() to test the validity of the bitmap. If the bitmap is valid, we will set the palette of colors and copy its pixel data. If invalid, the object will be deleted by VerifyD(). Now, if the bitmap shall be transparent, we will apply the transparency. If the bitmap cannot be created succesfully, due to a lack of memory or some other reason, the method will throw an exception.

In the class destructor, we need to delete all the memory that was allocated (if any). Here is the implementation:

cDConsoleView::~cDConsoleView()
{
   if(iButtons)
      delete [] iButtons;

   if(iBackground)
      delete iBackground;
	
   if(iBStates)
   {
      for(tUint8 i=0;i<kStatesCount;i++)
         if(iBStates[i] && i!=kStateActive) // Active and Normal states share the same bitmap
            delete iBStates[i];
	
      delete [] iBStates;
   }

   if(iBLabelsN)
   {
      for(tUint8 i=0;i<kLabelsCount;i++)
         if(iBLabelsN[i])
            delete iBLabelsN[i];
	
   delete [] iBLabelsN;
   }

   if(iBLabelsD)
   {
      for(tUint8 i=0;i<kLabelsCount;i++)
         if(iBLabelsD[i])
            delete iBLabelsD[i];

      delete [] iBLabelsD;
   }
}

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