Package de.esoco.j2me.ui

Source Code of de.esoco.j2me.ui.HierarchyView$DefaultNodeDisplay

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// J2ME-Lib source file
// Copyright (c) 2006 Elmar Sonnenschein / esoco GmbH
// Last Change: 17.10.2006 by eso
//
// J2ME-Lib is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 2.1 of the License, or (at your option)
// any later version.
//
// J2ME-Lib is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with J2ME-Lib; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA or use the contact
// information from the GNU website http://www.gnu.org
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
package de.esoco.j2me.ui;

import de.esoco.j2me.model.MutableHierarchyNode;
import de.esoco.j2me.model.HierarchyNode;
import de.esoco.j2me.model.NodeAccessException;
import de.esoco.j2me.resource.ResourceBundle;
import de.esoco.j2me.storage.StorageException;
import de.esoco.j2me.storage.StorageFullException;
import de.esoco.j2me.util.Executable;
import de.esoco.j2me.util.Log;
import de.esoco.j2me.util.TextUtil;
import de.esoco.j2me.util.UserNotificationException;

import java.util.Stack;

import javax.microedition.lcdui.Choice;
import javax.microedition.lcdui.ChoiceGroup;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Item;
import javax.microedition.lcdui.List;
import javax.microedition.lcdui.TextField;


/********************************************************************
* A view to display hierarchical data in J2ME lcdui List screens. To use this
* class it is mandatory that MIDlets are subclassed from the BasicMidlet class
* from this library because it provides the necessary infrastructure for
* resource and display access.
*
* @author eso
*/
public class HierarchyView
{
  //~ Static fields/initializers ---------------------------------------------

  /** The maximum length of node title strings */
  public static final int MAX_TITLE_SIZE = 128;

  //~ Instance fields --------------------------------------------------------

  /** This node reference stores entries that are cut or copied from a list */
  private HierarchyNode aClipboard = null;

  /** Image for new item at end of list */
  private Image aNewItemImage = null;

  /** Stack of the currently displayed Displayables (hierarchy levels) */
  private Stack aViewStack = new Stack();

  /** The currently displayed Displayable object */
  private Displayable rCurrentView;

  /** The root node of the data to be displayed (which itself is not shown) */
  private HierarchyNode rModel;

  /** The controller for the view */
  private HierarchyViewController rViewController;

  //~ Constructors -----------------------------------------------------------

  /***************************************
   * Constructor of a new HierarchyView for a specific set of data. It will
   * create a list for the root data but will not display it on the screen
   * already. This needs to be done by the application by calling the method
   * setCurrentView().
   *
   * @param rDataRoot        The root of the hierarchical data to display in
   *                         the view; this must always be a group node!
   * @param rNodeController  The controller for accessing and editing of nodes
   * @param rGeneralCommands A command array containing the general commands
   *                         that will always be availabe in all screens. This
   *                         array must contain either ExecutableCommand or
   *                         NodeCommand entries which can be performed
   *                         directly through their execute methods. If other
   *                         commands are set they will appear on the screen
   *                         but they <b>WILL NOT RESULT IN ANY ACTION BEING
   *                         PERFORMED!</b>
   */
  public HierarchyView(HierarchyNode  rDataRoot,
             NodeController rNodeController,
             Command[]    rGeneralCommands)
  {
    rModel = rDataRoot;

    rNodeController.setView(this);

    rViewController = createViewController(rNodeController,
                         rGeneralCommands);
    aNewItemImage   = getImage("JL_NewNodeI");

    init();
  }

  //~ Methods ----------------------------------------------------------------

  /***************************************
   * Returns the currently visisble Node, i.e. the node the children of which
   * are displayed.
   *
   * @return The currently active parent HierarchyNode instance
   */
  public HierarchyNode getCurrentNode()
  {
    if (rCurrentView instanceof HierarchyNodeDisplay)
    {
      return ((HierarchyNodeDisplay) rCurrentView).getRoot();
    }
    else
    {
      return getCurrentDisplay().getSelectedNode();
    }
  }

