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    }