C:\Documents and Settings\chakkerz.STORMCROW\Desktop\naiv_development\naiv_0.4\src\test_code\DragFileToJAIDemo.java
/*
 *  For the original version of this file see:
 *    http://java.sun.com/docs/books/tutorial/index.htm
 *
 *  About this modification:
 *
 *  This is file (and the three classes) are based extremely heavily on the
 *  DragFileDemo.java (and associated files found at:
 *    http://java.sun.com/docs/books/tutorial/uiswing/misc/examples/index.html
 *  
 *  It was customized and such during the development of NAIV 0.4.0 pre13 and is
 *  NOT subject to any licensing restrictions (just as the original file was not
 *  subject to any such restrictions, unless i missed it, in which case the same
 *  license applies). Let's face it ... this doesn't do much, so i think it is 
 *  safe to assume that Sun doesn't have restrictions on its tutorials :P .
 *
 *  This is a proof of concept only and as such is not necessarily pretty code,
 *  nor do i claim it makes a lot of sense. That said, at the time of writing 
 *  this, i'm definitly planning to do more documentation so it should be 
 *  prettier and make more sense. But i don't promise anything :)
 *
 *  This does require JAI to be installed, so if you haven't got it, download it
 *  and make it happen. The reason this is released is ... well ... i couldn't 
 *  find a code snippet that showed me how to make this happen, and based on the
 *  tutorial example it took me a fair while to work out what to do, especially
 *  for a non text based application. So this is really really stripped down, 
 *  and for JAI.
 *
 *  How to use this:
 *  -> start the program
 *  -> select a file or a few files (in explorer or something) and drag into the 
 *     frame;
 *  -> IF everything worked the way it was supposed to, there should be a (number
 *     of) tab(s) displaying your file(s).
 *  -> Hit the clear button and try again, or drag more. I don't really care :)
 *
 *  I hope this helps someone out there at some stage.
 *
 *  Christian chakkerz Unger
 */

package test_code;

import java.awt.BorderLayout;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JComponent;
import javax.swing.TransferHandler;
import javax.media.jai.JAI;
import javax.media.jai.PlanarImage;
import com.sun.media.jai.widget.DisplayJAI;

public class DragFileToJAIDemo extends JPanel implements ActionListener 
{
  JButton clear = new JButton("Clear Tabs");
  TabbedPaneController tpc;

  /**
   *  constructor
   */
  public DragFileToJAIDemo() 
  {
    //setup the frame
    super(new BorderLayout());
    //create a listener for the clear button
    clear.addActionListener(this);
    //setup button and panel for it
    JPanel buttonPanel = new JPanel(new BorderLayout());
    buttonPanel.add(clear);
    this.add(buttonPanel, BorderLayout.SOUTH);

    //And setup the TabbedPane and related, including the TabbedPaneController
    JTabbedPane tabbedPane = new JTabbedPane();
    JPanel tabPanel = new JPanel(new BorderLayout());
    tpc = new TabbedPaneController(tabbedPane, tabPanel);
    this.add(tabPanel, BorderLayout.CENTER);
  }

  /**
   *  what to do when the button is pressed
   */
  public void actionPerformed(ActionEvent e) 
  {
    if (e.getSource() == clear) 
    {
      tpc.clearAll();
    }
  }

  /**
   *  createAndShowGUI()
   *
   *  creates the frame, sets it up and displays it ...
   */
  private static void createAndShowGUI() 
  {
    //Create and set up the window.
    JFrame frame = new JFrame("DragFileDemo");
    frame.setSize(500,500);
    frame.setLocationRelativeTo(null);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    //Create and set up the menu bar and content pane.
    DragFileToJAIDemo demo = new DragFileToJAIDemo();
    demo.setOpaque(true);
    frame.setContentPane(demo);

    frame.setVisible(true);
  }

  /**
   *  main()
   */
  public static void main(String[] args) 
  {
    javax.swing.SwingUtilities.invokeLater(new Runnable() 
    {
      public void run() 
      {
        createAndShowGUI();
      }
    });
  }
}

