Modify the BlackBerry Hybrid Web Container so that Hybrid Apps appear in a tree view.
/* 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 ); } } }; }