MobileJava MobileBrew & J2ME: Let's Be Friends! Part 2

Brew & J2ME: Let’s Be Friends! Part 2

This is the second article in a series targeting mobile developers more accustomed to J2ME than BREW or BREW developers interested in lighter and more efficient code production. The last time[1], we presented a basic framework largely inspired by a Java GUI model easing the task of writing high-level interface code in BREW. For this installment, the framework was extended and refactored to properly handle real-life cases.

A basic example of multi-control screens allows us a detailed explanation of the inner mechanisms of the framework.

An Example

We start by directly presenting an example of a two-screens application:

class BJApp : public IMidlet
{
  typedef ListImpl<BJApp> List;
  typedef DateCtlImpl<BJApp> DateCtl;
  typedef List Command;
private:
  void BuildDateCtl(int year)
  {
    DateCtl* l = 
       (DateCtl*) getRegistered("DATE_CTL");
    if (!l)
    {
      l = DateCtl::createDateCtl("DATE_CTL", 
                                 this);
      if (!l) return ;
      Command* c = BuildDateCtlCommand(1);
      if (!c) return ;
      l->setScreen(8,20,110,60);
      l->installEventHandler(dateCtlHdl);
      l->installEventHandler(dateCtlHdl, 
                             TAB);
      l->setTitle("Select a Date");
      l->addDisplayable(c);
      l->addDisplayable(l);
    }
    l->setDate(year,12,31);
    l->setCurrent();
  }
  bool dateCtlHdl( DateCtl* l) 
  {
    l->setCurrentAt(0);
    return true;
  }
  Command* BuildDateCtlCommand(int ix)
  {
    Command* c = 
      (Command*) getRegistered("DATE_CTL_COMMAND");
    if (c)
      return c;
    c = Command::createCommand("DATE_CTL_COMMAND", 
                               this);
    if (!c) return 0;
    c->installEventHandler(dateCtlCmdHdl);
    c->installEventHandler(dateCtlCmdTabHdl,
                              TAB);
    WString a[] = {"Nowhere","Back"};
    c->append(a,SIZE_OF(a));
    c->setSelection(ix);
    return c;
  }
  bool dateCtlCmdTabHdl( Command* l)
  {
    setActive("DATE_CTL");
    return true;
  }
  bool dateCtlCmdHdl( Command* l)
  {
    int pos = l->getSelection();
    if (pos == INDEX_OUT_OF_BOUNDS)
      return false;
    if (pos == 1)
    {
      Command* c = 
        (Command*)getRegistered("DATE_COMMAND");
      if (!c) return false;
      c->setSelection(0);
      BuildInputList(0, "INPUT_LIST_0");
    }
    return true;
  }
  void BuildInputList(int focus, 
                      const String& lst)
  {
    List* l = (List*) getRegistered(lst);
    if (!l)
    {
      l = List::createList(lst, this);
      if (!l) return;
      Command* c = BuildDateCommand(0);
      if (!c) return;
      l->installEventHandler(inputListHdl);
      l->installEventHandler(inputListTabHdl,
                             TAB);
      l->setTitle("Start");
      l->setFullScreen(120);
      l->addDisplayable(c);
      l->addDisplayable(l);
    }
    l->setCurrentAt(focus);
  }
  bool inputListHdl( List* l)
  {
    int pos = l->getSelection();
    if (pos == INDEX_OUT_OF_BOUNDS)
      return false;
    Command* c = 
      (Command*)getRegistered("DATE_COMMAND");
    if (!c)
      return false;
    if (c->getSelection() == 0)
      BuildDateCtl(2003);
    else if (c->getSelection() == 1)
      BuildDateCtl(2004);
    else
      BuildDateCtl(9999);
    return true;
  }
  bool inputListTabHdl( List* l)
  {
    l->setCurrentAt(0);
    return true;
  }
  Command* BuildDateCommand(int ix)
  {
    Command* c = 
       (Command*) getRegistered("DATE_COMMAND");
    if (c)
      return c;
    c = Command::createCommand("DATE_COMMAND", 
                               this);
    if (!c) return 0;
    c->installEventHandler(dateCmdTabHdl);
    c->installEventHandler(dateCmdTabHdl,
                           TAB);
    WString a[] = {"2003","2004" };
    c->append(a,SIZE_OF(a));
    c->setSelection(ix);
    return c;
  }
  bool dateCmdTabHdl( Command* l)
  {
    int pos = l->getSelection();
    return dateManipulation(pos);
  }
  bool dateManipulation(int pos)
  {
    switch (pos)
    {
      case 0:
      {
        String s("INPUT_LIST_0");
        List* k = (List*) getRegistered(s);
        if (k && !k->size())
        {
          WString a[] = {"Conference","DevDay"};
          k->append(a,SIZE_OF(a));
        }
        BuildInputList(1,s);
        break;
      }
      case 1:
      {
        String s("INPUT_LIST_1");
        BuildInputList(1,s);
        List* k = (List*) getRegistered(s);
        if (k && !k->size())
        {
          WString a[] = {"Training","Vacation"};
          k->append(a,SIZE_OF(a));
        }
        BuildInputList(1,s);
        break;
      }
      default:
        return false;
    }
    return true;
  }
public:
  virtual bool onStart()
  {
    setColor(CLR_SYS_ITEM_SEL , 0,0, 255);
    setColor(CLR_USER_TEXT , 0,0, 255);
    BuildInputList(0, "INPUT_LIST_0");
    return true;
  }
};

All the widgets in this example register for EVT_CTL_TAB and EVT_COMMAND events and behave like singletons registered and retrieved by name. All the UI logic is conveniently implemented inside the event handlers.

The application starts in onStart() by creating a List (INPUT_LIST_0) and a Command (DATE_COMMAND).

