Control Stack Administrator for Symbian Series 60

My application simulates the dialing pane on a Series60 phone. I wanted to demonstrate a feature, which would become evident when you dial a number.

My main control is a rectangle covering the client rectangle, leaving the command buttons, and the context, navi, and title panes visible. The title pane is set to the name of the network, or "Searching". The navi pane shows the time, and the control draws the current country, local time, and home county time if roaming.

Root Screen
Number Entry

Two pop-up controls are in smaller rectangles, only partially obscuring the root control. I have 7 lists: phone book, test book, test cases, countries, dial log, dial variations.

As the users does things i.e. pressing buttons, the various lists, and other controls are created, and take over the keypad.

However this also affects the title pane, for example the name of a list is shown in the title pane when that control is on top.

Also the controls define the cba, and menu. In the early stages all controls went back to the root screen, so when they were created their cba, menu, and title were setup, and these were restored to the root screen values when the control was destroyed.

However I started to have a stack of controls when one list went back to its parent list, or a pop-up explanation control went back to a list. My back implementation became a nightmare, in finding out which control was on top, and which control was the new top.

So I wrote a control stack administrator. You create a control, and pass it to the administrator as the new control stack top.

Country List

As well as passing the control, you pass seven parameters: integers which define the title, navi, and context pane, and the cba, menu. There is also a flag to indicate if this control lodges in the root control window, or has its own window. And the seventh parameter is application defined.

The control stack administrator distributes key presses to the control. AddToStackL(), and RemoveFomStackL() are replaced by the administrator's AddToStackL(); and GoBackL(); You destroy the top of the stack, by calling GoBackL(). The new stack top, which may or not be the root control defines the cba, menu, title etc, and gets first try at handling key presses.

You can return to the root screen with GoBackL( 0 ), which will completely unwind the stack to the root screen. BackAddToStackL() allows you to combine both operations.

The old implementation of going back was very convoluted.

Scroll down to see the implementation using the Control Stack Administrator.

void CDialGenieController::GoBackL()
  {
  TBool tzList = FALSE;

  if ( iSubList )
    {
    iParentAppUi->RemoveFromStack( iSubList );
    delete iSubList;
    iSubList = NULL;

    RedrawTitlePaneL();
    }
  else if ( iHyena )
    {
    iParentAppUi->RemoveFromStack( iHyena );
    delete iHyena;
    iHyena = NULL;
    }
  else if ( iList )
    {
    tzList = iList->TimeZoneList();
    if ( tzList )
      iTzAsked = TRUE;

    iParentAppUi->RemoveFromStack( iList );
    delete iList;
    iList = NULL;
    }
  else if ( iNumberEntry )
    {
    iParentAppUi->RemoveFromStack( iNumberEntry );
    delete iNumberEntry;
    iNumberEntry = NULL;
    }
  else if (iDialScreen)
    {
    iParentAppUi->RemoveFromStack( iDialScreen );
    delete iDialScreen;
    iDialScreen = NULL;
    }
  else
    ASSERT( FALSE );
          
          
CDialGenieController::~CDialGenieController()
  {
  delete iStack;
  delete iGenie;
  delete iMinuteTick;
  delete iNaviDecorator;
  delete iDebug;
  }
          
          
My controller's new destructor above is a lot simpler than the original below
          
CDialGenieController::~CDialGenieController()
  {
  if ( iNumberEntry )
    {
    iParentAppUi->RemoveFromStack( iNumberEntry );
    delete iNumberEntry;
    }
  if (iSubList)
    {
    iParentAppUi->RemoveFromStack( iSubList );
    delete iSubList;
    }
  if (iList)
    {
    iParentAppUi->RemoveFromStack( iList );
    delete iList;
    }
  if (iDialScreen)
    {
    iParentAppUi->RemoveFromStack( iDialScreen );
    delete iDialScreen;
    }
  if (iHyena)
    {
    iParentAppUi->RemoveFromStack( iHyena );
    delete iHyena;
    }
  iParentAppUi->RemoveFromStack( iScreen );
  delete iScreen;

  delete iNumber;
  delete iGenie;
  delete iMinuteTick;
  delete iNaviDecorator;
  delete iDebug;
  }
          

In particular setting the cba, title, and menu is very awkward

  CEikButtonGroupContainer* cba = iAvkonAppUi->Cba();

  if (iList)
    {
    HBufC* titleText = StringLoader::LoadLC( R_DIAL_GENIE_TEXTM_REDIALER );
    iTitlePane->SetTextL( *titleText );
    CleanupStack::PopAndDestroy( titleText );  

    iMenuBar->SetMenuTitleResourceId( R_DIALGENIE_DIAL_LOG_MENUBAR );

    cba->SetCommandSetL(R_AVKON_SOFTKEYS_OPTIONS_BACK);
    cba->DrawDeferred();
    }
  else
    {
    cba->SetCommandSetL(R_DIALGENIE_SCREEN_CBA);
    cba->DrawDeferred();

    iMenuBar->SetMenuTitleResourceId(R_DIALGENIE_SCREEN_MENUBAR);
    if ( !iTzAsked && !tzList && iGenie->iUIState == CDialGenie::UIStateIdle )
      ListTimeZonesL();
    }
  RedrawTitlePaneL();
  }
          

