001    /*
002      Copyright (C) 2001-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,
011      but WITHOUT ANY WARRANTY; without even the implied warranty of
012      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013      GNU Lesser General Public License for more details.
014    
015      You should have received a copy of the GNU Lesser General Public License
016      along with this program; if not, write to the Free Software
017      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */
018    
019    package org.objectweb.jac.aspects.gui.swing;
020    
021    import java.awt.BorderLayout;
022    import java.awt.Insets;
023    import java.awt.Point;
024    import java.awt.event.ActionEvent;
025    import java.awt.event.ActionListener;
026    import java.awt.event.KeyEvent;
027    import java.awt.event.KeyListener;
028    import java.awt.event.MouseEvent;
029    import java.awt.event.MouseListener;
030    import java.util.List;
031    import javax.swing.DefaultListSelectionModel;
032    import javax.swing.JButton;
033    import javax.swing.JComponent;
034    import javax.swing.JPanel;
035    import javax.swing.JScrollPane;
036    import javax.swing.ListSelectionModel;
037    import javax.swing.event.ListSelectionEvent;
038    import javax.swing.event.ListSelectionListener;
039    import org.objectweb.jac.aspects.gui.*;
040    import org.objectweb.jac.aspects.session.SessionAC;
041    import org.objectweb.jac.core.Collaboration;
042    import org.objectweb.jac.core.Wrappee;
043    import org.objectweb.jac.core.rtti.ClassRepository;
044    import org.objectweb.jac.core.rtti.CollectionItem;
045    import org.objectweb.jac.core.rtti.FieldItem;
046    import org.objectweb.jac.core.rtti.MethodItem;
047    import org.objectweb.jac.core.rtti.NamingConventions;
048    import org.objectweb.jac.util.ExtArrays;
049    
050    /**
051     * Base class to implement ListView and TableView
052     */
053    public abstract class AbstractCollection extends AbstractView
054        implements ListSelectionListener, ActionListener, 
055                  MouseListener, CollectionView, KeyListener
056    {
057        CollectionItem collection;
058        Object substance;
059        CollectionModel model;
060        protected org.objectweb.jac.aspects.gui.CollectionItemView itemView;
061        JComponent component;
062    
063        JButton viewButton;
064        JButton removeButton;
065        JButton moveupButton; 
066        JButton movedownButton; 
067    
068        public CollectionModel getCollectionModel() {
069            return model;
070        }
071    
072        public AbstractCollection(ViewFactory factory, DisplayContext context,
073                                  CollectionItem collection, Object substance,
074                                  CollectionModel model, 
075                                  org.objectweb.jac.aspects.gui.CollectionItemView itemView) {
076            super(factory,context);
077            this.collection = collection;
078            this.substance = substance;
079            this.model = model;
080            this.itemView = itemView;
081    
082            setLayout(new BorderLayout());
083    
084            component = getInnerComponent(model);
085            component.addKeyListener(this);
086            JScrollPane scrollPane = new JScrollPane(component);
087            component.addMouseListener(this);
088            add(scrollPane, BorderLayout.CENTER);
089          
090            // add, remove and view buttons
091            JPanel lowerCont = new JPanel();
092            JButton b = null;
093    
094            CustomizedGUI customized = null;
095            List targets = null;
096            if (context.getCustomizedView()!=null) {
097                customized = context.getCustomizedView().getCustomizedGUI();
098                targets = customized.getFieldTargets(collection);
099            }
100    
101            if (targets==null || targets.size()==0) {
102                viewButton = createButton("view_icon","View","open");
103                viewButton.setEnabled(false);
104                lowerCont.add(viewButton);
105            }
106          
107            if (GuiAC.isAddable(substance,collection)) {
108                b = createButton("new_icon",GuiAC.getLabelAdd(),"add");
109                lowerCont.add(b);
110            }
111          
112            if (GuiAC.isRemovable(substance,collection)) {
113                removeButton = createButton("remove_icon","Remove","remove");
114                removeButton.setEnabled(false);
115                lowerCont.add(removeButton);
116            }
117    
118            if (collection.isList() && !collection.isCalculated()) {
119                moveupButton = createButton("up_icon","Move up","moveup");
120                moveupButton.setEnabled(false);
121                lowerCont.add(moveupButton);
122    
123                movedownButton = createButton("down_icon","Move up","movedown");
124                movedownButton.setEnabled(false);
125                lowerCont.add(movedownButton);
126            }
127    
128            MethodItem[] directMeths = (MethodItem[])
129                collection.getAttribute(GuiAC.DIRECT_COLLECTION_METHODS);
130            if (directMeths != null) {
131                for (int i=0; i<directMeths.length; i++) {
132                    // We should use SwingMethodView for more consistency
133                    lowerCont.add(new DirectButton(directMeths[i]));
134                }
135            }
136    
137            add(lowerCont,BorderLayout.SOUTH);
138    
139            //      Utils.registerCollection(substance,collection,this);
140        }
141    
142        protected JButton createButton(String icon, String tooltip, String action) {
143            JButton button = new JButton(ResourceManager.getIconResource(icon));
144            button.setToolTipText(tooltip);
145            button.setActionCommand(action);
146            button.addActionListener(this);
147            button.setMargin(new Insets(1,5,1,5));
148            return button;
149        }
150    
151        protected abstract JComponent getInnerComponent(Model model);
152    
153        boolean isEditor;
154        public boolean isEditor() {
155            return isEditor;
156        }
157        public void setEditor(boolean isEditor) {
158            this.isEditor = isEditor;
159        }
160    
161        public void setAutoUpdate(boolean autoUpdate) {
162            // TODO ...
163        }
164    
165        public void close(boolean validate) {
166            closed = true;
167            model.close();
168        }
169    
170        /**
171         * Defines what happens when the selection changes. 
172         */
173    
174        public void valueChanged(ListSelectionEvent event) {
175            loggerEvents.debug("valueChanged "+event);
176    
177            ListSelectionModel lsm = (ListSelectionModel)event.getSource();
178            if (lsm.isSelectionEmpty()) {
179                if (viewButton!=null)
180                    viewButton.setEnabled(false);
181                if (removeButton!=null)
182                    removeButton.setEnabled(false);
183                if (moveupButton!=null)
184                    moveupButton.setEnabled(false);
185                if (movedownButton!=null)
186                    movedownButton.setEnabled(false);
187                return;
188            } else if (event.getValueIsAdjusting()) {
189                return;
190            } else {
191                if (removeButton!=null)
192                    removeButton.setEnabled(true);
193                if (viewButton!=null)
194                    viewButton.setEnabled(selectionContainsAWrappee());
195                int indice = getSelectedIndices()[0];
196                CollectionPosition collpos = new CollectionPosition(
197                    this,
198                    collection,
199                    indice,
200                    substance);
201                EventHandler.get().onSelection(context,
202                                               collection,
203                                               getSelected()[0],
204                                               null,
205                                               collpos);
206                //null);
207             
208                if (moveupButton!=null)
209                    moveupButton.setEnabled(indice>0);
210                if (movedownButton!=null)
211                    movedownButton.setEnabled(indice<model.getRowCount()-1);
212            }
213        }
214    
215        /**
216         * Tells wether the selection contains at least on Wrappee object
217         */
218        boolean selectionContainsAWrappee() {
219            Object[] selection = getSelected();
220            for (int i=0; i<selection.length; i++) {
221                if (selection[i] instanceof Wrappee) {
222                    return true;
223                }
224            }
225            return false;
226        }
227    
228        // KeyListener interface
229    
230        public void keyTyped(KeyEvent event) {}
231    
232        public void keyPressed(KeyEvent event) {}
233    
234        public void keyReleased(KeyEvent event) {
235            if (event.getKeyCode()==KeyEvent.VK_DELETE &&
236                removeButton!=null
237                /* GuiAC.isRemovable(collection) // this does not work
238                   because the session's context is not initialized */ ) {
239                doDelete();
240            }
241        }
242    
243        void doDelete() {
244            Object[] selected = getSelected();
245            MethodItem removeMethod = collection.getRemover();
246            loggerEvents.debug("remover="+removeMethod);
247            if (removeMethod!=null) {
248                // try with the removing method
249                for (int i=0; i<selected.length; i++) {
250                    EventHandler.get().onRemoveFromCollection(
251                        context,
252                        new RemoveEvent(
253                            this,
254                            substance,
255                            collection,
256                            selected[i]),
257                        false);
258                }
259            } else {
260                // call the collection's method directly
261                /*
262                  getMethod = collection.getGetter();
263                  if (getMethod!=null) {
264                  MethodItem method = null;
265                  Object c;
266                  try {
267                  c = getMethod.invoke(substance,ExtArrays.emptyObjectArray);
268                  } catch (Exception e) {
269                  Log.error("Getting collection with "+getMethod.getName()+
270                  " failed : "+e);
271                  return;
272                  }
273                  ClassItem cl = ClassRepository.get().getClass(c.getClass());
274                  if (!collection.isMap()) {
275                  method = cl.getMethod("remove(java.lang.Object)");
276                  Log.trace("gui","Invoking "+method.getName()+" on collection itself");
277                  for (int i=0; i<selected.length; i++) {
278                  try {
279                  method.invoke(c,new Object[] {selected[i]});
280                  } catch (Exception e) {
281                  Log.error("Removing from collection with "+method.getName()+
282                  " failed : "+e);
283                  }
284                  }
285                  }
286                */   
287            }
288            onRemove();
289        }
290    
291        /**
292         * Handles the actions on this view.
293         *
294         * <p>On a collection view, the three default possible actions are
295         * to open a new view on the selected item, to add a new item to
296         * the collection, and to remove the selected item from the
297         * collection.
298         *
299         * @param event the user event 
300         */
301        public void actionPerformed(ActionEvent event) {
302            setContext();
303            loggerEvents.debug("action performed on collection : "+event.getActionCommand()+
304                      "; modifiers = "+event.getModifiers());
305            if (event.getActionCommand().equals("open")) {
306                Object[] selected = getSelected();
307                for (int i=0; i<selected.length; i++) {
308                    try {
309                        EventHandler.get().onSelection(
310                            context,collection,selected[i],null,null,true);
311                    } catch (Exception e) {
312                        loggerEvents.error("failed to view "+selected[i],e);
313                    }
314                }
315            } else if (event.getActionCommand().equals("add")) {
316                EventHandler.get().onAddToCollection(
317                    context,
318                    new AddEvent(this,substance,collection,null),
319                    (event.getModifiers()& ActionEvent.CTRL_MASK) != 0);
320            } else if (event.getActionCommand().equals("remove")) {
321                doDelete();
322            } else if (event.getActionCommand().equals("movedown")) {
323                Object selected = getSelected()[0];
324                int index = getSelectedIndices()[0];
325                if (index<model.getRowCount()-1) {
326                    Integer i = new Integer(index);
327                    collection.remove(substance,selected,i);
328                    CollectionUpdate update = getCollectionUpdate();
329                    update.onRemove(substance, collection, null, i , null);
330                    i = new Integer(index+1);
331                    collection.add(substance,selected,i);
332                    update.onAdd(substance, collection, null, i, null);
333                    setSelected(index+1);
334                }
335            } else if (event.getActionCommand().equals("moveup")) {
336                Object selected = getSelected()[0];
337                int index = getSelectedIndices()[0];
338                if (index>0) {
339                    Integer i = new Integer(index);
340                    collection.remove(substance,selected,i);
341                    CollectionUpdate update = getCollectionUpdate();
342                    update.onRemove(substance, collection, null, i, null);
343                    i = new Integer(index-1);
344                    collection.add(substance,selected,i);
345                    update.onAdd(substance, collection, null, i, null);
346                    setSelected(index-1);
347                }
348            }
349        }
350    
351        // button for direct method
352        class DirectButton extends JButton 
353            implements ListSelectionListener, ActionListener 
354        {
355            MethodItem method;
356            DirectButton(MethodItem method) {
357                super(GuiAC.getLabel(method));
358                this.method = method;
359                setIcon(ResourceManager.getIcon(GuiAC.getIcon(method)));
360                addActionListener(this);
361                setEnabled(false);
362                getSelectionModel().addListSelectionListener(this);
363            }
364            public void valueChanged(ListSelectionEvent event) {
365                ListSelectionModel lsm = (ListSelectionModel)event.getSource();
366                setEnabled(!lsm.isSelectionEmpty());
367            }
368            public void actionPerformed(ActionEvent event) {
369                Collaboration.get().addAttribute(
370                    SessionAC.SESSION_ID, GuiAC.getLocalSessionID());
371    
372                Object[] selected = getSelected();
373                for (int i=0; i<selected.length; i++) {
374                    try {
375                        EventHandler.get().onInvoke(
376                            context,
377                            new InvokeEvent(AbstractCollection.this,selected[i],method)).join();
378                    } catch(InterruptedException e) {
379                        context.getDisplay().showModal(
380                            e,
381                            "Error: "+e,
382                            "An error occured during the invocation of "+
383                                    method.getCompactFullName()+
384                                    " on "+selected[i].getClass().getName()+
385                                    " \""+GuiAC.toString(selected[i])+"\"",
386                            context.getWindow(),
387                            false,false,true);
388                    }
389                }
390            }   
391        }
392    
393        // should call clear selection on the component's selection model
394        protected abstract void onRemove();
395    
396        protected abstract CollectionUpdate getCollectionUpdate();
397    
398        /**
399         * Returns an array of the selected objects. The array is empty if
400         * no object is selected, but not null.
401         **/
402    
403        protected Object[] getSelected() {
404            Object[] selected = null;
405            int[] indices = getSelectedIndices();
406            if (indices != null) {
407                selected = new Object[indices.length];
408                for (int i=0; i<indices.length; i++)
409                {
410                    selected[i]= model.getObject(indices[i]);
411                }
412            } else {
413                selected = ExtArrays.emptyObjectArray;
414            }
415            return selected;
416        }
417    
418        /**
419         * Returns the indices of selected objects.
420         */
421        protected abstract int[] getSelectedIndices();
422    
423        protected abstract ListSelectionModel getSelectionModel();
424    
425        protected void setNoRefresh(boolean norefresh) {
426            if (norefresh==false) {
427                repaint();
428            }
429        }
430    
431        static class BetterListSelectionModel extends DefaultListSelectionModel {
432            JComponent list = null;
433            public BetterListSelectionModel( JComponent list ) {
434                super();
435                this.list = list;
436            }
437            public JComponent getList() {
438                return list;
439            }
440        }
441    
442        // MouseListener interface
443    
444        public void mouseClicked(MouseEvent me){
445            loggerEvents.debug("mouseClicked");
446            if (model.getRowCount()==1) {
447                loggerEvents.debug("rowCount==1 => forceRefresh");
448                if (getSelectedIndices().length>0) {
449                    CollectionPosition collpos = new CollectionPosition(
450                        this,
451                        collection,
452                        getSelectedIndices()[0],
453                        substance);
454                    EventHandler.get().onSelection(context,
455                                                   collection,
456                                                   getSelected()[0],
457                                                   null,
458                                                   collpos);
459                }
460            }
461        }
462    
463        public void mousePressed(MouseEvent e) {
464            maybeShowPopup(e);
465        }
466        public void mouseReleased(MouseEvent e) {
467            maybeShowPopup(e);
468        }
469        public void mouseExited(MouseEvent me){}
470        public void mouseEntered(MouseEvent me){}   
471        void maybeShowPopup(MouseEvent event) {
472            loggerEvents.debug("maybeShowPopup");
473            if (event.isPopupTrigger()) {
474                int index = locationToIndex(event.getPoint());
475                if (index!=-1) {
476                    Object o = model.getObject(index);
477                    if (o instanceof Wrappee) 
478                        SwingEvents.showObjectMenu(context, o, event);
479                }
480            }
481        }
482        /**
483         * Returns the index of the element at the given location, or -1 if
484         * if no element is under this location .  */
485        abstract int locationToIndex(Point location);
486    
487        public void setField(FieldItem field) {
488            collection = (CollectionItem)field;
489        }
490    
491        public void setSubstance(Object substance) {
492            this.substance = substance;
493        }
494    
495        public Object getSubstance() {
496            return substance;
497        }
498    
499        public FieldItem getField() {
500            return collection;
501        }
502    
503        public void setValue(Object value) {
504        }
505    
506        public void updateModel(Object substance) {
507        }
508    
509    }