  /***************************************
   * Returns the currently shown Displayable object of the HierarchyView.
   *
   * @return The current Displayable object
   */
  public Displayable getCurrentView()
  {
    return rCurrentView;
  }

  /***************************************
   * Returns the currently selected Node, i.e. the child node of the current
   * node which is currently highlighted. If the currently displayed Screen is
   * not a List, the current Node will be returned instead (because in this
   * case either a leaf editor or viewer is visible).
   *
   * @return The currently selected HierarchyNode instance or NULL if the"New
   *         Item" entry is selected
   */
  public HierarchyNode getSelectedNode()
  {
    if (rCurrentView instanceof HierarchyNodeDisplay)
    {
      return ((HierarchyNodeDisplay) rCurrentView).getSelectedNode();
    }
    else
    {
      return getCurrentNode();
    }
  }

  /***************************************
   * Initializes and re-displays the view so that it shows the same state as
   * if it had just been created. This will also empty the clipboard.
   */
  public void reset()
  {
    aClipboard = null;
    init();
    show();
  }

  /***************************************
   * Displays the hierarchy view on the screen. This method should normally be
   * used by the application to display the first HierarchyView screen.
   * invokes <code>ScreenManager.setCurrent()</code> and therefore replaces
   * the currently visible screen on the screen stack.
   */
  public void show()
  {
    if (rCurrentView != ScreenManager.getCurrent())
    {
      ScreenManager.setCurrent(rCurrentView);
    }
  }

  /***************************************
   * Re-displays the current view from the internal view stack. This method is
   * used internally during navigation but it can also be used by applications
   * and subclasses to restore a HierarchyView's last screen if they have
   * displayed other screens in front of it.
   */
  public void updateCurrentView()
  {
    rCurrentView = (Displayable) aViewStack.peek();
    ScreenManager.setCurrent(rCurrentView);
  }

  /***************************************
   * Updates the list item for a certain node with new values for the title
   * string and/or the item image. The given node must be a child node of the
   * currently displayed parent node.
   *
   * @param rNode     The node for which the item shall be updated
   * @param sNewTitle The new title or NULL to keep the old string
   * @param rNewImage The new image or NULL for the default image (queried
   *                  from the node controller)
   */
  public void updateNodeItem(HierarchyNode rNode,
                 String     sNewTitle,
                 Image     rNewImage)
  {
    if (rNode != null)
    {
      HierarchyNodeDisplay rDisplay = getCurrentDisplay();
      int           nIndex   = rDisplay.getRoot().getChildIndex(rNode);

      if (nIndex >= 0)
      {
        if (sNewTitle == null)
        {
          sNewTitle = rDisplay.getListView().getString(nIndex);
        }

        if (rNewImage == null)
        {
          rNewImage = rViewController.getNodeController()
                         .getNodeImage(rNode
                               .getNodeType(),
                               rNode);
        }

        rDisplay.getListView().set(nIndex, sNewTitle, rNewImage);
      }
    }
  }

  /***************************************
   * Internal tool method to recursively add a command to all list views on
   * the stack.
   *
   * @param rCmd          The command to add
   * @param bEditableOnly If TRUE the command will only be added to Lists
   *                      which display an MutableHierarchyNode
   */
  protected void addListCommand(Command rCmd, boolean bEditableOnly)
  {
    for (int i = aViewStack.size(); i > 0;)
    {
      Object rView = aViewStack.elementAt(--i);

      if (rView instanceof HierarchyNodeDisplay)
      {
        HierarchyNodeDisplay rDisplay = (HierarchyNodeDisplay) rView;
        HierarchyNode     rNode    = rDisplay.getRoot();

        if (!bEditableOnly || (rNode instanceof MutableHierarchyNode))
        {
          ((Displayable) rDisplay).addCommand(rCmd);
        }
      }
    }
  }

  /***************************************
   * To go back one level in the hierarchical data (if not already on top).
   */
  protected void back()
  {
    if (aViewStack.size() > 1)
    {
      aViewStack.pop();
      updateCurrentView();
    }
  }