//******************************************************************************
class TabbedPaneController 
{
  JPanel tabbedPanel = null;
  JTabbedPane tabbedPane;
  JPanel emptyFilePanel = null;
  DisplayJAI emptyFileArea = null;
  FileToDisplayJAITransferHandler transferHandler;
  boolean noFiles = true;
  String fileSeparator;

  /**
   *  constructor
   */
  public TabbedPaneController(JTabbedPane tb, JPanel tp) 
  {
    tabbedPane = tb;
    tabbedPanel = tp;
    transferHandler = new FileToDisplayJAITransferHandler(this);
    fileSeparator = System.getProperty("file.separator");
    if ("\\".equals(fileSeparator)) 
    {
      fileSeparator = "\\\\";
    }
    this.init();
  }

  public DisplayJAI addTab(String filename) 
  {
    if (noFiles) 
    {
      tabbedPanel.remove(emptyFilePanel);
      tabbedPanel.add(tabbedPane, BorderLayout.CENTER);
      noFiles = false;
    }
    String[] str = filename.split(fileSeparator);
    return this.makeDisplayJAIPanel(str[str.length-1], filename);
  }

  /**
   *  clearAll()
   *
   *  removes all ... not much more to it
   */
  public void clearAll() 
  {
    if (!noFiles) 
    {
      tabbedPane.removeAll();
      tabbedPanel.remove(tabbedPane);
    }
    init();
  }

  /**
   *  init()
   *
   *  initalizes the startup frame contents.
   */
  private void init() 
  {
    noFiles = true;
    if (emptyFilePanel == null) 
    {
      emptyFileArea = new DisplayJAI();
      emptyFileArea.setTransferHandler(transferHandler);
      JScrollPane fileScrollPane = new JScrollPane(emptyFileArea);
      emptyFilePanel = new JPanel(new BorderLayout(), false);
      emptyFilePanel.add(fileScrollPane, BorderLayout.CENTER);
    }
    tabbedPanel.add(emptyFilePanel, BorderLayout.CENTER);
    tabbedPanel.repaint();
  }

  /**
   *  makeDisplayJAIPanel()
   *
   *  creates a new tab (containing a scrollable displayJAI)
   */
  protected DisplayJAI makeDisplayJAIPanel(String name, String toolTip) 
  {
    DisplayJAI fileArea = new DisplayJAI();
    fileArea.setTransferHandler(transferHandler);
    JScrollPane fileScrollPane = new JScrollPane(fileArea);
    tabbedPane.addTab(name, null, (JComponent)fileScrollPane, toolTip);
    tabbedPane.setSelectedComponent((JComponent)fileScrollPane);
    return fileArea;
  }
}

//******************************************************************************
class FileToDisplayJAITransferHandler extends  TransferHandler 
{
  private TabbedPaneController tpc;
  private DisplayJAI source;
  
  //local instances of the variables (well aliases) to save looking up and 
  //writing long(er) code.
  private DataFlavor fileListFlavor;
  private DataFlavor imageFlavor;

  /** 
   *  Constructor
   */
  FileToDisplayJAITransferHandler(TabbedPaneController t) 
  {
    tpc = t;
    fileListFlavor = DataFlavor.javaFileListFlavor;
    imageFlavor = DataFlavor.imageFlavor;
  }

