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