  /***************************************
   * To copy the child node at the position currently selected into the
   * internal clipboard. The list will not be changed by this action.
   *
   * @param rNode The Node to cut to the clipboard
   */
  protected void copyNode(HierarchyNode rNode)
  {
    aClipboard = rNode.deepCopy();
    addListCommand(rViewController.getPasteCommand(), true);
  }

  /***************************************
   * Creates a new node display instance that displays a list of the node's
   * children. This method may be overwritten by subclasses to return a
   * different kind of node display. The returned object must also be a
   * subclass of {@link Displayable}, else the invoking code will throw a
   * ClassCastException.
   *
   * <p>This default implementation returns an instance of the inner class
   * {@link DefaultNodeDisplay}.</p>
   *
   * @param  rRoot   The root node to display the children of
   * @param  rTitles The list item titles
   * @param  rImages The list item images
   *
   * @return A displayable instance
   */
  protected HierarchyNodeDisplay createNodeDisplay(HierarchyNode rRoot,
                           String[]     rTitles,
                           Image[]     rImages)
  {
    return new DefaultNodeDisplay(rRoot, rTitles, rImages);
  }

  /***************************************
   * Creates an instance of the view controller that is used by this view.
   *
   * @param  rNodeController  The node controller to use for node access
   * @param  rGeneralCommands The commands that are always available
   *
   * @return A HierarchyViewController instance
   */
  protected HierarchyViewController createViewController(NodeController rNodeController,
                               Command[]    rGeneralCommands)
  {
    return new HierarchyViewController(this, rNodeController,
                       rGeneralCommands);
  }

  /***************************************
   * To copy the child node at the position currently selected to the internal
   * clipboard and to delete it from the displayed parent node afterwards. All
   * nodes after that position will be shifted one place up.
   *
   * @param  rNode The Node to cut to the clipboard
   *
   * @throws UserNotificationException If the operations fails
   */
  protected void cutNode(HierarchyNode rNode) throws UserNotificationException
  {
    aClipboard = rNode;
    deleteNode(rNode);
    addListCommand(rViewController.getPasteCommand(), true);
  }

  /***************************************
   * To delete the child node at the position currently selected from the
   * displayed parent node. All nodes after that position will be shifted one
   * place up.
   *
   * @param  rNode The Node to delete
   *
   * @throws UserNotificationException If the operations fails
   */
  protected void deleteNode(HierarchyNode rNode)
    throws UserNotificationException
  {
    HierarchyNodeDisplay rDisplay = (HierarchyNodeDisplay) rCurrentView;
    HierarchyNode     rParent  = rDisplay.getRoot();
    int           nIndex   = rParent.getChildIndex(rNode);

    try
    {
      ((MutableHierarchyNode) rParent).removeChild(nIndex);
      rDisplay.getListView().delete(nIndex);
    }
    catch (StorageException eStorage)
    {
      errorException(eStorage, "JL_MtFile", "JL_MsStorErr",
               eStorage.getMessage());
    }
    catch (NodeAccessException eNodeAccess)
    {
      errorException(eNodeAccess, "JL_MtError", "JL_MsNodeErr",
               eNodeAccess.getMessage());
    }
  }

  /***************************************
   * To end the editing of a leaf node's data.
   *
   * @param  rNode TRUE if editing has been cancelled, Ok otherwise.
   *
   * @throws UserNotificationException If the operations fails
   */
  protected void endLeafEditor(MutableHierarchyNode rNode)
    throws UserNotificationException
  {
    try
    {
      rViewController.getNodeController().editFinished(rCurrentView,
                               rNode);
    }
    catch (StorageFullException eFull)
    {
      errorException(eFull, "JL_MtFile", "JL_MsStorFul",
               eFull.getMessage());
    }
    catch (StorageException eStorage)
    {
      errorException(eStorage, "JL_MtFile", "JL_MsStorErr",
               eStorage.getMessage());
    }

    back();
    rViewController.getNodeController().updateLeafViewer(rCurrentView,
                               rNode);
  }

