Source for javax.swing.TransferHandler

   1: /* TransferHandler.java --
   2:    Copyright (C) 2004, 2005, 2006, Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing;
  40: 
  41: import java.awt.Toolkit;
  42: import java.awt.datatransfer.Clipboard;
  43: import java.awt.datatransfer.DataFlavor;
  44: import java.awt.datatransfer.Transferable;
  45: import java.awt.datatransfer.UnsupportedFlavorException;
  46: import java.awt.dnd.DragGestureEvent;
  47: import java.awt.dnd.DragGestureListener;
  48: import java.awt.dnd.DragGestureRecognizer;
  49: import java.awt.dnd.DragSource;
  50: import java.awt.dnd.DragSourceContext;
  51: import java.awt.dnd.DragSourceDragEvent;
  52: import java.awt.dnd.DragSourceDropEvent;
  53: import java.awt.dnd.DragSourceEvent;
  54: import java.awt.dnd.DragSourceListener;
  55: import java.awt.event.ActionEvent;
  56: import java.awt.event.InputEvent;
  57: import java.awt.event.MouseEvent;
  58: import java.beans.BeanInfo;
  59: import java.beans.IntrospectionException;
  60: import java.beans.Introspector;
  61: import java.beans.PropertyDescriptor;
  62: import java.io.IOException;
  63: import java.io.Serializable;
  64: import java.lang.reflect.Method;
  65: 
  66: public class TransferHandler implements Serializable
  67: {
  68: 
  69:   /**
  70:    * An implementation of {@link Transferable} that can be used to export
  71:    * data from a component's property.
  72:    */
  73:   private static class PropertyTransferable
  74:     implements Transferable
  75:   {
  76:     /**
  77:      * The component from which we export.
  78:      */
  79:     private JComponent component;
  80: 
  81:     /**
  82:      * The property descriptor of the property that we handle.
  83:      */
  84:     private PropertyDescriptor property;
  85: 
  86:     /**
  87:      * Creates a new PropertyTransferable.
  88:      *
  89:      * @param c the component from which we export
  90:      * @param prop the property from which we export
  91:      */
  92:     PropertyTransferable(JComponent c, PropertyDescriptor prop)
  93:     {
  94:       component = c;
  95:       property = prop;
  96:     }
  97: 
  98:     /**
  99:      * Returns the data flavors supported by the Transferable.
 100:      *
 101:      * @return the data flavors supported by the Transferable
 102:      */
 103:     public DataFlavor[] getTransferDataFlavors()
 104:     {
 105:       DataFlavor[] flavors;
 106:       Class propClass = property.getPropertyType();
 107:       String mime = DataFlavor.javaJVMLocalObjectMimeType + "; class="
 108:                     + propClass.getName();
 109:       try
 110:         {
 111:           DataFlavor flavor = new DataFlavor(mime);
 112:           flavors = new DataFlavor[]{ flavor };
 113:         }
 114:       catch (ClassNotFoundException ex)
 115:         {
 116:           flavors = new DataFlavor[0];
 117:         }
 118:       return flavors;
 119:     }
 120: 
 121:     /**
 122:      * Returns <code>true</code> when the specified data flavor is supported,
 123:      * <code>false</code> otherwise.
 124:      *
 125:      * @return <code>true</code> when the specified data flavor is supported,
 126:      *         <code>false</code> otherwise
 127:      */
 128:     public boolean isDataFlavorSupported(DataFlavor flavor)
 129:     {
 130:       Class propClass = property.getPropertyType();
 131:       return flavor.getPrimaryType().equals("application")
 132:         && flavor.getSubType().equals("x-java-jvm-local-objectref")
 133:         && propClass.isAssignableFrom(flavor.getRepresentationClass());
 134:     }
 135: 
 136:     /**
 137:      * Returns the actual transfer data.
 138:      *
 139:      * @param flavor the data flavor
 140:      *
 141:      * @return the actual transfer data
 142:      */
 143:     public Object getTransferData(DataFlavor flavor)
 144:       throws UnsupportedFlavorException, IOException
 145:     {
 146:       if (isDataFlavorSupported(flavor))
 147:         {
 148:           Method getter = property.getReadMethod();
 149:           Object o;
 150:           try
 151:             {
 152:               o = getter.invoke(component, null);
 153:               return o;
 154:             }
 155:           catch (Exception ex)
 156:             {
 157:               throw new IOException("Property read failed: "
 158:                                     + property.getName());
 159:             }
 160:         }
 161:       else
 162:         throw new UnsupportedFlavorException(flavor);
 163:     }
 164:   }
 165: 
 166:   static class TransferAction extends AbstractAction
 167:   {
 168:     private String command;
 169: 
 170:     public TransferAction(String command)
 171:     {
 172:       super(command);
 173:       this.command = command;
 174:     }
 175:     
 176:     public void actionPerformed(ActionEvent event)
 177:     {
 178:       JComponent component = (JComponent) event.getSource();
 179:       TransferHandler transferHandler = component.getTransferHandler();
 180:       Clipboard clipboard = getClipboard(component);
 181: 
 182:       if (clipboard == null)
 183:     {
 184:       // Access denied!
 185:       Toolkit.getDefaultToolkit().beep();
 186:       return;
 187:     }
 188: 
 189:       if (command.equals(COMMAND_COPY))
 190:     transferHandler.exportToClipboard(component, clipboard, COPY);
 191:       else if (command.equals(COMMAND_CUT))
 192:     transferHandler.exportToClipboard(component, clipboard, MOVE);
 193:       else if (command.equals(COMMAND_PASTE))
 194:     {
 195:       Transferable transferable = clipboard.getContents(null);
 196: 
 197:       if (transferable != null)
 198:         transferHandler.importData(component, transferable);
 199:     }
 200:     }
 201:   
 202:     /**
 203:      * Get the system cliboard or null if the caller isn't allowed to
 204:      * access the system clipboard.
 205:      * 
 206:      * @param component a component, used to get the toolkit.
 207:      * @return the clipboard
 208:      */
 209:     private static Clipboard getClipboard(JComponent component)
 210:     {
 211:       try
 212:     {
 213:       return component.getToolkit().getSystemClipboard();
 214:     }
 215:       catch (SecurityException se)
 216:     {
 217:       return null;
 218:     }
 219:     }
 220:   }
 221: 
 222:   private static class SwingDragGestureRecognizer extends DragGestureRecognizer
 223:   {
 224: 
 225:     protected SwingDragGestureRecognizer(DragGestureListener dgl)
 226:     {
 227:       super(DragSource.getDefaultDragSource(), null, NONE, dgl);
 228:     }
 229: 
 230:     void gesture(JComponent c, MouseEvent e, int src, int drag)
 231:     {
 232:       setComponent(c);
 233:       setSourceActions(src);
 234:       appendEvent(e);
 235:       fireDragGestureRecognized(drag, e.getPoint());
 236:     }
 237: 
 238:     protected void registerListeners()
 239:     {
 240:       // Nothing to do here.
 241:     }
 242: 
 243:     protected void unregisterListeners()
 244:     {
 245:       // Nothing to do here.
 246:     }
 247:     
 248:   }
 249: 
 250:   private static class SwingDragHandler
 251:     implements DragGestureListener, DragSourceListener
 252:   {
 253: 
 254:     private boolean autoscrolls;
 255: 
 256:     public void dragGestureRecognized(DragGestureEvent e)
 257:     {
 258:       JComponent c = (JComponent) e.getComponent();
 259:       TransferHandler th = c.getTransferHandler();
 260:       Transferable t = th.createTransferable(c);
 261:       if (t != null)
 262:         {
 263:           autoscrolls = c.getAutoscrolls();
 264:           c.setAutoscrolls(false);
 265:           try
 266:             {
 267:               e.startDrag(null, t, this);
 268:               return;
 269:             }
 270:           finally
 271:             {
 272:               c.setAutoscrolls(autoscrolls);
 273:             }
 274:         }
 275:       th.exportDone(c, t, NONE);
 276:     }
 277: 
 278:     public void dragDropEnd(DragSourceDropEvent e)
 279:     {
 280:       DragSourceContext ctx = e.getDragSourceContext();
 281:       JComponent c = (JComponent) ctx.getComponent();
 282:       TransferHandler th = c.getTransferHandler();
 283:       if (e.getDropSuccess())
 284:         {
 285:           th.exportDone(c, ctx.getTransferable(), e.getDropAction());
 286:         }
 287:       else
 288:         {
 289:           th.exportDone(c, ctx.getTransferable(), e.getDropAction());
 290:         }
 291:       c.setAutoscrolls(autoscrolls);
 292:     }
 293: 
 294:     public void dragEnter(DragSourceDragEvent e)
 295:     {
 296:       // Nothing to do here.
 297:     }
 298: 
 299:     public void dragExit(DragSourceEvent e)
 300:     {
 301:       // Nothing to do here.
 302:     }
 303: 
 304:     public void dragOver(DragSourceDragEvent e)
 305:     {
 306:       // Nothing to do here.
 307:     }
 308: 
 309:     public void dropActionChanged(DragSourceDragEvent e)
 310:     {
 311:       // Nothing to do here.
 312:     }
 313:     
 314:   }
 315: 
 316:   private static final long serialVersionUID = -967749805571669910L;
 317: 
 318:   private static final String COMMAND_COPY = "copy";
 319:   private static final String COMMAND_CUT = "cut";
 320:   private static final String COMMAND_PASTE = "paste";
 321:   
 322:   public static final int NONE = 0;
 323:   public static final int COPY = 1;
 324:   public static final int MOVE = 2;
 325:   public static final int COPY_OR_MOVE = 3;
 326: 
 327:   private static Action copyAction = new TransferAction(COMMAND_COPY);
 328:   private static Action cutAction = new TransferAction(COMMAND_CUT);
 329:   private static Action pasteAction = new TransferAction(COMMAND_PASTE);
 330:   
 331:   private int sourceActions;
 332:   private Icon visualRepresentation;
 333: 
 334:   /**
 335:    * The name of the property into/from which this TransferHandler
 336:    * imports/exports. 
 337:    */
 338:   private String propertyName;
 339: 
 340:   /**
 341:    * The DragGestureRecognizer for Swing.
 342:    */
 343:   private SwingDragGestureRecognizer recognizer;
 344: 
 345:   public static Action getCopyAction()
 346:   {
 347:     return copyAction;
 348:   }
 349: 
 350:   public static Action getCutAction()
 351:   {
 352:     return cutAction;
 353:   }
 354: 
 355:   public static Action getPasteAction()
 356:   {
 357:     return pasteAction;
 358:   }
 359: 
 360:   protected TransferHandler()
 361:   {
 362:     this.sourceActions = NONE;
 363:   }
 364: 
 365:   public TransferHandler(String property)
 366:   {
 367:     propertyName = property;
 368:     this.sourceActions = property != null ? COPY : NONE;
 369:   }
 370: 
 371:   /**
 372:    * Returns <code>true</code> if the data in this TransferHandler can be
 373:    * imported into the specified component. This will be the case when:
 374:    * <ul>
 375:    *   <li>The component has a readable and writable property with the property
 376:    *   name specified in the TransferHandler constructor.</li>
 377:    *   <li>There is a dataflavor with a mime type of
 378:    *     <code>application/x-java-jvm-local-object-ref</code>.</li>
 379:    *   <li>The dataflavor's representation class matches the class of the
 380:    *    property in the component.</li>
 381:    * </li>
 382:    *
 383:    * @param c the component to check
 384:    * @param flavors the possible data flavors
 385:    *
 386:    * @return <code>true</code> if the data in this TransferHandler can be
 387:    *         imported into the specified component, <code>false</code>
 388:    *         otherwise
 389:    */
 390:   public boolean canImport(JComponent c, DataFlavor[] flavors)
 391:   {
 392:     PropertyDescriptor propDesc = getPropertyDescriptor(c);
 393:     boolean canImport = false;
 394:     if (propDesc != null)
 395:       {
 396:         // Check if the property is writable. The readable check is already
 397:         // done in getPropertyDescriptor().
 398:         Method writer = propDesc.getWriteMethod();
 399:         if (writer != null)
 400:           {
 401:             Class[] params = writer.getParameterTypes();
 402:             if (params.length == 1)
 403:               {
 404:                 // Number of parameters ok, now check mime type and
 405:                 // representation class.
 406:                 DataFlavor flavor = getPropertyDataFlavor(params[0], flavors);
 407:                 if (flavor != null)
 408:                   canImport = true;
 409:               }
 410:           }
 411:       }
 412:     return canImport;
 413:   }
 414: 
 415:   /**
 416:    * Creates a {@link Transferable} that can be used to export data
 417:    * from the specified component.
 418:    *
 419:    * This method returns <code>null</code> when the specified component
 420:    * doesn't have a readable property that matches the property name
 421:    * specified in the <code>TransferHandler</code> constructor.
 422:    *
 423:    * @param c the component to create a transferable for
 424:    *
 425:    * @return a {@link Transferable} that can be used to export data
 426:    *         from the specified component, or null if the component doesn't
 427:    *         have a readable property like the transfer handler
 428:    */
 429:   protected Transferable createTransferable(JComponent c) 
 430:   {
 431:     Transferable transferable = null;
 432:     if (propertyName != null)
 433:       {
 434:         PropertyDescriptor prop = getPropertyDescriptor(c);
 435:         if (prop != null)
 436:           transferable = new PropertyTransferable(c, prop);
 437:       }
 438:     return transferable;
 439:   }
 440: 
 441:   public void exportAsDrag(JComponent c, InputEvent e, int action)
 442:   {
 443:     int src = getSourceActions(c);
 444:     int drag = src & action;
 445:     if (! (e instanceof MouseEvent))
 446:       {
 447:         drag = NONE;
 448:       }
 449:     if (drag != NONE)
 450:       {
 451:         if (recognizer == null)
 452:           {
 453:             SwingDragHandler ds = new SwingDragHandler();
 454:             recognizer = new SwingDragGestureRecognizer(ds);
 455:           }
 456:         recognizer.gesture(c, (MouseEvent) e, src, drag);
 457:       }
 458:     else
 459:       {
 460:         exportDone(c, null, NONE);
 461:       }
 462:   }
 463: 
 464:   /**
 465:    * This method is invoked after data has been exported.
 466:    * Subclasses should implement this method to remove the data that has been
 467:    * transferred when the action was <code>MOVE</code>.
 468:    *
 469:    * The default implementation does nothing because MOVE is not supported.
 470:    *
 471:    * @param c the source component
 472:    * @param data the data that has been transferred or <code>null</code>
 473:    *        when the action is NONE
 474:    * @param action the action that has been performed
 475:    */
 476:   protected void exportDone(JComponent c, Transferable data, int action)
 477:   {
 478:     // Nothing to do in the default implementation.
 479:   }
 480: 
 481:   /**
 482:    * Exports the property of the component <code>c</code> that was
 483:    * specified for this TransferHandler to the clipboard, performing
 484:    * the specified action.
 485:    *
 486:    * This will check if the action is allowed by calling
 487:    * {@link #getSourceActions(JComponent)}. If the action is not allowed,
 488:    * then no export is performed.
 489:    *
 490:    * In either case the method {@link #exportDone} will be called with
 491:    * the action that has been performed, or {@link #NONE} if the action
 492:    * was not allowed or could otherwise not be completed.
 493:    * Any IllegalStateException that is thrown by the Clipboard due to
 494:    * beeing unavailable will be propagated through this method.
 495:    *
 496:    * @param c the component from which to export
 497:    * @param clip the clipboard to which the data will be exported
 498:    * @param action the action to perform
 499:    *
 500:    * @throws IllegalStateException when the clipboard is not available
 501:    */
 502:   public void exportToClipboard(JComponent c, Clipboard clip, int action) 
 503:     throws IllegalStateException
 504:   {
 505:     action &= getSourceActions(c);
 506:     Transferable transferable = createTransferable(c);
 507:     if (transferable != null && action != NONE)
 508:       {
 509:         try
 510:           {
 511:             clip.setContents(transferable, null);
 512:             exportDone(c, transferable, action);
 513:           }
 514:         catch (IllegalStateException ex)
 515:           {
 516:             exportDone(c, transferable, NONE);
 517:             throw ex;
 518:           }
 519:       }
 520:     else
 521:       exportDone(c, null, NONE);
 522:   } 
 523: 
 524:   public int getSourceActions(JComponent c)
 525:   {
 526:     return sourceActions;
 527:   }
 528: 
 529:   public Icon getVisualRepresentation(Transferable t)
 530:   {
 531:     return visualRepresentation;
 532:   }
 533: 
 534:   /**
 535:    * Imports the transfer data represented by <code>t</code> into the specified
 536:    * component <code>c</code> by setting the property of this TransferHandler
 537:    * on that component. If this succeeds, this method returns
 538:    * <code>true</code>, otherwise <code>false</code>.
 539:    * 
 540:    *
 541:    * @param c the component to import into
 542:    * @param t the transfer data to import
 543:    *
 544:    * @return <code>true</code> if the transfer succeeds, <code>false</code>
 545:    *         otherwise
 546:    */
 547:   public boolean importData(JComponent c, Transferable t) 
 548:   {
 549:     boolean ok = false;
 550:     PropertyDescriptor prop = getPropertyDescriptor(c);
 551:     if (prop != null)
 552:       {
 553:         Method writer = prop.getWriteMethod();
 554:         if (writer != null)
 555:           {
 556:             Class[] params = writer.getParameterTypes();
 557:             if (params.length == 1)
 558:               {
 559:                 DataFlavor flavor = getPropertyDataFlavor(params[0],
 560:                                                    t.getTransferDataFlavors());
 561:                 if (flavor != null)
 562:                   {
 563:                     try
 564:                       {
 565:                         Object value = t.getTransferData(flavor);
 566:                         writer.invoke(c, new Object[]{ value });
 567:                         ok = true;
 568:                       }
 569:                     catch (Exception ex)
 570:                       {
 571:                         // If anything goes wrong here, do nothing and return
 572:                         // false;
 573:                       }
 574:                   }
 575:               }
 576:           }
 577:       }
 578:     return ok;
 579:   }
 580: 
 581:   /**
 582:    * Returns the property descriptor for the property of this TransferHandler
 583:    * in the specified component, or <code>null</code> if no such property
 584:    * exists in the component. This method only returns properties that are
 585:    * at least readable (that is, it has a public no-arg getter method).
 586:    *
 587:    * @param c the component to check
 588:    *
 589:    * @return the property descriptor for the property of this TransferHandler
 590:    *         in the specified component, or <code>null</code> if no such
 591:    *         property exists in the component
 592:    */
 593:   private PropertyDescriptor getPropertyDescriptor(JComponent c)
 594:   {
 595:     PropertyDescriptor prop = null;
 596:     if (propertyName != null)
 597:       {
 598:         Class clazz = c.getClass();
 599:         BeanInfo beanInfo;
 600:         try
 601:           {
 602:             beanInfo = Introspector.getBeanInfo(clazz);
 603:           }
 604:         catch (IntrospectionException ex)
 605:           {
 606:             beanInfo = null;
 607:           }
 608:         if (beanInfo != null)
 609:           {
 610:             PropertyDescriptor[] props = beanInfo.getPropertyDescriptors();
 611:             for (int i = 0; i < props.length && prop == null; i++)
 612:               {
 613:                 PropertyDescriptor desc = props[i];
 614:                 if (desc.getName().equals(propertyName))
 615:                   {
 616:                     Method reader = desc.getReadMethod();
 617:                     if (reader != null)
 618:                       {
 619:                         Class[] params = reader.getParameterTypes();
 620:                         if (params == null || params.length == 0)
 621:                           prop = desc;
 622:                       }
 623:                   }
 624:               }
 625:           }
 626:       }
 627:     return prop;
 628:   }
 629: 
 630:   /**
 631:    * Searches <code>flavors</code> to find a suitable data flavor that
 632:    * has the mime type application/x-java-jvm-local-objectref and a
 633:    * representation class that is the same as the specified <code>clazz</code>.
 634:    * When no such data flavor is found, this returns <code>null</code>.
 635:    *
 636:    * @param clazz the representation class required for the data flavor
 637:    * @param flavors the possible data flavors
 638:    *
 639:    * @return the suitable data flavor or null if none is found
 640:    */
 641:   private DataFlavor getPropertyDataFlavor(Class clazz, DataFlavor[] flavors)
 642:   {
 643:     DataFlavor found = null;
 644:     for (int i = 0; i < flavors.length && found == null; i++)
 645:       {
 646:         DataFlavor flavor = flavors[i];
 647:         if (flavor.getPrimaryType().equals("application")
 648:             && flavor.getSubType().equals("x-java-jvm-local-objectref")
 649:             && clazz.isAssignableFrom(flavor.getRepresentationClass()))
 650:           found = flavor;
 651:       }
 652:     return found;
 653:   }
 654: }