  /**
   *  importData()
   *
   *  Overrides TransferHandler.importData() ... this is another function that
   *  does not strictly make sense to me
   *
   *  
   *
   */
  public boolean importData(JComponent c, Transferable t) 
  {
    DisplayJAI tc;

    //check to see if the dragged in files can be imported and if not fails
    if (!canImport(c, t.getTransferDataFlavors())) 
    {
      //fail
      return false;
    }
    
    //if the return above was not triggered importing must be possible.
    try 
    {
      //so this has flavor that can be imported, which means it can be either
      // file list flavor OR
      if (this.hasFlavor(this.fileListFlavor, t.getTransferDataFlavors())) 
      {
        //generate a list of the selected files (can of course be just the one 
        //file to be loaded)
        java.util.List files = (java.util.List)t.getTransferData(this.fileListFlavor);
        
        for (int i = 0; i < files.size(); i++) 
        {
          //for each file get the file, add a tab with the filename as the tab
          File file = (File)files.get(i);
          tc = tpc.addTab(file.toString());
          //and try to load the file
          try 
          {
            //Sun's documentation highlights that this should be a separate thread.
            //bur for simplicity reasons they and i did not do this.
            PlanarImage image = JAI.create("fileload", file.toString());
            tc.set(image, 0, 0);
          }
          catch (Exception x) 
          {
            System.out.println("importData: Unable to read from file " + file.toString());
          } 
        }
        return true;
      } 
      //... an image flavor
      else if (this.hasFlavor(this.imageFlavor, t.getTransferDataFlavors())) 
      {
        //not sure what this section does (well besides the obvious cast thing)
        //my guess is allow dragging from one panel to another 
        tc = (DisplayJAI)c;
        return true;
      }
    }
    catch (UnsupportedFlavorException ufe) 
    {
      System.out.println("importData: unsupported data flavor");
    } 
    catch (IOException ieo) 
    {
      System.out.println("importData: I/O exception");
    }
    return false;
  }

  /**
   *  canImport()
   *
   *  Overrides TransferHandler.canImport() - which is why the JComponent is not
   *  used in this class. Which means the test is really broken (from the doc):
   *  "Indicates whether a component would accept an import of the given set of
   *    data flavors prior to actually attempting to import it." However since
   *  what the component can take is never checked. This does of course seem 
   *  silly but consider this from the stance of the developer (of the 
   *  application) - s/he knows what container the program has so assumptions
   *  can safely be made. 
   *  The language designer (or the application designer with more nack for 
   *  customizations) focuses on fault tolerance, so i guess a check what the 
   *  component can actually take, based on what it is or some property would 
   *  make more sense ... sadly i have yet to find any markers in my Display JAI
   *  and JComponent has not got a function that does this... at least not that
   *  i can see.
   *
   *  Oh, and after writing the doc on the last function (hasFlavor())) i
   *  noticed that this functions also has a logic error in the sun version:
   *
   *  if (hasFlavor(this.fileListFlavor, flavors))
   *  {
   *    return true; 
   *  }
   * if (hasFlavor(this.imageFlavor, flavors))
   * { 
   *   return true; 
   * }
   * return false;
   *
   *  Which returns a true if the first flavour of the type first checked 
   *  returns a true. I should mention that i don't really know that it needs 
   *  all this checking, but seeing as they bothered, one would assume yes.
   *  Mind you this flaw runs quite deep, because it doesn't need to succeed on
   *  both tests as the importData() function above caters for both flavors.
   *
   */
  public boolean canImport(JComponent c, DataFlavor[] flavors) 
  {
    if (
        (hasFlavor(this.fileListFlavor, flavors)) 
        || 
        (hasFlavor(this.imageFlavor, flavors))
        )
    { 
      return true; 
    }
    return false;
  }

  /**
   *  hasFlavor()
   *
   *  gets the flavor you want to check against (shouldHave) and the flavors
   *  that you actually have. Then it looks if the flavors you passed in. The
   *  example from the Sun page has a logic bug at this point:
   *
   *  for (int i = 0; i < flavors.length; i++) 
   *  {
   *    if (shouldHave.equals(flavors[i])) 
   *    {
   *      return true;
   *    }
   *  }
   *  return false;
   *
   *  Granted i've dropped my shouldHave in there instead of a specific flavor,
   *  but this does not change the logic flow. The Sun sample will do a return
   *  true IF the first flavor passed in matches and ignore all subsequent ones.
   *  Clearly that isn't ... fault resistant. Most of the time this isn't an 
   *  issue; but that's hardly the point.
   *
   *  So this is a more flexible function, and is (more) fault resistant. It 
   *  only returns true if ALL flavors passed are ok, and even a single one will
   *  cause a false;
   *
   */
  private boolean hasFlavor(DataFlavor shouldHave, DataFlavor[] flavors) 
  {
    for (int i = 0; i < flavors.length; i++) 
    {
      if (!shouldHave.equals(flavors[i])) 
      {
        return false;
      }
    }
    return true;
  }
}