  /***************************************
   * Internal method to create and throw a new UserNotificationException based
   * on several parameters. The original exception which caused the error
   * condition can be queried as the causing exception of the new
   * UserNotificationException which will be thrown.
   *
   * @param  eCause      The original exception that caused the error
   * @param  sTitleKey   The resource key for the error message's title
   * @param  sMessageKey The resource key for the message of the new exception
   * @param  rMessageArg An optional argument for the message that will be
   *                     used in a call to TextFormat.formatMessage() or NULL
   *
   * @throws UserNotificationException This method will always throw a new
   *                                   exception
   */
  protected void errorException(Exception eCause,
                  String    sTitleKey,
                  String    sMessageKey,
                  Object    rMessageArg)
    throws UserNotificationException
  {
    String sMessage = TextUtil.formatMessage(getString(sMessageKey),
                         new Object[] { rMessageArg });

    Log.error(eCause);

    throw new UserNotificationException(getString(sTitleKey), sMessage,
                      eCause);
  }

  /***************************************
   * Returns the current List of the HierarchyView. This will scan over all
   * other displayable objects on the view stack until it finds the topmost
   * List object (there must always be at least one).
   *
   * @return The topmost List on the view stack
   */
  protected HierarchyNodeDisplay getCurrentDisplay()
  {
    Displayable rCheck;
    int        nPos = aViewStack.size();

    do
    {
      rCheck = (Displayable) aViewStack.elementAt(--nPos);
    }
    while (!(rCheck instanceof HierarchyNodeDisplay));

    return (HierarchyNodeDisplay) rCheck;
  }

  /***************************************
   * Internal shortcut method for {@link ResourceBundle#getImage(String)}.
   *
   * @see ResourceBundle#getImage(String)
   */
  protected Image getImage(String sKey)
  {
    return ResourceBundle.getCurrent().getImage(sKey);
  }

  /***************************************
   * Internal shortcut method for {@link ResourceBundle#getString(String)}.
   *
   * @see ResourceBundle#getString(String)
   */
  protected String getString(String sKey)
  {
    return ResourceBundle.getCurrent().getString(sKey);
  }

  /***************************************
   * Initializes the view screen without displaying it.
   */
  protected void init()
  {
    Displayable aDisplay = newNodeDisplay(rModel, false);

    aViewStack.removeAllElements();
    aViewStack.push(aDisplay);
    rCurrentView = aDisplay;
  }

  /***************************************
   * To insert a node into the displayed parent node at the position currently
   * selected. The node currently at that position and all after it will be
   * shifted one place further down. To append the node to the end of the list
   * there is a special "new item" element at the end.
   *
   * @param  rNode The node to insert
   *
   * @throws UserNotificationException If inserting the node fails
   */
  protected void insertNode(HierarchyNode rNode)
    throws UserNotificationException
  {
    HierarchyNodeDisplay rDisplay = (HierarchyNodeDisplay) rCurrentView;
    HierarchyNode     rParent  = rDisplay.getRoot();
    int           nIndex   = rDisplay.getListView()
                        .getSelectedIndex();

    try
    {
      ((MutableHierarchyNode) rParent).insertChild(rNode, nIndex);
      rDisplay.getListView().insert(nIndex, rNode.getTitle(),
                      rViewController.getNodeController()
                      .getNodeImage(rNode.getNodeType(),
                            rNode));
    }
    catch (StorageFullException eFull)
    {
      errorException(eFull, "JL_MtFile", "JL_MsStorFul",
               eFull.getMessage());
    }
    catch (StorageException eStorage)
    {
      errorException(eStorage, "JL_MtFile", "JL_MsStorErr",
               eStorage.getMessage());
    }
    catch (NodeAccessException eNodeAccess)
    {
      errorException(eNodeAccess, "JL_MtError", "JL_MsNodeErr",
               eNodeAccess.getMessage());
    }
  }