INPUT_LIST_0 hosts both widgets. EVT_CTL_TAB switches the focus and EVT_COMMAND implements some date-related logic and switches the screen to DATE_CTL via BuildDateCtl.

DATE_COMMAND deals both events via the same handler (dateCmdTabHdl). Based on selection, a new List can be created (INPUT_LIST_1) associated with DATE_COMMAND and given focus, or the focus can be moved back to INPUT_LIST_0.

BuildDateCtl creates an association between DATE_CTL and DATE_CTL_COMMAND with simple UI logic—the “back” command brings the app back to the INPUT_LIST_0 state.

And Some Explanation…

Our application deals with three GUI widgets: List, Command, and DateCtl. Actually, List and Command are variations of the same basic type—ListImpl—but serve different purposes. All applications inherit from IMidlet and can override onStart(), onStop(), onResume(), and onSuspend() callbacks. IMidlet now exhibits a naïve setColor() functionality.

There is a new mechanism used to implement widget persistency. onStart() creates a list called “INPUT_LIST_0” inside BuildInputList. Widgets now can be retrieved by name (in the previous implementation it was possible by index only), eliminating the need of declaring them explicitly at the class scope:

List* l = (List*) getRegistered(lst);

A widget is always created by using the “Named Constructor Idiom”[2], using either createXXX(const String& name, T* t) or createXXX(T* t). The first form allows later retrieval by name and was used extensively in the example. Please note the change in method names: createXXX() now replaces getXXX().

Event handling was generalized. There is a new method replacing setCbk()—void installEventHandler(F f, LIST_EVT evt). Basically, this method registers one callback per event, as defined in the LIST_EVT definition:

typedef  enum  {TAB,CMD, EVT_SIZE} LIST_EVT;
// EVT_SIZE is a guard value
//currently only CMD & TAB supported
//CMD = EVT_COMMAND
//TAB = EVT_CTL_TAB

f is an application method.

Implementation detail: Event handling was packaged as a stand-alone service in

template <class T, class F>
class EvtHandler;

A Command (DATE_COMMAND) is created in BuildDateCommand(). A Command is a soft key wrapper and, from a BREW perspective, is a minor specialization of List.

And now comes the most interesting part: assembling widgets into richer displays. Our UI hierarchy slightly resembles J2ME—there are Displayables on top, Screens inheriting from Displayables (this is a new addition to the hierarchy), and finally Commands, Lists, and other widgets inheriting from Screens. From a BREW point of view, an IDisplayable is equivalent to an IControl. Screen adds the container capability—every Screen is capable of hosting any other Controls (IDisplayable), including itself.

class Screen: public IDisplayable
{
public:
  void addDisplayable(IDisplayable* d);
  void setDisplayableAt(IDisplayable* d, UINT pos);
  void setCurrent(bool isRedrawing = true);
  void setCurrentAt(UINT mainDisplayable, bool isRedrawing = true);
  virtual ~Screen()
  {}
protected:
  Screen(IMidlet* m):IDisplayable(m->getDisplayableRegistry()),
                                  m_(m)
  {}
private:
  void setCurrentImpl(IDisplayable* displayable, 
                      bool isRedrawing );
  void redrawDisplayables();
private:
  DISPLAYABLES displayables_;
  IMidlet* m_;
};

Widgets are implemented on top of Screens, making very flexible arrangements possible. For example, a Command can host a List or the other way around. The following code better explains the concept:

List*    l = List::createList("lst", this);
Command* c = Command::createCommand("cmd", this);
  l->addDisplayable(c);
  l->addDisplayable(l);
  l->setCurrentAt(0);

List l hosts both a Command c and itself (l) and at the very end everything is rendered on the screen and c is made active (setCurrentAt(0) ). setCurrentAt(index) uses a zero-based index notation, but a string (name)-based implementation is equally possible—with a slight performance penalty. A shortcut (setCurrent()) is available—setting the focus on the container directly.

As mentioned in the previous article, indexes are auto generated, considerably simplifying items’ manipulation. Manipulation of indexes was packaged in a specialized class—Indexes—used by all interested parties (mainly widgets):

class Indexes
{
public:
  void append(int id);
  int size() const;
  template <class P>
  int getIDImpl(int ix);
private:
  BrewVector<int> indxs_;
};

Another useful mechanism was added in IMidlet. As mentioned before, setCurrent() and setCurrentAt() have dual functionality: They both redraw the screen and set the active widget inside a composite view. Internally, setCurrent() and setCurrentAt() use IMidlet::setActive(), whose unique role is to save a reference to the active IDisplayable.

IMidlet::setActive() can be used when no screen redrawing is necessary, for example:

bool dateCtlCmdTabHdl( Command* l)
  {
    setActive("DATE_CTL");
    return true;
  }

A DateCtl (with PickCtl as a minor variaton) was implemented in DateIml.h.

Wrapping Up

This framework can be easily extended—by adding new controls and new events as needed. It is easier to use than the original BREW API because it is similar to the J2ME model and provides automatic memory management. All the previously described frameworks (see Generic Connection Framework, and Cooperative Multitasking) easily fit inside this one.

References

  1. Brew & J2ME: Let’s Be Friends! http://www.developer.com/ws/brew/article.php/3116331
  2. What Is the “Named Constructor Idiom?” http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.8

Download Sample Program

Source code: cppapp.zip – 222 kb

About the Author

Radu Braniste is Director of Technology at Epicad. He can be contacted at rbraniste@epicad.com

# # #

Get the Free Newsletter!
Subscribe to Developer Insider for top news, trends & analysis
This email address is invalid.
Get the Free Newsletter!
Subscribe to Developer Insider for top news, trends & analysis
This email address is invalid.

Latest Posts

Related Stories