001 /* 002 Copyright (C) 2003-2003 Renaud Pawlak <renaud@aopsys.com>, 003 Laurent Martelli <laurent@aopsys.com> 004 005 This program is free software; you can redistribute it and/or modify 006 it under the terms of the GNU Lesser General Public License as 007 published by the Free Software Foundation; either version 2 of the 008 License, or (at your option) any later version. 009 010 This program is distributed in the hope that it will be useful, but 011 WITHOUT ANY WARRANTY; without even the implied warranty of 012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 Lesser General Public License for more details. 014 015 You should have received a copy of the GNU Lesser General Public 016 License along with this program; if not, write to the Free Software 017 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 018 USA */ 019 020 package org.objectweb.jac.aspects.gui.swing; 021 022 023 import java.awt.BorderLayout; 024 import java.awt.Component; 025 import java.awt.Dimension; 026 import java.awt.datatransfer.*; 027 import java.awt.dnd.*; 028 import java.awt.event.ActionEvent; 029 import java.awt.event.ActionListener; 030 import java.awt.event.KeyEvent; 031 import java.awt.event.KeyListener; 032 import java.awt.event.MouseEvent; 033 import java.awt.event.MouseListener; 034 import java.util.Arrays; 035 import java.util.Enumeration; 036 import java.util.List; 037 import javax.swing.Icon; 038 import javax.swing.ImageIcon; 039 import javax.swing.JButton; 040 import javax.swing.JPanel; 041 import javax.swing.JScrollPane; 042 import javax.swing.JTree; 043 import javax.swing.ToolTipManager; 044 import javax.swing.event.TreeExpansionEvent; 045 import javax.swing.event.TreeExpansionListener; 046 import javax.swing.event.TreeSelectionEvent; 047 import javax.swing.event.TreeSelectionListener; 048 import javax.swing.tree.DefaultTreeCellRenderer; 049 import javax.swing.tree.TreeNode; 050 import javax.swing.tree.TreePath; 051 import org.apache.log4j.Logger; 052 import org.objectweb.jac.aspects.gui.*; 053 import org.objectweb.jac.aspects.gui.Transfer; 054 import org.objectweb.jac.core.Collaboration; 055 import org.objectweb.jac.core.Wrappee; 056 import org.objectweb.jac.core.rtti.CollectionItem; 057 import org.objectweb.jac.core.rtti.FieldItem; 058 import org.objectweb.jac.core.rtti.MethodItem; 059 060 /** 061 * This class defines a Swing component tree view for objects that are 062 * related to a root object through relations or collections. 063 * 064 * @see GuiAC */ 065 066 public class Tree extends AbstractView 067 implements View, TreeSelectionListener, MouseListener, 068 TreeExpansionListener, TreeListener, KeyListener, 069 DragGestureListener, DragSourceListener, DropTargetListener 070 { 071 static Logger logger = Logger.getLogger("gui.treeview"); 072 073 JTree tree; 074 TreeModel model; 075 // String pathDef = null; 076 boolean showRelations = true; 077 078 JButton viewButton = null; 079 JButton newButton = null; 080 JButton removeButton = null; 081 082 RootNode rootNode = null; 083 084 /** 085 * Builds a new tree view. 086 * 087 * @param pathDef designate root objects of the tree 088 * @param showRelations wether to build a node for relation items 089 */ 090 public Tree(ViewFactory factory, DisplayContext context, 091 String pathDef, boolean showRelations ) { 092 super(factory,context); 093 094 this.showRelations = showRelations; 095 096 setLayout(new BorderLayout()); 097 098 tree = new JTree(); 099 100 DragSource dragSource = DragSource.getDefaultDragSource(); 101 // creating the recognizer is all that's necessary - it 102 // does not need to be manipulated after creation 103 dragSource.createDefaultDragGestureRecognizer( 104 tree, // component where drag originates 105 DnDConstants.ACTION_COPY_OR_MOVE, // actions 106 this); // drag gesture listener 107 108 JScrollPane upperCont = new JScrollPane(tree); 109 tree.addTreeExpansionListener(this); 110 JPanel downCont = new JPanel(); 111 112 tree.putClientProperty("JTree.lineStyle", "Angled"); 113 ToolTipManager.sharedInstance().registerComponent(tree); 114 tree.setRootVisible(false); 115 tree.addTreeSelectionListener( this ); 116 117 rootNode = new RootNode(); 118 model = new TreeModel(rootNode, pathDef, showRelations); 119 tree.setModel(model); 120 tree.setCellRenderer(new TreeNodeRenderer()); 121 tree.addMouseListener(this); 122 tree.addKeyListener(this); 123 model.addTreeListener(this); 124 125 // add, remove and view buttons 126 viewButton = createButton("view_icon","View",new openHandler()); 127 downCont.add(viewButton); 128 129 130 newButton = createButton("new_icon","Add",new addHandler()); 131 downCont.add(newButton); 132 133 removeButton = createButton( 134 "remove_icon","Remove", 135 new ActionListener() { 136 public void actionPerformed(ActionEvent event) { 137 doDelete(false); 138 } 139 } 140 ); 141 142 downCont.add(removeButton); 143 144 add(upperCont, BorderLayout.CENTER); 145 add(downCont, BorderLayout.SOUTH); 146 147 new DropTarget(tree, // component 148 DnDConstants.ACTION_COPY_OR_MOVE, // actions 149 this); // DropTargetListener 150 151 expandRoot(); 152 } 153 154 // DND interfaces 155 public void dragGestureRecognized(DragGestureEvent e) { 156 loggerDnd.debug("drag gesture detected"); 157 // drag anything ... 158 159 TreePath tp = tree.getPathForLocation( 160 (int)e.getDragOrigin().getX(), (int)e.getDragOrigin().getY() ); 161 if (tp != null) { 162 AbstractNode node=(AbstractNode) tp.getLastPathComponent(); 163 Object o = node.getUserObject(); 164 if(o instanceof Wrappee) { 165 node=(AbstractNode)node.getParent(); 166 Object parent = node.getUserObject(); 167 if(parent != null && (parent instanceof FieldItem)) { 168 node=(AbstractNode)node.getParent(); 169 parent = node.getUserObject(); 170 } 171 Wrappee[] toTransfer; 172 if(parent instanceof Wrappee) { 173 toTransfer=new Wrappee[] {(Wrappee)o,(Wrappee)parent}; 174 } else { 175 toTransfer=new Wrappee[] {(Wrappee)o,null}; 176 } 177 loggerDnd.debug("to transfer: "+Arrays.asList(toTransfer)); 178 e.startDrag( 179 null,//DragSource.DefaultCopyDrop, // cursor 180 Transfer.getJACTransfer(toTransfer), 181 this); // drag source listener 182 } 183 } 184 } 185 public void dragDropEnd(DragSourceDropEvent e) {} 186 public void dragEnter(DragSourceDragEvent e) {} 187 public void dragExit(DragSourceEvent e) {} 188 public void dragOver(DragSourceDragEvent e) {} 189 public void dropActionChanged(DragSourceDragEvent e) {} 190 191 public void drop(DropTargetDropEvent e) { 192 try { 193 loggerDnd.debug("drop event"); 194 Transferable tr = e.getTransferable(); 195 TreePath tp = tree.getPathForLocation( 196 (int)e.getLocation().getX(), (int)e.getLocation().getY() ); 197 if(tp!=null) { 198 List transfered=Transfer.getTransferedWrappees(tr); 199 Object droppedObject=transfered.get(0); 200 Object source=transfered.get(1); 201 Object target=((AbstractNode) tp.getLastPathComponent()).getUserObject(); 202 loggerDnd.debug("target="+target+", droppedObject="+ 203 droppedObject+", source="+source); 204 if(droppedObject==null || target==null || 205 (!(target instanceof Wrappee)) ) return; 206 EventHandler.get().onDropObject(getContext(),target,droppedObject,source,false); 207 } 208 } catch(Exception ex) { 209 ex.printStackTrace(); 210 } 211 } 212 public void dragEnter(DropTargetDragEvent e) {} 213 public void dragExit(DropTargetEvent e) {} 214 public void dragOver(DropTargetDragEvent e) {} 215 public void dropActionChanged(DropTargetDragEvent e) {} 216 217 // end of DND interfaces 218 219 /** 220 * Expands nodes linked to the tree root. Useful because it is not 221 * automatically done and nodes or expand buttons are not displayed 222 * at the beginning (you can't open children). 223 */ 224 225 public void expandRoot() 226 { 227 Object root = model.getRoot(); 228 int rootCount = model.getChildCount(root); 229 for (int i = 0; i < rootCount; i++) 230 { 231 Object child = model.getChild(root, i); 232 TreePath path = new TreePath(model.getPathToRoot((TreeNode) child)); 233 tree.expandPath(path); 234 } 235 } 236 237 /** 238 * Create a disabled button 239 * @param inconName resource name of the icon 240 * @param text text of the icon, which is used as a tooltip if iconName!=null 241 * @param listener an ActionListener for the button 242 * @return a new button 243 */ 244 JButton createButton(String iconName, String text, ActionListener listener) { 245 ImageIcon icon = ResourceManager.getIconResource(iconName); 246 JButton button; 247 if (icon==null) 248 button = new JButton (text); 249 else { 250 button = new JButton(icon); 251 button.setToolTipText(text); 252 } 253 button.addActionListener(listener); 254 button.setEnabled(false); 255 return button; 256 } 257 258 // interface TreeView 259 260 public void close(boolean validate) { 261 model.unregisterEvents(); 262 } 263 264 void selectObject(AbstractNode root,Object toSelect) { 265 Enumeration children=root.children(); 266 while(children.hasMoreElements()) { 267 AbstractNode node = (AbstractNode)children.nextElement(); 268 if( node.getUserObject()==toSelect ) { 269 AbstractNode parent = (AbstractNode)node.getParent(); 270 while (parent!=null) { 271 tree.expandPath(new TreePath(parent.getPath())); 272 parent = (AbstractNode)parent.getParent(); 273 } 274 tree.expandPath(new TreePath(node.getPath())); 275 tree.addSelectionPath(new TreePath(node.getPath())); 276 } 277 selectObject(node,toSelect); 278 } 279 } 280 281 public void treeCollapsed(TreeExpansionEvent event) { 282 loggerEvents.debug("treeCollapsed "+event.getPath()); 283 } 284 285 public void treeExpanded(TreeExpansionEvent event) { 286 loggerEvents.debug("treeExpanded "+event.getPath()); 287 Object node = (AbstractNode)event.getPath().getLastPathComponent(); 288 if (node instanceof ObjectNode) { 289 ((ObjectNode)node).updateChildren(); 290 } 291 } 292 293 /** 294 * Handles the tree view selection changes. 295 */ 296 public void valueChanged(TreeSelectionEvent event) { 297 loggerEvents.debug("valueChanged "+event.getSource().getClass().getName()); 298 AbstractNode node = (AbstractNode) 299 tree.getLastSelectedPathComponent(); 300 if (node == null) { 301 viewButton.setEnabled(false); 302 removeButton.setEnabled(false); 303 } else { 304 Object selected = node.getUserObject(); 305 if (selected instanceof CollectionItem) { 306 loggerEvents.debug("selected the collection "+ 307 ((CollectionItem)selected).getName()); 308 viewButton.setEnabled(false); 309 newButton.setEnabled(true); 310 removeButton.setEnabled(doDelete(true)); 311 } else { 312 loggerEvents.debug("selected a wrappee "+selected); 313 viewButton.setEnabled(selected!=null); 314 newButton.setEnabled(false); 315 removeButton.setEnabled(doDelete(true)); 316 EventHandler.get().onNodeSelection(context,node,false); 317 } 318 } 319 } 320 321 // MouseListener interface 322 323 /** Do nothing. */ 324 public void mouseClicked(MouseEvent me){} 325 326 /** 327 * Shows a popup if needed. 328 * 329 * @param e mouse event descriptor */ 330 331 public void mousePressed(MouseEvent e) { 332 maybeShowPopup(e); 333 } 334 335 /** 336 * Closes a popup if needed. 337 * 338 * @param e mouse event descriptor */ 339 340 public void mouseReleased(MouseEvent e) { 341 maybeShowPopup(e); 342 } 343 344 /** Do nothing. */ 345 public void mouseExited(MouseEvent me){} 346 /** Do nothing. */ 347 public void mouseEntered(MouseEvent me){} 348 349 private void maybeShowPopup(MouseEvent e) { 350 loggerEvents.debug("maybeShowPopup: "+e.getClass().getName()); 351 TreePath tp = tree.getPathForLocation( e.getX(), e.getY() ); 352 if (tp != null) { 353 Object o = ((AbstractNode) tp.getLastPathComponent()).getUserObject(); 354 if (e.isPopupTrigger()) { 355 //tree.setSelectionPath(tp); 356 if (o instanceof Wrappee) 357 SwingEvents.showObjectMenu(context, o, e); 358 } 359 } 360 } 361 362 // KeyListener interface 363 364 public void keyTyped(KeyEvent event) {} 365 366 public void keyPressed(KeyEvent event) {} 367 368 public void keyReleased(KeyEvent event) { 369 if (event.getKeyCode()==KeyEvent.VK_DELETE) { 370 doDelete(false); 371 } 372 } 373 374 public void setSelection(TreePath selection) { 375 tree.setSelectionPath(selection); 376 } 377 378 /** 379 * Handles "open" actions 380 */ 381 class openHandler implements ActionListener { 382 public void actionPerformed(ActionEvent event) { 383 loggerEvents.debug("action performed: OPEN"); 384 setContext(); 385 AbstractNode node = (AbstractNode)tree 386 .getAnchorSelectionPath().getLastPathComponent(); 387 AbstractNode parentNode = (AbstractNode)node.getParent(); 388 Collaboration.get().addAttribute( 389 "Session.sid", "Swing"+org.objectweb.jac.core.dist.Distd.getLocalContainerName() ); 390 391 if (node instanceof ObjectNode) { 392 ObjectNode objectNode = (ObjectNode)node; 393 EventHandler.get().onSelection( 394 context,objectNode.getRelation(),objectNode.getUserObject(), 395 null,null,true); 396 } 397 } 398 } 399 400 /** 401 * Handles "add" actions 402 */ 403 class addHandler implements ActionListener { 404 public void actionPerformed(ActionEvent event) { 405 loggerEvents.debug("action performed: ADD"); 406 setContext(); 407 AbstractNode node = (AbstractNode)tree 408 .getAnchorSelectionPath().getLastPathComponent(); 409 AbstractNode parentNode = (AbstractNode)node.getParent(); 410 Collaboration.get().addAttribute( 411 "Session.sid", "Swing"+org.objectweb.jac.core.dist.Distd.getLocalContainerName() ); 412 413 Object o = node.getUserObject(); 414 if( o instanceof CollectionItem ) { 415 if( parentNode != null ) { 416 try { 417 MethodItem[] addingMethods = ((CollectionItem)o).getAddingMethods(); 418 if( addingMethods != null && addingMethods.length > 0 ) { 419 logger.debug("invoking "+addingMethods[0]+" on "+ 420 parentNode.getUserObject()); 421 if (((CollectionItem)o).getAttribute(GuiAC.AUTO_CREATE)!=null) { 422 logger.debug("auto creation asked"); 423 Collaboration.get().addAttribute( 424 GuiAC.AUTO_CREATE, addingMethods[0].getParameterTypes()[0]); 425 } 426 Collaboration.get().addAttribute( 427 GuiAC.ASK_FOR_PARAMETERS, addingMethods[0]); 428 Collaboration.get().addAttribute( 429 GuiAC.DISPLAY_CONTEXT, context); 430 addingMethods[0].invoke( 431 parentNode.getUserObject(), 432 new Object[addingMethods[0].getParameterTypes().length]); 433 } 434 } catch( Exception e ) { 435 e.printStackTrace(); 436 } 437 } 438 } 439 } 440 } 441 442 /** 443 * Delete selected objects. 444 * 445 * @param softRun if true, will not actually call delete, but just 446 * return if the user is allowed to see a delete button. 447 * @return true if the user is allowed to see a delete button for 448 * all of the selected objects. 449 */ 450 protected boolean doDelete(boolean softRun) { 451 setContext(); 452 453 TreePath[] selectionPaths = tree.getSelectionPaths(); 454 if (selectionPaths==null) 455 return false; 456 boolean result = true; 457 for (int i=0; i<selectionPaths.length; i++) { 458 loggerEvents.debug("selectionPath = "+selectionPaths[i]); 459 AbstractNode node = 460 (AbstractNode)selectionPaths[i].getLastPathComponent(); 461 AbstractNode parentNode = 462 (AbstractNode)node.getParent(); 463 Object selectedObject = node.getUserObject(); 464 if (selectedObject instanceof CollectionItem) { 465 CollectionItem collection = (CollectionItem)selectedObject; 466 // If the selected node is a collection node, 467 // interactively invoke the remove method 468 if (parentNode != null && 469 GuiAC.isRemovable(parentNode.getUserObject(),collection)) { 470 if (!softRun) { 471 EventHandler.get().onRemoveFromCollection( 472 context, 473 new RemoveEvent( 474 this, 475 parentNode.getUserObject(), 476 (CollectionItem)selectedObject, 477 null), 478 false); 479 } else { 480 result = false; 481 } 482 } 483 } else if (node instanceof ObjectNode && 484 ((ObjectNode)node).getRelation() instanceof CollectionItem) { 485 loggerEvents.debug("node = "+node+"; parentNode="+parentNode); 486 FieldItem coll = ((ObjectNode)node).getRelation(); 487 if (coll instanceof CollectionItem && 488 GuiAC.isRemovable(((ObjectNode)node).getSubstance(), 489 (CollectionItem)coll)) 490 { 491 if (!softRun) { 492 Collaboration.get().addAttribute(GuiAC.REMOVED_NODE,node); 493 try { 494 EventHandler.get().onRemoveFromCollection( 495 context, 496 new RemoveEvent( 497 this, 498 ((ObjectNode)node).getSubstance(), 499 (CollectionItem)coll, 500 selectedObject), 501 false); 502 } finally { 503 Collaboration.get().removeAttribute(GuiAC.REMOVED_NODE); 504 } 505 } 506 } else { 507 result = false; 508 } 509 } else { 510 result = false; 511 } 512 } 513 return result; 514 } 515 516 static class TreeNodeRenderer extends DefaultTreeCellRenderer { 517 518 public TreeNodeRenderer() {} 519 520 public Component getTreeCellRendererComponent(JTree tree, 521 Object value, 522 boolean sel, 523 boolean expanded, 524 boolean leaf, 525 int row, 526 boolean hasFocus) { 527 528 this.hasFocus = hasFocus; 529 AbstractNode node = (AbstractNode)value; 530 setText(node.getText()); 531 setToolTipText(node.getToolTip()); 532 533 if(sel) 534 setForeground(getTextSelectionColor()); 535 else 536 setForeground(getTextNonSelectionColor()); 537 // There needs to be a way to specify disabled icons. 538 if (!tree.isEnabled()) { 539 setEnabled(false); 540 if (leaf) { 541 setDisabledIcon(getLeafIcon()); 542 } else if (expanded) { 543 setDisabledIcon(getOpenIcon()); 544 } else { 545 setDisabledIcon(getClosedIcon()); 546 } 547 } else { 548 setEnabled(true); 549 setIcon(ResourceManager.getIcon(node.getIcon())); 550 } 551 setComponentOrientation(tree.getComponentOrientation()); 552 selected = sel; 553 554 return this; 555 } 556 557 public Dimension getPreferredSize() { 558 Dimension dim = super.getPreferredSize(); 559 Icon icon = getIcon(); 560 if (icon!=null) { 561 if (icon.getIconHeight()+2>dim.getHeight()) 562 dim.setSize((int)dim.getWidth(), getIcon().getIconHeight()+2); 563 } 564 return dim; 565 } 566 567 } 568 }