  /***************************************
   * To create a new node as a child of the currently displayed node at the
   * position currently selected. The node currently at that position and all
   * after it will be shifted one place further down. To append a node to the
   * end of the list there is a special "new item" element at the end.
   *
   * @param sType  The type of node to create as defined by the
   *               getChildCreateOptions() of MutableHierarchyNode
   * @param sTitle The title of the new node
   *
   * @see   MutableHierarchyNode#getChildCreateOptions()
   */
  protected void newNode(String sType, String sTitle)
    throws UserNotificationException
  {
    try
    {
      MutableHierarchyNode rParent = (MutableHierarchyNode)
                      getCurrentNode();
      HierarchyNode      rNode   = rParent.createChild(sType, sTitle);

      insertNode(rNode);
    }
    catch (NodeAccessException eNodeAccess)
    {
      errorException(eNodeAccess, "JL_MtError", "JL_MsNodeErr",
               eNodeAccess.getMessage());
    }
  }

  /***************************************
   * Returns a new Displayable instance that will display a list of the
   * children of a particular Node.
   *
   * @param  rParent          The data node to create a new list for it's
   *                          children
   * @param  bWithBackCommand TRUE if the list shall have a "Back" command
   *
   * @return A new List object for the current hierarchy level
   */
  protected Displayable newNodeDisplay(HierarchyNode rParent,
                     boolean     bWithBackCommand)
  {
    boolean bEditable = (rParent instanceof MutableHierarchyNode);
    int     nCount    = rParent.getChildCount();
    int     nSize     = nCount;

    // increase size to reserve space for "New" command on editable nodes
    if (bEditable)
    {
      nSize++;
    }

    String[] aTitles = new String[nSize];
    Image[]  aImages = new Image[nSize];

    for (int i = 0; i < nCount; i++)
    {
      HierarchyNode rChild = rParent.getChild(i);

      aTitles[i] = rChild.getTitle();
      aImages[i] = rViewController.getNodeController().getNodeImage(rChild
                                      .getNodeType(),
                                      rChild);
    }

    if (bEditable)
    {
      aTitles[nCount] = getString("JL_NewItem");
      aImages[nCount] = aNewItemImage;
    }

    Displayable aDisplay = (Displayable) createNodeDisplay(rParent, aTitles,
                                 aImages);

    rViewController.setListCommands(aDisplay, rParent, bEditable,
                    aClipboard != null, bWithBackCommand);

    return aDisplay;
  }

  /***************************************
   * To copy the node currently in the clipboard into the displayed parent
   * node at the position currently selected. It uses the method insertNode()
   * to perform the insertion.
   *
   * @see #insertNode(HierarchyNode)
   */
  protected void pasteNode() throws UserNotificationException
  {
    // insert a copy to prevent side effects with the original node that
    // is still kept in the clipboard
    insertNode(aClipboard.deepCopy());
  }

