Creating a Tree View

Modify the BlackBerry Hybrid Web Container so that Hybrid Apps appear in a tree view. 

  1. In the BlackBerry HybridWebContainer template project, in the src folder, right-click the com.sybase.hwc.amp package and choose New > File.
  2. Enter TreeViewAppScreen.java for the file name, and click Finish.
  3. Open the TreeViewAppScreen.java file for editing, and paste this code into the file. 
    /*
     Copyright (c) SAP, Inc. 2012 All rights reserved. 
    
     In addition to the license terms set out in the SAP License Agreement for 
     the SAP Mobile Platform ("Program"), the following additional or different 
     rights and accompanying obligations and restrictions shall apply to the source 
     code in this file ("Code"). SAP grants you a limited, non-exclusive, 
     non-transferable, revocable license to use, reproduce, and modify the Code 
     solely for purposes of (i) maintaining the Code as reference material to better 
     understand the operation of the Program, and (ii) development and testing of 
     applications created in connection with your licensed use of the Program. 
     The Code may not be transferred, sold, assigned, sublicensed or otherwise 
     conveyed (whether by operation of law or otherwise) to another party without 
     SAP's prior written consent. The following provisions shall apply to any 
     modifications you make to the Code: (i) SAP will not provide any maintenance 
     or support for modified Code or problems that result from use of modified Code; 
     (ii) SAP expressly disclaims any warranties and conditions, express or 
     implied, relating to modified Code or any problems that result from use of the 
     modified Code; (iii) SAP SHALL NOT BE LIABLE FOR ANY LOSS OR DAMAGE RELATING 
     TO MODIFICATIONS MADE TO THE CODE OR FOR ANY DAMAGES RESULTING FROM USE OF THE 
     MODIFIED CODE, INCLUDING, WITHOUT LIMITATION, ANY INACCURACY OF DATA, LOSS OF 
     PROFITS OR DIRECT, INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES, EVEN 
     IF SAP HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES; (iv) you agree 
     to indemnify, hold harmless, and defend SAP from and against any claims or 
     lawsuits, including attorney's fees, that arise from or are related to the 
     modified Code or from use of the modified Code.
     */
    package com.sybase.hwc.amp;
    
    import com.sybase.mo.*;
    import com.sybase.hybridApp.*;
    
    import java.util.Enumeration;
    
    import net.rim.device.api.i18n.ResourceBundle;
    import net.rim.device.api.system.*;
    import net.rim.device.api.ui.*;
    import net.rim.device.api.ui.component.*;
    import net.rim.device.api.ui.container.*;
    import net.rim.device.api.util.SimpleSortingVector;
    import com.sybase.hwc.*;
    
    // BLACKBERRY_CUSTOMIZATION_POINT_AUTOSTART
    // BLACKBERRY_CUSTOMIZATION_POINT_COLORS
    // BLACKBERRY_CUSTOMIZATION_POINT_FONTS
    // BLACKBERYY_CUSTOMIZATION_POINT_HYBRIDAPPLIST
    
    /**
     * This class displays a list of user invokable widgets currently present on the
     * device.
     */
    public class TreeViewAppScreen extends MainScreen {
    
       // Create a ResourceBundle object to contain the localized resources.
       // Here is a little bit of MAGIC.  How do you know what there is a class HybridWebContainerResource? (hint: its not from the docs)
       // It is auto generated by the JDE.  Convention is AppNameResource.BUNDLE_ID, AppNameResource.BUNDLE_NAME
       // http://www.codeproject.com/KB/mobile/EndToEndBBApp5.aspx
       public static final ResourceBundle RESOURCE = 
             ResourceBundle.getBundle( 
                   HybridWebContainerResource.BUNDLE_ID, 
                   HybridWebContainerResource.BUNDLE_NAME );
    
    
       public TreeViewAppScreen() {
          super(Manager.VERTICAL_SCROLL | Manager.NO_HORIZONTAL_SCROLLBAR);
    
          setTitle( RESOURCE.getString( HybridWebContainerResource.IDS_HYBRIDAPPS ) );
    
          // Sort apps by their display name
          m_oApps = new SimpleSortingVector();
          m_oApps.setSortComparator( CustomizationHelper.getInstance().getHybridAppComparator() );
    
          m_oApps.setSort(false);
    
          // Populate and sort list
          BBHybridAppHelper.addAppStoreListener( m_oAppListener );
          // Add list field to screen
          m_oTreeField = new TreeField( m_oTreeFieldCallback, TreeField.FOCUSABLE );
          m_oTreeField.setEmptyString( BBHybridWebContainer.getMocaStringResource( MocaClientLibResource.LBL_NO_WIDGETS_FOUND ), DrawStyle.HCENTER  );
          // set the size of the indentation
          m_oTreeField.setIndentWidth( 30 );
          populateList();
          updateScreen();
    
          // add the tree field to the screen
          add( m_oTreeField );
       }
    
    
       /**
        * Handle clicking on an application
        */
       protected boolean navigationClick(int status, int time)
       {
          Field oField = getFieldWithFocus();
          // only handle if it was the tree field that was clicked
          if ( oField instanceof TreeField )
          {
             Object obj = m_oTreeField.getCookie( ( ( TreeField ) oField ).getCurrentNode() );
             // only handle the click if it was a hybrid app (not a tree label)
             if( obj instanceof HybridApp )
             {
                // launch the clicked hybrid app
                HybridApp oApp = ( HybridApp ) obj;
                XmlHybridApp.startHybridApp( oApp.getModuleId(), oApp.getVersion(), false );
                return true;
             }
          }
    
          return super.navigationClick(status, time);
       }
    
       /**
        * Override the default Screen.close method
        */
       public void close() 
       {
          BBHybridAppHelper.removeAppStoreListener( m_oAppListener );
    
          UiApplication oApp = UiApplication.getUiApplication();
          oApp.popScreen(this);
    
          if ( oApp.getScreenCount() == 0 ) 
          {
             oApp.requestBackground();
          }
       }
    
       protected void makeMenu( Menu menu, int instance ) 
       {
          menu.deleteAll();
    
          if ( CustomizationHelper.getInstance().enableSettings() )
          {
             menu.add(m_mniSettings);
          }
    
          menu.add(MenuItem.getPrefab(MenuItem.CLOSE));
       }
    
       /**
        * Fills in list of apps
        */
       private void populateList() 
       {
          m_oApps.removeAllElements();
    
          for ( Enumeration e = BBHybridAppHelper.getClientHybridApps().elements(); e.hasMoreElements(); ) 
          {
             HybridApp oHybridApp = ( HybridApp )e.nextElement();
             m_oApps.addElement( oHybridApp );   
          }
          m_oApps.reSort();
       }
    
       /**
        * Updates the screen
        */
       private void updateScreen() 
       {
          // have to do stuff to the UI on a separate thread
          UiApplication.getUiApplication().invokeLater( 
                new Runnable() 
                {
                   public void run()
                   {
                      m_oTreeField.deleteAll();
                      // if there're no hybrid apps then we do not even want to add the tree labels
                      // so that the empty string will be displayed
                      if( m_oApps.size() > 0 )
                      {
                         // In this example, there are 3 top level categories of hybrid apps: Forms, Expense, and Miscellaneous.
                         // Forms has a sub-category of SpecialForms.  In practice you can have as many or as few categories
                         // and sub-categories as you like.  Here the category of a hybrid app is determined by whether
                         // keywords exist in the display name of that hybrid app, but you could use anything else (for example
                         // you could determine the category of a hybrid app by its icon).
                         int iMiscel = m_oTreeField.addChildNode( 0, "Miscellaneous Hybrid Apps");
                         int iForms = m_oTreeField.addChildNode( 0, "Form Hybrid Apps");
                         int iSpecialForms = m_oTreeField.addChildNode( iForms, "Special Forms");
                         int iExpense = m_oTreeField.addChildNode( 0, "Expense Hybrid Apps");
                         //have to iterate backwards through m_oApps since addChildNode adds the new node
                         //to the first position (appears above the nodes previously added).
                         for( int index = m_oApps.size()-1; index >= 0; index-- )
                         {
                            HybridApp oHybridApp = (HybridApp) m_oApps.elementAt( index );
                            int iParent = iMiscel;
                            if( oHybridApp.getDisplayName().indexOf("Expense") >= 0 )
                            {
                               iParent = iExpense;
                            }
                            else if( oHybridApp.getDisplayName().indexOf("Form") >= 0 )
                            {
                               if( oHybridApp.getDisplayName().indexOf("Special") >= 0 )
                               {
                                  iParent = iSpecialForms;
                               }
                               else
                               {
                                  iParent = iForms;
                               }
                            }
                            m_oTreeField.addChildNode( iParent, m_oApps.elementAt( index ) );
                         }
                      }
    
                   }
                } );
       }
    
       // Settings menu item
       private MenuItem m_mniSettings =
             new MenuItem( m_res.getString(HybridWebContainerResource.IDS_SETTINGS), 
                   100001,
                   10) 
       {
          public void run() 
          {
             XmlHybridApp.startHybridAppSettings(false);
          }
       };
    
       // Listener for app changes
       private HybridAppsListener m_oAppListener = 
             new HybridAppsListener() 
       {
          public void onRefreshRequired() 
          {
             populateList();
             updateScreen();
          }
    
          public void onHybridAppAdded(HybridApp oHybridApp) 
          {
             populateList();
             updateScreen();
          }
    
          public void onHybridAppRemoved(HybridApp oHybridApp) 
          {
             populateList();
             updateScreen();
          }
    
          public void onHybridAppUpdated(HybridApp oHybridApp) 
          {
             populateList();
             updateScreen();
          }
       };
    
       private SimpleSortingVector m_oApps;
    
       private TreeField m_oTreeField;
    
       private static ResourceBundle m_res = ResourceBundle.getBundle( 
             HybridWebContainerResource.BUNDLE_ID,
             HybridWebContainerResource.BUNDLE_NAME );
    
       private TreeFieldCallback m_oTreeFieldCallback = new TreeFieldCallback()
       {
          public void drawTreeItem( TreeField oTree, Graphics oGraphics, int iNode, int iY, int iWidth, int iIndent )
          {
    
             Object obj = oTree.getCookie( iNode );
             if( obj instanceof String )
             {
                oGraphics.setColor( Color.BLACK );
                oGraphics.drawText( (String)obj, iIndent, iY);
             }
             else if( obj instanceof HybridApp )
             {
                // y parameter is already offset to center text
                int iOffset = (oTree.getRowHeight() - getFont().getHeight()) >> 1;
    
                // Draw a background color for the hybrid apps to distinguish them from the tree labels.
                // However, if this node has focus we don't want to draw the grey rectangle because it
                // will cover up the blue color indicating the node is selected.
                if( iNode != m_oTreeField.getCurrentNode() )
                {
                   oGraphics.setColor( Color.LIGHTGRAY );
                   oGraphics.fillRect( iIndent, iY - iOffset, iWidth, m_oTreeField.getRowHeight() );
                }
    
                HybridApp oApp = ( HybridApp ) obj;
    
                final int iMargin = 2;
    
                // Draw image
                EncodedImage oImage = EncodedImage.getEncodedImageResource( "ampicon" + oApp.getIconIndex() + ".png" );
                int iBitmapWidth = 0;
    
    
                if ( oImage != null ) 
                {
                   CustomIcon oIcon = oApp.getDefaultCustomIcon();
    
                   if ( oIcon != null )
                   {
                      EncodedImage oImageTmp = oApp.getCustomIconImage( oIcon );
    
                      if ( oImageTmp != null ) 
                      {
                         if ( oImageTmp.getHeight() != oImage.getHeight() || oImageTmp.getWidth() != oImage.getWidth() ) 
                         {
                            MocaLog.getAmpHostLog().logMessage( 
                                  "Icon image size doesn't match the built-in icon size, the layout result could be different.", 
                                  MocaLog.eMocaLogLevel.Normal );
                         }
    
                         oImage = oImageTmp;
                      }
                   }
    
                   Bitmap oBitmap = oImage.getBitmap();
                   int iRowHeight = oTree.getRowHeight();
    
                   int iSize = oImage.getHeight() > oImage.getWidth() ? oImage.getHeight() : oImage.getWidth();
    
                   if ( iSize >= iRowHeight ) 
                   {
                      oBitmap = HWCMessagesListField.getScaledBitmapImage( oImage, iRowHeight - iMargin, iSize );   
                   }
    
                   oGraphics.drawBitmap( 
                         iMargin + iIndent, 
                         iY - iOffset + ( oTree.getRowHeight() - oBitmap.getHeight() ) / 2, 
                         oBitmap.getWidth(), oBitmap.getHeight(), oBitmap, 0, 0 );
    
                   iBitmapWidth = oBitmap.getWidth();
                }
                else
                {
                   MocaLog.getAmpHostLog().logMessage( "Can not find application icon image of application " + 
                         oApp.getDisplayName() + ".", MocaLog.eMocaLogLevel.Normal );
                }
    
                // Draw text
                oGraphics.setColor( Color.BLACK );
                oGraphics.drawText( oApp.getDisplayName(), 2 * iMargin + iBitmapWidth + iIndent, iY );
             }
          }
       };
    }
    
    This file is based on the AppScreen.java file. The main differences are in the constructor, navigationClick, populateList, and updateScreen functions. Also, the TreeFieldCallback class replaces the ListFieldCallback class from AppScreen.java.
  4. Open the CustomizationHelper.java file for editing, find the getHybridAppScreenClass function, and replace the existing return statement with this line:
    return com.sybase.hwc.amp.TreeViewAppScreen.class;
  5. Save the CustomizationHelper.java file.
  6. Rebuild the HybridWebContainer project.  
    When you run the Hybrid Web Container, the Hybrid Apps are shown in a tree field.