In comparison the new back implementation is very neat

  case EDialGenieCmdBack:
  case EAknSoftkeyBack:
    if (iStack->iStackTop->iId == EObjectIdListTimeZone)
      iTzAsked = TRUE;
    iStack->GoBackL();
    AttemptListTimeZonesL();
    break;
          

The controller class inherits from CControlStackAdmin::MClient, and implements the pure virtual StackTopL(). This will be called whenever the stack top changes, either going up or down, and it is used to define the cba, menu, and other items as needed.

Back() allows you to alter the stack without calling NewStackTopL(), but it should be followed by another call to GoBackL() or AddToStackL(), unless it is part of the destructor in which case Back(-1) also destroys the root screen.

Note GoBackL(-1) is illegal, but BackAddToStackl( -1, …) is allowed and lets you replace the root screen; never used in my case.

CountComponentControls(), and ComponentControls() are implemented in CControlStackAdmin and advise which window lodger controls are lodging in the root control's window.

Note changing your controls from window owning to window lodging affects their rectangles. Make sure you know how to modify the rectangle of lodger controls.

The control stack administrator acts as a limited cleanup stack. A control can be created and passed to it, and it takes responsibility for deleting the object thereafter. While NewStackTopL() can leave, it will ensure the object passed to it is deleted in its destructor.

There are three ways of invoking AddToStackL():

If you have a new NewL() function this is simplest:

  iStack->AddToStackL( CMyClass::NewL(), id, lodger, cba, menu, title, navi, context );  
          

If NewL is not implemented and ConstructL() must be called before NewStackTopL(). This could be the case if NewStackTopL() does something that requires this instance of CMyClass to be fully constructed.

  iStack->Push( new (ELeave)CMyClass );
  ((CMyClass *)iStack->iPushedObject)->ConstructL();
  iStack->AddToStackL( id, lodger, cba, menu, title, navi, context );
          

If NewL is not implemented but ConstructL can safely be called; after the call to NewStackTopL() in the client that results from the call to AddToStackL()

  iStack->AddToStackL( new( ELeave)CMyClass, id, lodger, cba, menu, title, navi, context );
  ((CMyClass *)iStack->iStackTop->iObject)->ConstructL();
          

In my implementation of NewStackTopL() positive values for cba, title etc represent resources, and negative values are handled by logic in the controller. Controls that don't have options in their cba don't need a menu hence iMenu might be zero. Also in my application the context and navi panes are independent of the stack.

  void CDialGenieController::StackTopL( CControlStackAdmin::SElement *aNewTop )
    {
    iCba->SetCommandSetL( aNewTop->iCba );
    iCba->DrawDeferred();
  
    if (aNewTop->iMenu > 0)
      iMenuBar->SetMenuTitleResourceId( aNewTop->iMenu );
  
    RedrawTitlePaneL();
  // This theoretically only needs to be performed after going back. Might extend 
  // control stack admin to support that idea.
    iStack->iStackBottom->iObject->DrawDeferred();
    }
          

This procedure interprets two negative values for the title pane, or if its positive, loads the resource text. It might be called before the stack is created, hence the checks.

  void CDialGenieController::RedrawTitlePaneL()
    {
    if (iStack)
      if (iStack->iLevel >= 0)
        {
        if ( iStack->iStackTop->iTitle == -1 )
          if ( iGenie->iUIState == CDialGenie::UIStateSearching )
            iTitlePane->SetTextL( KTextSearching );
          else
            iTitlePane->SetTextL( iGenie->iCurrentNetwork->Des() );
        else if ( iStack->iStackTop->iTitle == -2 )
          iTitlePane->SetTextL( iTzListHeader );
        else
          {
          HBufC* titleText = StringLoader::LoadLC( iStack->iStackTop->iTitle );
          iTitlePane->SetTextL( *titleText );
          CleanupStack::PopAndDestroy( titleText );
          }
        } // if (iStack->iLevel >= 0)
    }