  /***************************************
   * To query the parameters for the creation of a new node. This method
   * displays a custom message box to query the new node's type and name and
   * invokes the newNode() method if the user confirms this dialog (i. e.
   * selects Ok).
   *
   * @param rParentNode The parent node to create the new node in
   *
   * @see   #newNode(String, String)
   */
  protected void queryNewNode(MutableHierarchyNode rParentNode)
  {
    final String[] aTypeKeys = rParentNode.getChildCreateOptions();
    String[]     aTypes    = new String[aTypeKeys.length];
    Image[]       aImages   = new Image[aTypeKeys.length];

    for (int i = 0; i < aTypes.length; i++)
    {
      String sType = aTypeKeys[i];

      aImages[i] = rViewController.getNodeController().getNodeImage(sType,
                                      null);
      aTypes[i= getString(sType);
    }

    final ChoiceGroup aChoice = new ChoiceGroup(getString("JL_LNodeType"),
                          Choice.EXCLUSIVE, aTypes,
                          aImages);
    final TextField   aInput  = new TextField(getString("JL_LNodeName"),
                          null, MAX_TITLE_SIZE,
                          TextField.ANY);

    // Use a MessageScreen to query the new title and, if confirmed, callback
    // to method newNode(); params null, null: no Image, empty textfield
    MessageScreen.showMessageBox(getString("JL_MtNewNode"), null, null,
                  new Item[] { aChoice, aInput }, false,
                  new Executable()
                  {
                    public void execute(Object rArg)
                      throws UserNotificationException
                    {
                      // this callback will only be invoked if the user selects Ok
                      newNode(aTypeKeys[aChoice
                                .getSelectedIndex()],
                          aInput.getString());
                    }
                  });
  }

  /***************************************
   * To query the new name for renaming a particular node. This method
   * displays a input message box and invokes the renameNode() method if the
   * user confirms this dialog (i.e. selects Ok).
   *
   * @param rNode The Node to delete
   *
   * @see   #renameNode(MutableHierarchyNode, String)
   */
  protected void queryRenameNode(final MutableHierarchyNode rNode)
  {
    // Use a MessageScreen to query the new title, if confirmed callback
    // to method renameNode()
    MessageScreen.showInput(getString("JL_MtRenNode"),
               getString("JL_LNodeName"), rNode.getTitle(),
               MAX_TITLE_SIZE, TextField.ANY, false,
               new Executable()
               {
                 public void execute(Object rArg)
                   throws UserNotificationException
                 { // this callback will only be invoked if the user selects Ok
                   renameNode(rNode,
                          (String) ((Dialog) rArg)
                          .getResult());
                 }
               });
  }

  /***************************************
   * To change the title of a node in the current list.
   *
   * @param  rNode     The node to be renamed
   * @param  sNewTitle The new title for the node
   *
   * @throws UserNotificationException If the operations fails
   */
  protected void renameNode(MutableHierarchyNode rNode, String sNewTitle)
    throws UserNotificationException
  {
    try
    {
      rNode.setTitle(sNewTitle);
      updateNodeItem(rNode, sNewTitle, null);
    }
    catch (StorageFullException eFull)
    {
      errorException(eFull, "JL_MtFile", "JL_MsStorFul",
               eFull.getMessage());
    }
    catch (StorageException eStorage)
    {
      errorException(eStorage, "JL_MtFile", "JL_MsStorErr",
               eStorage.getMessage());
    }
  }

  /***************************************
   * To set a new Displayable object to be displayed on the screen.
   *
   * @param rView The object to display next
   */
  protected void setNextView(Displayable rView)
  {
    aViewStack.push(rView);
    updateCurrentView();
  }

  //~ Inner Classes ----------------------------------------------------------

  /********************************************************************
   * Extended List class that manages the associated parent node and
   * additional state needed for navigating the node hierarchy.
   */
  protected class DefaultNodeDisplay extends List
    implements HierarchyNodeDisplay
  {
    //~ Instance fields ----------------------------------------------------

    private HierarchyNode rRoot;

    //~ Constructors -------------------------------------------------------

//        private int nLastSelection = 0;

    /***************************************
     * Constructor for a list that displays the titles of the nodes of a
     * certain parent node.
     *
     * @param rRoot   The parent node of the listed nodes
     * @param rTitles The title strings of the listed nodes
     * @param rImages The (optional) images for the listed nodes
     */
    public DefaultNodeDisplay(HierarchyNode rRoot,
                  String[]    rTitles,
                  Image[]    rImages)
    {
      super(rRoot.getTitle(), List.IMPLICIT, rTitles, rImages);

      this.rRoot = rRoot;
    }

    //~ Methods ------------------------------------------------------------

    /***************************************
     * @see HierarchyNodeDisplay#getListView()
     */
    public Choice getListView()
    {
      return this;
    }

    /***************************************
     * @see HierarchyNodeDisplay#getRoot()
     */
    public HierarchyNode getRoot()
    {
      return rRoot;
    }

    /***************************************
     * @see HierarchyNodeDisplay#getSelectedNode()
     */
    public HierarchyNode getSelectedNode()
    {
      int nIndex = getSelectedIndex();

      // The index may be out of range because of the <New Item> entry
      // at the end of lists that does not represent an existing Node
      if (nIndex < rRoot.getChildCount())
      {
        return rRoot.getChild(nIndex);
      }
      else
      {
        return null;
      }
    }
  }
}
TOP

Related Classes of de.esoco.j2me.ui.HierarchyView$DefaultNodeDisplay

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.