001/*
002  The Broad Institute
003  SOFTWARE COPYRIGHT NOTICE AGREEMENT
004  This software and its documentation are copyright (2003-2008) by the
005  Broad Institute/Massachusetts Institute of Technology. All rights are
006  reserved.
007
008  This software is supplied without any warranty or guaranteed support
009  whatsoever. Neither the Broad Institute nor MIT can be responsible for its
010  use, misuse, or functionality.
011*/
012
013package ca.bc.webarts.widgets.treetable;
014// package org.genomespace.gsui.ui.treetable;
015
016import java.awt.AlphaComposite;
017import java.awt.Color;
018import java.awt.Component;
019import java.awt.GradientPaint;
020import java.awt.Graphics;
021import java.awt.Graphics2D;
022import java.awt.Point;
023import java.awt.Rectangle;
024import java.awt.SystemColor;
025import java.awt.datatransfer.DataFlavor;
026import java.awt.datatransfer.Transferable;
027import java.awt.datatransfer.UnsupportedFlavorException;
028import java.awt.dnd.DnDConstants;
029import java.awt.dnd.DragGestureEvent;
030import java.awt.dnd.DragGestureListener;
031import java.awt.dnd.DragSource;
032import java.awt.dnd.DragSourceDragEvent;
033import java.awt.dnd.DragSourceDropEvent;
034import java.awt.dnd.DragSourceEvent;
035import java.awt.dnd.DragSourceListener;
036import java.awt.dnd.DropTarget;
037import java.awt.dnd.DropTargetDragEvent;
038import java.awt.dnd.DropTargetDropEvent;
039import java.awt.dnd.DropTargetEvent;
040import java.awt.event.MouseAdapter;
041import java.awt.event.MouseEvent;
042import java.awt.event.WindowAdapter;
043import java.awt.event.WindowEvent;
044import java.awt.image.BufferedImage;
045import java.util.ArrayList;
046import java.util.List;
047import javax.swing.*;
048import javax.swing.table.JTableHeader;
049import javax.swing.table.TableCellRenderer;
050import javax.swing.table.TableColumnModel;
051import javax.swing.tree.*;
052
053import java.awt.dnd.DragGestureRecognizer;
054import java.awt.event.InputEvent;
055
056/*
057import org.genomespace.datamanager.core.GSFileMetadata;
058import org.genomespace.gsui.ui.graphics.draggable.TransferableTreePath;
059import org.genomespace.gsui.ui.maindisplay.GSFileTransferUtility;
060import org.genomespace.gsui.ui.maindisplay.GenomeSpaceUI;
061import org.genomespace.gsui.ui.maindisplay.IconManager;
062import org.genomespace.gsui.ui.project.GSProjectDirModel;
063import org.genomespace.gsui.ui.table.*;
064*/
065
066import ca.bc.webarts.widgets.Util;
067
068/**
069 * A tree table that supports sorting the columns
070 *
071 *
072 */
073public class SortableTreeTable extends JTreeTable implements DragSourceListener, DragGestureListener
074{
075
076    /**
077   * a test JFrame.
078   */
079  protected static JFrame appFrame_ = null;
080
081
082  JTree tree;
083
084  private TreePath _pathSource;  // The path being dragged
085
086  private BufferedImage _imgGhost;  // The 'drag image'
087
088  private Point _ptOffset = new Point();  // Where, in the drag image, the mouse
089  // was clicked
090
091  SortableHeaderRenderer sortableHeaderRenderer;
092  public static int ASCENDING = SortableHeaderRenderer.ASCENDING;
093  public static int DESCENDING = SortableHeaderRenderer.DESCENDING;
094
095  private boolean inDrag = false;
096
097  SortableTreeTableModel model;
098
099  DragSource dragSource;
100
101  private boolean dragLeafsOnly = false;  // changed for GS from leafs only 11/30/11
102
103
104    private static void  s(String s) { System.out.println("start: "+s);}
105  private static void e(String s) { System.out.println("end: "+s);}
106
107
108  public SortableTreeTable( SortableTreeTableModel m )
109  {
110    super( m );
111    this.model = m;
112    setSelectionMode( ListSelectionModel.SINGLE_INTERVAL_SELECTION );
113
114    DropTarget dropTarget = new DropTarget( this, new TreeDropTargetListener() );
115    dropTarget.setDefaultActions( DnDConstants.ACTION_COPY_OR_MOVE );
116
117    tree = ( JTree ) getDefaultRenderer( org.jdesktop.swingx.treetable.TreeTableModel.class );
118    tree.setCellRenderer( new ClippedTreeCellRenderer() );
119
120    dragSource = DragSource.getDefaultDragSource();
121
122    DragGestureRecognizer dgr = dragSource.createDefaultDragGestureRecognizer( this, DnDConstants.ACTION_COPY_OR_MOVE, this );
123    dgr.setSourceActions( dgr.getSourceActions() & ~InputEvent.BUTTON3_MASK );
124
125    tree.getSelectionModel().setSelectionMode ( TreeSelectionModel.SINGLE_TREE_SELECTION );
126    setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
127
128    sortableHeaderRenderer = new SortableHeaderRenderer( this, m )
129    {
130      public void setSortingStatus( int column, int status )
131      {
132        try
133        {
134          TreePath selectionPath = tree.getSelectionPath();
135          List expandedPaths = new ArrayList();
136
137          int rc = tree.getRowCount();
138          for ( int i = 0; i < rc; i++ )
139          {
140            if ( tree.isExpanded( i ) )
141            {
142              expandedPaths.add( tree.getPathForRow( i ) );
143            }
144          }
145
146          super.setSortingStatus( column, status );
147          for ( int i = 0; i < expandedPaths.size(); i++ )
148          {
149            TreePath path = ( TreePath ) expandedPaths.get( i );
150            tree.expandPath( path );
151          }
152          if ( selectionPath != null )
153          {
154            tree.setSelectionPath( selectionPath );
155          }
156        }
157        catch ( Throwable t )
158        { }
159      }
160    };
161    sortableHeaderRenderer.setSortingStatus(0, SortableHeaderRenderer.ASCENDING );
162  }
163
164
165  public static void main(String [] args)
166  {
167    appFrame_ = new JFrame();
168    appFrame_.getContentPane().setLayout(new java.awt.BorderLayout());
169    appFrame_.setTitle("SortableTreeTable TEST Window.");
170    final SortableTreeTable myTestTable = new SortableTreeTable(SampleSortableTreeTabelModelImpl.getInstance());
171
172    appFrame_.addWindowListener(
173      new WindowAdapter()
174      {
175        public void windowClosing(WindowEvent e)
176        {
177          System.exit(0);
178        }
179      });
180
181    appFrame_.getContentPane().add(myTestTable);
182    appFrame_.pack();
183    //KiwiUtils.centerWindow(appFrame_);
184    appFrame_.toFront();
185    appFrame_.setVisible(true);
186    appFrame_.repaint();
187  }
188
189
190  public void setSortingStatus( int column, int status )
191  {
192    if ( status != ASCENDING && status != DESCENDING )
193    {
194      throw new IllegalArgumentException( "Invalid sort order" );
195    }
196    sortableHeaderRenderer.setSortingStatus( column, status );
197  }
198
199  public Object getValueAt( int row, int column )
200  {
201          s("getValueAt("+row+","+column+")" );
202    try
203    {
204      return super.getValueAt( row, column );
205    }
206    catch ( Throwable t )
207    {
208      return null;
209    }
210  }
211
212  public void setDragLeafsOnly( boolean b )
213  {
214    dragLeafsOnly = b;
215  }
216
217  public final void dragGestureRecognized( final DragGestureEvent e )
218  {
219
220    final Point ptDragOrigin = e.getDragOrigin();
221    int row = this.getSelectedRow();
222
223    if ( getSelectedColumn() != 0 )
224    {
225      return;
226    }
227
228    final TreePath path = getPathForLocation( ptDragOrigin.x, ptDragOrigin.y );
229    if ( path == null )
230    {
231      return;
232    }
233    if ( isRootPath( path ) )
234    {      // Ignore user trying to drag the root node
235      return;
236    }
237
238    Object comp = path.getLastPathComponent();
239    if ( dragLeafsOnly && !tree.getModel().isLeaf( comp ) )
240    {
241      return;
242    }
243
244    System.out.println( "Dragging " + comp );
245
246    // Work out the offset of the drag point from the TreePath bounding
247    // rectangle origin
248    final Rectangle raPath = tree.getPathBounds( path );
249
250    _ptOffset.setLocation( raPath.x - ptDragOrigin.x, raPath.y - ptDragOrigin.y );
251
252    // Get the cell renderer (which is a JLabel) for the path being dragged
253    final JLabel lbl = ( JLabel ) tree.getCellRenderer()    // tree
254    // value
255    // isSelected (dont want a colored background)
256    // isExpanded
257    // isLeaf
258    // row (not important for rendering)
259    // hasFocus (dont want a focus rectangle)
260    .getTreeCellRendererComponent( tree, path.getLastPathComponent(), false, isExpanded( path ), tree.getModel().isLeaf( path.getLastPathComponent() ), 0, false );
261    lbl.setSize( ( int ) raPath.getWidth(), ( int ) raPath.getHeight() );    // <--
262    // The
263    // layout
264    // manager
265    // would
266    // normally
267    // do
268    // this
269
270    // Get a buffered image of the selection for dragging a ghost image
271    _imgGhost = new BufferedImage( ( int ) raPath.getWidth(), ( int ) raPath.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE );
272    final Graphics2D g2 = _imgGhost.createGraphics();
273
274    // Ask the cell renderer to paint itself into the BufferedImage
275    g2.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC, 0.5f ) );    // Make
276    // the
277    // image
278    // ghostlike
279    lbl.paint( g2 );
280
281    // Now paint a gradient UNDER the ghosted JLabel text (but not under the
282    // icon if any)
283    // Note: this will need tweaking if your icon is not positioned to the
284    // left of the text
285    final Icon icon = lbl.getIcon();
286    final int nStartOfText = ( icon == null ) ? 0 : icon.getIconWidth() + lbl.getIconTextGap();
287    g2.setComposite( AlphaComposite.getInstance( AlphaComposite.DST_OVER, 0.5f ) );    // Make the gradient ghostlike
288    g2.setPaint( new GradientPaint( nStartOfText, 0, SystemColor.controlShadow, getWidth(), 0, Color.white ) );
289    g2.fillRect( nStartOfText, 0, getWidth(), _imgGhost.getHeight() );
290
291    g2.dispose();
292
293    tree.setSelectionPath( path );    // Select this path in the tree
294    // Wrap the path being transferred into a Transferable object
295    final Transferable transferable = new TransferableTreePath( path );
296
297    // Remember the path being dragged (because if it is being moved, we
298    // will have to delete it later)
299    _pathSource = path;
300
301    // We pass our drag image just in case it IS supported by the platform
302
303    _ptOffset.y = 0;
304    try
305    {
306      e.startDrag( DragSource.DefaultCopyDrop, _imgGhost, _ptOffset, transferable, this );
307    }
308    catch ( Exception dragStartE )
309    {
310      dragStartE.printStackTrace();
311    }
312
313  }
314
315  // Interface: DragSourceListener
316  public final void dragEnter( DragSourceDragEvent e )
317  {
318    inDrag = true;
319  }
320
321  public final void dragOver( final DragSourceDragEvent e )
322  {
323    inDrag = true;
324  }
325
326  public final void dragExit( DragSourceEvent e )
327  {
328
329    inDrag = false;
330    java.awt.Window window = javax.swing.SwingUtilities.getWindowAncestor( tree );
331    if ( window != null )
332    {
333      window.repaint();
334    }
335  }
336
337
338  public final void dropActionChanged( DragSourceDragEvent e )
339  {
340  }
341
342
343  public final void dragDropEnd( final DragSourceDropEvent e )
344  {
345    inDrag = false;
346    if ( e.getDropSuccess() )
347    {
348      final int nAction = e.getDropAction();
349      if ( nAction == DnDConstants.ACTION_MOVE )
350      {        // The dragged item
351        // (_pathSource) has been
352        // inserted at the target
353        // selected by the user.
354        // Now it is time to delete it from its original location.
355
356        // .
357        // .. ask your TreeModel to delete the node
358        // .
359
360        _pathSource = null;
361      }
362    }
363  }
364
365
366  private class ClippedTreeCellRenderer extends DefaultTreeCellRenderer
367  {
368
369    Icon sharedFileIcon = Util.loadIcon( "sharedFile.gif" );
370    Icon sharedFolderIcon = Util.loadIcon( "project.gif" );
371
372    public Component getTreeCellRendererComponent( JTree tree,
373                                                   Object value,
374                                                   boolean sel,
375                                                   boolean expanded,
376                                                   boolean leaf,
377                                                   int row,
378                                                   boolean hasFocus )
379    {
380      super.getTreeCellRendererComponent ( tree, value, sel, expanded, leaf, row, hasFocus );
381      /*
382      if ( value instanceof GSProjectDirModel.GSRootNode )
383      {
384
385      }
386      else if ( value instanceof GSProjectDirModel.GSFileNode )
387      {
388        GSProjectDirModel.GSFileNode gsFileNode = ( GSProjectDirModel.GSFileNode ) value;
389        if ( isNotOwner( gsFileNode.gsFile ) )
390        {
391          setIcon( sharedFileIcon );
392        }
393      }
394      else if ( value instanceof GSProjectDirModel.GSProjectDirNode )
395      {
396        GSProjectDirModel.GSProjectDirNode gsFileNode = ( GSProjectDirModel.GSProjectDirNode ) value;
397        if ( isNotOwner( gsFileNode.gsFileMetadata ) )
398        {
399          setIcon( sharedFolderIcon );
400          // setToolTipText(gsFileNode.gsFileMetadata.getName() + " was shared to you by "+gsFileNode.gsFileMetadata.getOwner().getName());
401
402        }
403      }
404*/
405      return this;
406    }
407
408
409    /*
410    public boolean isNotOwner( GSFileMetadata gsFile )
411    {
412      String owner = gsFile.getOwner().getName();
413      String user = GenomeSpaceUI.getInstance().getGsUser().getName();
414      return !user.equals( owner );
415    }
416    */
417
418    public void paint( Graphics g )
419    {
420      String fullText = super.getText();
421      // getText() calls tree.convertValueToText();
422      // tree.convertValueToText() should call treeModel.convertValueToText(), if possible
423
424      String shortText = SwingUtilities.layoutCompoundLabel ( this,
425                                                              g.getFontMetrics(),
426                                                              fullText,
427                                                              getIcon(),
428                                                              getVerticalAlignment(),
429                                                              getHorizontalAlignment(),
430                                                              getVerticalTextPosition(),
431                                                              getHorizontalTextPosition(),
432                                                              getItemRect( itemRect ),
433                                                              iconRect,
434                                                              textRect,
435                                                              getIconTextGap() );
436
437      setText( shortText );      // temporarily truncate text
438      super.paint( g );
439      setText( fullText );      // restore full text
440    }
441
442    private Rectangle getItemRect( Rectangle itemRect )
443    {
444      getBounds( itemRect );
445      itemRect.width = tree.getWidth() - itemRect.x;
446      return itemRect;
447    }
448
449    // Rectangles filled in by SwingUtilities.layoutCompoundLabel();
450    private final Rectangle iconRect = new Rectangle();
451    private final Rectangle textRect = new Rectangle();
452    // Rectangle filled in by this.getItemRect();
453    private final Rectangle itemRect = new Rectangle();
454  }
455
456  private boolean isRootPath( TreePath path )
457  {
458    return tree.isRootVisible() && tree.getRowForPath( path ) == 0;
459  }
460
461  class TreeDropTargetListener implements java.awt.dnd.DropTargetListener
462  {
463    protected DataFlavor[] ok_flavors = null;
464
465    protected DataFlavor textFlavor = null;
466
467    public final void dragEnter( final DropTargetDragEvent e )
468    {
469      if ( !isDragAcceptable( e ) )
470      {
471        e.rejectDrag();
472      }
473      else
474      {
475        e.acceptDrag( e.getDropAction() );
476        // indicateGoodDrop();
477      }
478    }
479
480    public final void dragExit( final DropTargetEvent e )
481    {
482      // indicateNormal();
483    }
484
485    public final void dragOver( final DropTargetDragEvent e )
486    {
487      if ( !isDragAcceptable( e ) )
488      {
489        e.rejectDrag();
490      }
491      else
492      {
493        e.acceptDrag( e.getDropAction() );
494        // indicateGoodDrop();
495      }
496    }
497
498    public final void dropActionChanged( final DropTargetDragEvent e )
499    {
500      if ( !isDragAcceptable( e ) )
501      {
502        e.rejectDrag();
503      }
504      else
505      {
506        e.acceptDrag( e.getDropAction() );
507      }
508    }
509
510    public final void drop( final DropTargetDropEvent e )
511    {
512      if ( !isDropAcceptable( e ) )
513      {
514        e.rejectDrop();
515        // indicateNormal();
516        return;
517      }
518
519      e.acceptDrop( e.getDropAction() );
520
521      final Transferable transferable = e.getTransferable();
522
523      DataFlavor[] flavors = transferable.getTransferDataFlavors();
524      final int limit = flavors.length;
525      boolean dropComplete = false;
526
527      for ( int i = 0; i < limit; i++ )
528      {
529        final DataFlavor flavor = flavors[i];
530        try
531        {
532          if ( flavor.isMimeTypeEqual( DataFlavor.javaJVMLocalObjectMimeType ) )
533          {
534            final TreePath pathSource = ( TreePath ) transferable.getTransferData( flavor );
535            final Object last = pathSource.getLastPathComponent();
536
537            if ( last instanceof DefaultMutableTreeNode )
538            {
539              final DefaultMutableTreeNode sourceNode = ( DefaultMutableTreeNode ) last;
540
541              final TreePath path = getPathForLocation( e.getLocation().x, e.getLocation().y );
542              DefaultMutableTreeNode dropOnNode = ( DefaultMutableTreeNode ) path.getLastPathComponent();
543
544              Util.moveFile( sourceNode.toString(), dropOnNode.getParent().toString() );
545
546              dropComplete = true;
547              break;
548            }
549            else
550            {
551              System.err.println( "Note couldn't handle class " + last );
552            }
553          }
554        }
555        catch ( UnsupportedFlavorException ufe )
556        {
557          System.out.println( ufe );
558        }
559        catch ( java.io.IOException ioe )
560        {
561          System.out.println( ioe );
562        }
563      }
564
565      e.dropComplete( dropComplete );
566      // indicateNormal();
567    }
568
569    public final boolean isDragAcceptable( final DropTargetDragEvent e )
570    {
571      // Only accept particular flavors
572      if ( !isDataFlavorSupported( e ) )
573      {
574        return false;
575      }
576
577      try
578      {
579        final Transferable transferable = e.getTransferable();
580        DataFlavor[] flavors = transferable.getTransferDataFlavors();
581        final int limit = flavors.length;
582        boolean dropComplete = false;
583
584        for ( int i = 0; i < limit; i++ )
585        {
586          final DataFlavor flavor = flavors[i];
587          final TreePath pathSource = ( TreePath ) transferable.getTransferData( flavor );
588          final Object last = pathSource.getLastPathComponent();
589          if ( last instanceof DefaultMutableTreeNode )
590          {
591            return true;
592          }
593        }
594      }
595      catch ( Exception e2 )
596      {
597        e2.printStackTrace();
598      }
599      return false;
600    }
601
602    public final boolean isDropAcceptable( final DropTargetDropEvent e )
603    {
604      System.out.println( "Is drop acceptable = " + isDataFlavorSupported( e ) );
605      // Only accept particular flavors
606      if ( !isDataFlavorSupported( e ) )
607      {
608        return false;
609      }
610      return true;
611    }
612
613    /** determines if the DropTargetDropEvent will support one of the flavors */
614    protected final boolean isDataFlavorSupported( final DropTargetDropEvent e )
615    {
616
617      final int limit = getOkFlavors().length;
618      for ( int i = 0; i < limit; i++ )
619      {
620        if ( e.isDataFlavorSupported( getOkFlavors()[i] ) )
621        {
622          return true;
623        }
624      }
625
626      return false;
627    }
628
629    /** determines if the DropTargetDragEvent will support one of the flavors */
630    protected final boolean isDataFlavorSupported( final DropTargetDragEvent e )
631    {
632
633      final int limit = getOkFlavors().length;
634      for ( int i = 0; i < limit; i++ )
635      {
636        if ( e.isDataFlavorSupported( getOkFlavors()[i] ) )
637        {
638          return true;
639        }
640      }
641      return false;
642    }
643
644    /**
645     * all the supported data flavors note String Flavor is supported
646     * differently since it is assumes that it actually is a URL
647     */
648    protected DataFlavor[] getOkFlavors()
649    {
650      if ( ok_flavors == null )
651      {
652        try
653        {
654          textFlavor = new DataFlavor( "text/plain; class=java.io.InputStream" );
655          ok_flavors = new DataFlavor[]
656          { TransferableTreePath.TREEPATH_FLAVOR};
657        }
658        catch ( ClassNotFoundException e )
659        {
660          e.printStackTrace();
661        }
662      }
663      return ok_flavors;
664    }
665  }
666
667}
668