/****************************************************************************
  $Archive: /njcl_sample/NJCLApplet/src/plugins/DataPopupHandler.java $
  $Revision: 1 $
  $Modtime: 3/12/99 8:51a $

  Copyright (c) 1999 Novell, Inc.  All Rights Reserved.

 THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND TREATIES.
 USE AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO THE LICENSE AGREEMENT
 ACCOMPANYING THE SOFTWARE DEVELOPMENT KIT (SDK) THAT CONTAINS THIS WORK.
 PURSUANT TO THE SDK LICENSE AGREEMENT, NOVELL HEREBY GRANTS TO DEVELOPER A
 ROYALTY-FREE, NON-EXCLUSIVE LICENSE TO INCLUDE NOVELL'S SAMPLE CODE IN ITS
 PRODUCT. NOVELL GRANTS DEVELOPER WORLDWIDE DISTRIBUTION RIGHTS TO MARKET,
 DISTRIBUTE, OR SELL NOVELL'S SAMPLE CODE AS A COMPONENT OF DEVELOPER'S
 PRODUCTS. NOVELL SHALL HAVE NO OBLIGATIONS TO DEVELOPER OR DEVELOPER'S
 CUSTOMERS WITH RESPECT TO THIS CODE.
 ***************************************************************************/

import java.io.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.border.*;
import javax.naming.*;
import javax.naming.directory.*;

import com.novell.java.io.DataAccessable;
import com.novell.java.io.NInputStream;
import com.novell.java.io.NOutputStream;

/**
 * This class provides a 'snap-in' that keys off of a popup menu for file
 * display.
 *
 * <p>This handler basically provides a text/hex file viewer for anything
 * that can have a stream (via DataAccessable).
 * </p>
 */
public class DataPopupHandler
   implements PopupHandler
{
   /**
    * Required public, null constructor.
    */
   public DataPopupHandler ()
   {
   }
   
   /**
    * Method called for registered handlers when the popup event occurs.
    *
    * @param frame               The frame in which the popup is being
    *                            requested.
    * @param popup               The popup menu being created. If the handler
    *                            wants to provide custom handling for the
    *                            object being passed, it can add one or more
    *                            to this menu before returnning. Otherwise
    *                            it can ignore this request.
    * @param obj                 The object being customized. This is likely
    *                            to be a ContextTreeNode.
    */
   public void popupRequested (
      JFrame frame,           // unused
      JPopupMenu popup,
      Object obj)
   {
      Context ctx;
                        
      if (obj instanceof ContextTreeNode)
      {
         ctx = ((ContextTreeNode) obj).getThisContext ();
         if ((null != ctx) &&
             (ctx instanceof DirContext) &&
             (ctx instanceof DataAccessable))
            new DataActionListener (obj.toString(), (DirContext) ctx, popup);
      }
   } // popupRequested ()
   
} // class DataPopupHandler

/**
 * Internal class to handle NetWare data object.
 */
class DataActionListener
   implements ActionListener
{
   String fileName;
   DirContext fileCtx;
   JMenuItem dataMenuItem;

   private static int buttonHeight = 25;

   /**
    * Public, default constructor required for plugins.
    *
    * <p>Even though this is not a plug-in, it is in the plug-in directory.
    * </p>
    */
   public DataActionListener () {}

   /**
    * Constructor which hangs onto the context.
    *
    * @param fileName            The name of the file.
    * @param fileCtx             The file context to be used.
    * @param popup               The popup menu being created. If the handler
    *                            wants to provide custom handling for the
    *                            object being passed, it can add one or more
    *                            to this menu before returnning. Otherwise
    *                            it can ignore this request.
    */   
   public DataActionListener (
         String fileName,
         DirContext fileCtx,
         JPopupMenu popup)
   {
      this.fileName = fileName;
      this.fileCtx = fileCtx;   // already checked for DataAccessable
      dataMenuItem = new JMenuItem ("View Contents");
      dataMenuItem.setToolTipText ("Text/hex viewer");
      dataMenuItem.addActionListener (this);
      popup.add (dataMenuItem);
   } // DataActionListener ()

   // ActionListener methods ------------------------------------------------
   /**
    * ActionListener event handler method.
    *
    * @param event               The event describing what action occured.
    */
   public void actionPerformed (
         ActionEvent event)
   {
      Object object = event.getSource ();
   
      if (object == dataMenuItem)
         data_Action (event);
   } // actionPerformed ()

   /**
    * Action handler for the data menu item.
    *
    * @param event               The event describing what action occured.
    */
   void data_Action (
         ActionEvent event)
   {
      DataViewFrame frame = new DataViewFrame (fileName, fileCtx);
      Point p = ((Component) event.getSource ()).getLocation ();
      p.translate (buttonHeight, buttonHeight);
      frame.setLocation (p);
      frame.show ();
   } // data_Action ()
   
} // class DataActionListener

/**
 * Internal class which presents a text or hex file viewer.
 */
class DataViewFrame
   extends JFrame
   implements ActionListener
{
   Context fileCtx;
   
   private static int emptySpace = 5;
   private static int buttonHeight = 25;
   private static int buttonWidth = 80;
   private static int mainPaneHeight = 300;
   private static int mainPaneWidth = 580;
   
   JButton asciiButton;
   JButton hexButton;
   JButton saveButton;
   JButton closeButton;

   JScrollPane fileScrollPane;
   JTextArea fileTextArea;
   JList fileHexList;
   DefaultListModel fileListModel;
   static final int hexDisplayByteCount = 16;
   
   private boolean textView;  // Set to true when text view, false for hex

   /**
    * Constructs a 'file' viewer in text mode.
    *
    * @param fileName            The name of the 'file' to be viewed.
    * @param fileCtx             The context to be used to access the 'file'.
    */
   public DataViewFrame (
         String fileName,
         DirContext fileCtx)
   {
      super (fileName);
      this.fileCtx = fileCtx;
      
      getContentPane ().setLayout (null);
      setSize (
            mainPaneWidth + (emptySpace * 2),
            mainPaneHeight + buttonHeight + (emptySpace * 2));

      fileScrollPane = new JScrollPane ();
      fileScrollPane.setBounds (
                        emptySpace,
                        emptySpace,
                        mainPaneWidth - (emptySpace * 2),
                        mainPaneHeight - (buttonHeight + (emptySpace * 3)));
      getContentPane ().add (fileScrollPane);
      
      // Buttons
      asciiButton = new JButton ("ASCII");
      asciiButton.setToolTipText ("Display the data in ASCII form");
      asciiButton.setBounds (
                        emptySpace,
                        mainPaneHeight - (buttonHeight + emptySpace),
                        buttonWidth,
                        buttonHeight);
      asciiButton.addActionListener (this);
      getContentPane ().add (asciiButton);

      hexButton = new JButton ("Hex");
      hexButton.setToolTipText ("Display the data in hexidecimal form");
      hexButton.setBounds (
                        (buttonWidth + emptySpace) + emptySpace,
                        mainPaneHeight - (buttonHeight + emptySpace),
                        buttonWidth,
                        buttonHeight);
      hexButton.addActionListener (this);
      getContentPane ().add (hexButton);

      saveButton = new JButton ("Save");
      saveButton.setToolTipText ("Save the data");
      saveButton.setBounds (
                        (buttonWidth + emptySpace) * 2 + emptySpace,
                        mainPaneHeight - (buttonHeight + emptySpace),
                        buttonWidth,
                        buttonHeight);
      saveButton.addActionListener (this);
      getContentPane ().add (saveButton);

      closeButton = new JButton ("Close");
      closeButton.setToolTipText ("Close this window");
      closeButton.setBounds (
                        (buttonWidth + emptySpace) * 3 + emptySpace,
                        mainPaneHeight - (buttonHeight + emptySpace),
                        buttonWidth,
                        buttonHeight);
      closeButton.addActionListener (this);
      getContentPane ().add (closeButton);

      textView = false;       // preset so that showText does its work
      showText ();
   }  // DataViewFrame()
   
   /**
    * ActionListener event handler method.
    */
   public void actionPerformed (
         ActionEvent event)
   {
      Object object = event.getSource ();
      if (object == asciiButton)
         asciiButton_Action (event);
      else if (object == hexButton)
         hexButton_Action (event);
      else if (object == saveButton)
         saveButton_Action (event);
      else if (object == closeButton)
         closeButton_Action (event);
   } // ActionListener.actionPerformed ()

   /**
    * Action handler for the ASCII button.
    */
   void asciiButton_Action (
         ActionEvent event)
   {
      setVisible (false);
      showText ();
      setVisible (true);
   } // asciiButton_Action ()

   /**
    * Action handler for the hex button.
    */
   void hexButton_Action (
         ActionEvent event)
   {
      setVisible (false);
      showHex ();
      setVisible (true);
   } // hexButton_Action ()
   
   /**
    * Action handler for the save button.
    */
   void saveButton_Action (
         ActionEvent event)
   {
      Cursor saveCursor = getCursor ();
      setCursor (Cursor.getPredefinedCursor (Cursor.WAIT_CURSOR));

      try
      {
         DataOutputStream out = 
            new DataOutputStream (
                  new NOutputStream ((DataAccessable) fileCtx));

         out.writeBytes (fileTextArea.getText ());
         out.close ();
      }
      catch (Throwable e)
      {
      }

      setCursor (saveCursor);
   } // saveButton_Action ()
   
   /**
    * Action handler for the close button.
    */
   void closeButton_Action (
         ActionEvent event)
   {
      dispose ();
   } // closeButton_Action ()

   /**
    * Set the main panel view to text mode view.
    */   
   void showText ()
   {
      if (textView)
         return;              //We're already displaying ASCII
         
      textView = true;        // Remember what mode we're in
      
      // Be sure to remove the hex view component
      if (null != fileHexList)
         fileScrollPane.remove (fileHexList);

      if (null == fileTextArea)
      {
         fileTextArea = new JTextArea ();
         fileTextArea.setEditable (true);
         
         String newLine = System.getProperty ("line.separator");
         
         boolean finished = false;
         String s = null;

         try
         {
            BufferedReader in = 
               new BufferedReader ( 
                     new InputStreamReader (
                           new NInputStream ((DataAccessable) fileCtx)));
         
            s = in.readLine ();
            while (s != null)
            {
               fileTextArea.append (s + newLine);
               s = in.readLine ();
            }
            in.close ();
         }
         catch (IOException e)
         {
            fileTextArea.append (newLine + "<<< Interrupted >>>" + newLine);
            (new MessageBox (
                     "Error",
                     "Unable to read file:\n" +
                     Util.getExceptionTrace (e))).show ();
         }
         catch (Throwable e)
         {
            (new MessageBox (
                     "Error",
                     "Unable to read file:\n" +
                     Util.getExceptionTrace (e))).show ();
         }
      } // if (null == fileTextArea)
      
      fileScrollPane.getViewport().add (fileTextArea);
   } // showText()

   /**
    * Set the main panel view to hex mode view.
    */
   void showHex ()
   {
      if (!textView)
         return;              //We're already displaying hex

      textView = false;       // Remember what mode we're in

      // Be sure to remove the ascii view component
      if (null != fileTextArea)
         fileScrollPane.getViewport ().remove (fileTextArea);

      if (null == fileHexList)
      {
         fileListModel = new DefaultListModel ();
         fileHexList = new JList (fileListModel);
         fileHexList.setFont (new Font ("Courier", Font.PLAIN, 12));

         try
         {
            BufferedReader in = 
               new BufferedReader (
                     new InputStreamReader (
                           new NInputStream ((DataAccessable) fileCtx)));

            char a[] = new char [hexDisplayByteCount];
            char disp[] = new char[12 + a.length * 3 + 1 + a.length];
            char hex[] = { '0','1','2','3','4','5','6','7',
                           '8','9','A','B','C','D','E','F' };
            int numBytesRead = 0;
            int totalBytes = 0;
            boolean finished = false;
            int maxTextWidth = 0, dispWidth;

            while (!finished)
            {
               int i = 0;

               for (int j = 8; j > 0; j--)
               {
                  disp[i++] = hex[(totalBytes >>> (4*j)) & 0x0F];
               }
               disp[i++] = ':';
               disp[i++] = ' ';

               // Read in the next section and check for EOF
               numBytesRead = in.read (a, 0, a.length);
               if (numBytesRead < a.length)
                  finished = true;     // EOF

               // fill in hex columns
               for (int j = 0; j < a.length; j++)
               {
                  if (j < numBytesRead)
                  {
                     disp[i++] = hex[(a[j] >>> 4) & 0x0F];
                     disp[i++] = hex[(a[j] >>> 0) & 0x0F];
                  }
                  else
                  {
                     // spaces for bytes not read
                     disp[i++] = ' ';
                     disp[i++] = ' ';
                  }

                  if (j == (a.length / 2) - 1)
                  {
                     disp[i++] = ' ';
                     disp[i++] = !finished ? '-' : ' ';
                  }

                  disp[i++] = ' ';
               }

               disp[i++] = ' ';

               // fill in printable characters, dots for unprintables
               for (int j = 0; j < a.length; j++)
               {
                  if (j < numBytesRead)
                  {
                     if (a[j] < (char) 32 || a[j] > (char) 127)
                        disp[i++] = '.';
                     else
                        disp[i++] = (char) a[j];
                  }
                  else
                     disp[i++] = ' ';
               }

               totalBytes += numBytesRead;
               fileListModel.addElement (new String (disp));
            }
            in.close ();
         }
         catch (IOException e)
         {
            fileListModel.addElement ("<<< Interrupted >>>");
            (new MessageBox (
                     "Error",
                     "Unable to read file:\n" +
                     Util.getExceptionTrace (e))).show ();
         }
         catch (Throwable e)
         {
            (new MessageBox (
                     "Error",
                     "Unable to read file:\n" +
                     Util.getExceptionTrace (e))).show ();
         }
      } // if (null == fileHexList)
      
      fileScrollPane.getViewport ().add (fileHexList);
   } // showHex ()
   
} // class DataViewFrame
